diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java b/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java index 5282eed49e..42a1f60f58 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java @@ -143,6 +143,8 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder void setSharedObject(Class sharedType, C object) { super.setSharedObject(sharedType, object); @@ -2729,7 +2743,12 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder Unit) * @since 5.3 * @param http the [HttpSecurity] which all configurations will be applied to * @param init the configurations to apply to the provided [HttpSecurity] + * @property authenticationManager the default [AuthenticationManager] to use */ @SecurityMarker class HttpSecurityDsl(private val http: HttpSecurity, private val init: HttpSecurityDsl.() -> Unit) { private val HANDLER_MAPPING_INTROSPECTOR = "org.springframework.web.servlet.handler.HandlerMappingIntrospector" + var authenticationManager: AuthenticationManager? = null + /** * Allows configuring the [HttpSecurity] to only be invoked when matching the * provided pattern. @@ -858,5 +862,6 @@ class HttpSecurityDsl(private val http: HttpSecurity, private val init: HttpSecu */ internal fun build() { init() + authenticationManager?.also { this.http.authenticationManager(authenticationManager) } } } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/builders/HttpSecurityAuthenticationManagerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/builders/HttpSecurityAuthenticationManagerTests.java new file mode 100644 index 0000000000..47a14e9713 --- /dev/null +++ b/config/src/test/java/org/springframework/security/config/annotation/web/builders/HttpSecurityAuthenticationManagerTests.java @@ -0,0 +1,119 @@ +/* + * Copyright 2012-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.config.annotation.web.builders; + +import org.junit.Rule; +import org.junit.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.TestingAuthenticationToken; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.test.SpringTestRule; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.test.web.servlet.MockMvc; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.springframework.security.config.Customizer.withDefaults; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; + +public class HttpSecurityAuthenticationManagerTests { + + @Autowired + MockMvc mvc; + + @Rule + public final SpringTestRule spring = new SpringTestRule(); + + @Test + public void authenticationManagerWhenConfiguredThenUsed() throws Exception { + this.spring.register(AuthenticationManagerConfig.class).autowire(); + + given(AuthenticationManagerConfig.AUTHENTICATION_MANAGER.authenticate(any())) + .willReturn(new TestingAuthenticationToken("user", "test", "ROLE_USER")); + + this.mvc.perform(get("/").with(httpBasic("user", "test"))); + + verify(AuthenticationManagerConfig.AUTHENTICATION_MANAGER).authenticate(any()); + } + + @Test + public void authenticationManagerWhenBuilderAndAuthenticationManagerConfiguredThenBuilderIgnored() + throws Exception { + this.spring.register(AuthenticationManagerBuilderConfig.class).autowire(); + + given(AuthenticationManagerBuilderConfig.AUTHENTICATION_MANAGER.authenticate(any())) + .willReturn(new TestingAuthenticationToken("user", "test", "ROLE_USER")); + + this.mvc.perform(get("/").with(httpBasic("user", "test"))); + + verify(AuthenticationManagerBuilderConfig.AUTHENTICATION_MANAGER).authenticate(any()); + verifyNoInteractions(AuthenticationManagerBuilderConfig.USER_DETAILS_SERVICE); + } + + @EnableWebSecurity + static class AuthenticationManagerConfig extends WebSecurityConfigurerAdapter { + + static final AuthenticationManager AUTHENTICATION_MANAGER = mock(AuthenticationManager.class); + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests((authz) -> authz + .anyRequest().authenticated() + ) + .httpBasic(withDefaults()) + .authenticationManager(AUTHENTICATION_MANAGER); + // @formatter:on + } + + } + + @EnableWebSecurity + static class AuthenticationManagerBuilderConfig extends WebSecurityConfigurerAdapter { + + static final AuthenticationManager AUTHENTICATION_MANAGER = mock(AuthenticationManager.class); + static final UserDetailsService USER_DETAILS_SERVICE = mock(UserDetailsService.class); + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests((authz) -> authz + .anyRequest().authenticated() + ) + .httpBasic(withDefaults()) + .authenticationManager(AUTHENTICATION_MANAGER); + // @formatter:on + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.userDetailsService(USER_DETAILS_SERVICE); + } + + } + +} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java index 6a4d40e789..2b8e7a727a 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java @@ -996,6 +996,24 @@ public class OAuth2ResourceServerConfigurerTests { verifyBean(AuthenticationProvider.class).authenticate(any(Authentication.class)); } + @Test + public void getWhenDefaultAndCustomJwtAuthenticationManagerThenCustomUsed() throws Exception { + this.spring.register(DefaultAndJwtAuthenticationManagerConfig.class, BasicController.class).autowire(); + DefaultAndJwtAuthenticationManagerConfig config = this.spring.getContext() + .getBean(DefaultAndJwtAuthenticationManagerConfig.class); + AuthenticationManager defaultAuthenticationManager = config.defaultAuthenticationManager(); + AuthenticationManager jwtAuthenticationManager = config.jwtAuthenticationManager(); + given(defaultAuthenticationManager.authenticate(any())) + .willThrow(new RuntimeException("should not interact with default auth manager")); + given(jwtAuthenticationManager.authenticate(any())).willReturn(JWT_AUTHENTICATION_TOKEN); + // @formatter:off + this.mvc.perform(get("/authenticated").with(bearerToken("token"))) + .andExpect(status().isOk()) + .andExpect(content().string("mock-test-subject")); + // @formatter:on + verify(jwtAuthenticationManager).authenticate(any(Authentication.class)); + } + @Test public void getWhenIntrospectingThenOk() throws Exception { this.spring.register(RestOperationsConfig.class, OpaqueTokenConfig.class, BasicController.class).autowire(); @@ -1054,6 +1072,24 @@ public class OAuth2ResourceServerConfigurerTests { verifyBean(AuthenticationProvider.class).authenticate(any(Authentication.class)); } + @Test + public void getWhenDefaultAndCustomIntrospectionAuthenticationManagerThenCustomUsed() throws Exception { + this.spring.register(DefaultAndOpaqueTokenAuthenticationManagerConfig.class, BasicController.class).autowire(); + DefaultAndOpaqueTokenAuthenticationManagerConfig config = this.spring.getContext() + .getBean(DefaultAndOpaqueTokenAuthenticationManagerConfig.class); + AuthenticationManager defaultAuthenticationManager = config.defaultAuthenticationManager(); + AuthenticationManager opaqueTokenAuthenticationManager = config.opaqueTokenAuthenticationManager(); + given(defaultAuthenticationManager.authenticate(any())) + .willThrow(new RuntimeException("should not interact with default auth manager")); + given(opaqueTokenAuthenticationManager.authenticate(any())).willReturn(INTROSPECTION_AUTHENTICATION_TOKEN); + // @formatter:off + this.mvc.perform(get("/authenticated").with(bearerToken("token"))) + .andExpect(status().isOk()) + .andExpect(content().string("mock-test-subject")); + // @formatter:on + verify(opaqueTokenAuthenticationManager).authenticate(any(Authentication.class)); + } + @Test public void getWhenCustomIntrospectionAuthenticationManagerInLambdaThenUsed() throws Exception { this.spring.register(OpaqueTokenAuthenticationManagerInLambdaConfig.class, BasicController.class).autowire(); @@ -2017,6 +2053,39 @@ public class OAuth2ResourceServerConfigurerTests { } + @EnableWebSecurity + static class DefaultAndJwtAuthenticationManagerConfig extends WebSecurityConfigurerAdapter { + + AuthenticationManager defaultAuthenticationManager = mock(AuthenticationManager.class); + + AuthenticationManager jwtAuthenticationManager = mock(AuthenticationManager.class); + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authenticationManager(this.defaultAuthenticationManager) + .authorizeRequests((authz) -> authz + .anyRequest().authenticated() + ) + .oauth2ResourceServer((oauth2) -> oauth2 + .jwt((jwt) -> jwt + .authenticationManager(this.jwtAuthenticationManager) + ) + ); + // @formatter:on + } + + AuthenticationManager defaultAuthenticationManager() { + return this.defaultAuthenticationManager; + } + + AuthenticationManager jwtAuthenticationManager() { + return this.jwtAuthenticationManager; + } + + } + @EnableWebSecurity static class CustomJwtValidatorConfig extends WebSecurityConfigurerAdapter { @@ -2230,6 +2299,39 @@ public class OAuth2ResourceServerConfigurerTests { } + @EnableWebSecurity + static class DefaultAndOpaqueTokenAuthenticationManagerConfig extends WebSecurityConfigurerAdapter { + + AuthenticationManager defaultAuthenticationManager = mock(AuthenticationManager.class); + + AuthenticationManager opaqueTokenAuthenticationManager = mock(AuthenticationManager.class); + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authenticationManager(this.defaultAuthenticationManager) + .authorizeRequests((authz) -> authz + .anyRequest().authenticated() + ) + .oauth2ResourceServer((oauth2) -> oauth2 + .opaqueToken((opaque) -> opaque + .authenticationManager(this.opaqueTokenAuthenticationManager) + ) + ); + // @formatter:on + } + + AuthenticationManager defaultAuthenticationManager() { + return this.defaultAuthenticationManager; + } + + AuthenticationManager opaqueTokenAuthenticationManager() { + return this.opaqueTokenAuthenticationManager; + } + + } + @EnableWebSecurity static class OpaqueAndJwtConfig extends WebSecurityConfigurerAdapter { diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurerTests.java index a04c2bbb22..c191b5dff7 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurerTests.java @@ -163,6 +163,12 @@ public class Saml2LoginConfigurerTests { performSaml2Login("ROLE_AUTH_MANAGER"); } + @Test + public void saml2LoginWhenDefaultAndSamlAuthenticationManagerThenSamlManagerIsUsed() throws Exception { + this.spring.register(Saml2LoginConfigWithDefaultAndCustomAuthenticationManager.class).autowire(); + performSaml2Login("ROLE_AUTH_MANAGER"); + } + @Test public void saml2LoginWhenConfiguringAuthenticationDefaultsUsingCustomizerThenTheProviderIsConfigured() throws Exception { @@ -290,6 +296,24 @@ public class Saml2LoginConfigurerTests { } + @EnableWebSecurity + @Import(Saml2LoginConfigBeans.class) + static class Saml2LoginConfigWithDefaultAndCustomAuthenticationManager extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authenticationManager(getAuthenticationManagerMock("DEFAULT_AUTH_MANAGER")) + .saml2Login((saml) -> saml + .authenticationManager(getAuthenticationManagerMock("ROLE_AUTH_MANAGER")) + ); + super.configure(http); + // @formatter:on + } + + } + @EnableWebSecurity @Import(Saml2LoginConfigBeans.class) static class Saml2LoginConfigWithAuthenticationDefaultsWithPostProcessor extends WebSecurityConfigurerAdapter { diff --git a/config/src/test/kotlin/org/springframework/security/config/web/servlet/HttpSecurityDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/web/servlet/HttpSecurityDslTests.kt index ec3690ac18..46a8e79551 100644 --- a/config/src/test/kotlin/org/springframework/security/config/web/servlet/HttpSecurityDslTests.kt +++ b/config/src/test/kotlin/org/springframework/security/config/web/servlet/HttpSecurityDslTests.kt @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,9 @@ package org.springframework.security.config.web.servlet +import io.mockk.every +import io.mockk.mockkObject +import io.mockk.verify import org.assertj.core.api.Assertions.assertThat import org.junit.Rule import org.junit.Test @@ -23,6 +26,10 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.http.HttpHeaders +import org.springframework.security.authentication.AuthenticationManager +import org.springframework.security.authentication.ProviderManager +import org.springframework.security.authentication.TestingAuthenticationProvider +import org.springframework.security.authentication.TestingAuthenticationToken import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter @@ -30,6 +37,7 @@ import org.springframework.security.config.test.SpringTestRule import org.springframework.security.core.userdetails.User import org.springframework.security.core.userdetails.UserDetailsService import org.springframework.security.provisioning.InMemoryUserDetailsManager +import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic import org.springframework.security.web.FilterChainProxy import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter @@ -41,6 +49,7 @@ import org.springframework.security.web.util.matcher.RegexRequestMatcher import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.get import org.springframework.test.web.servlet.post +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders import org.springframework.web.servlet.config.annotation.EnableWebMvc import javax.servlet.Filter @@ -217,6 +226,36 @@ class HttpSecurityDslTests { } } + @Test + fun `authentication manager when configured in DSL then used`() { + this.spring.register(AuthenticationManagerConfig::class.java).autowire() + mockkObject(AuthenticationManagerConfig.AUTHENTICATION_MANAGER) + every { + AuthenticationManagerConfig.AUTHENTICATION_MANAGER.authenticate(any()) + } returns TestingAuthenticationToken("user", "test", "ROLE_USER") + val request = MockMvcRequestBuilders.get("/") + .with(httpBasic("user", "password")) + this.mockMvc.perform(request) + verify(exactly = 1) { AuthenticationManagerConfig.AUTHENTICATION_MANAGER.authenticate(any()) } + } + + @EnableWebSecurity + open class AuthenticationManagerConfig : WebSecurityConfigurerAdapter() { + companion object { + val AUTHENTICATION_MANAGER: AuthenticationManager = ProviderManager(TestingAuthenticationProvider()) + } + + override fun configure(http: HttpSecurity) { + http { + authenticationManager = AUTHENTICATION_MANAGER + authorizeRequests { + authorize(anyRequest, authenticated) + } + httpBasic { } + } + } + } + @Test fun `HTTP security when custom filter configured then custom filter added to filter chain`() { this.spring.register(CustomFilterConfig::class.java).autowire()