浏览代码

Add AuthenticationManager to HttpSecurity

Closes gh-10040
Eleftheria Stein 4 年之前
父节点
当前提交
585788ad0a

+ 20 - 1
config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java

@@ -143,6 +143,8 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
 
 	private FilterOrderRegistration filterOrders = new FilterOrderRegistration();
 
+	private AuthenticationManager authenticationManager;
+
 	/**
 	 * Creates a new instance
 	 * @param objectPostProcessor the {@link ObjectPostProcessor} that should be used
@@ -2722,6 +2724,18 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
 		return HttpSecurity.this;
 	}
 
+	/**
+	 * Configure the default {@link AuthenticationManager}.
+	 * @param authenticationManager the {@link AuthenticationManager} to use
+	 * @return the {@link HttpSecurity} for further customizations
+	 * @since 5.6
+	 */
+	public HttpSecurity authenticationManager(AuthenticationManager authenticationManager) {
+		Assert.notNull(authenticationManager, "authenticationManager cannot be null");
+		this.authenticationManager = authenticationManager;
+		return HttpSecurity.this;
+	}
+
 	@Override
 	public <C> void setSharedObject(Class<C> sharedType, C object) {
 		super.setSharedObject(sharedType, object);
@@ -2729,7 +2743,12 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
 
 	@Override
 	protected void beforeConfigure() throws Exception {
-		setSharedObject(AuthenticationManager.class, getAuthenticationRegistry().build());
+		if (this.authenticationManager != null) {
+			setSharedObject(AuthenticationManager.class, this.authenticationManager);
+		}
+		else {
+			setSharedObject(AuthenticationManager.class, getAuthenticationRegistry().build());
+		}
 	}
 
 	@Override

+ 6 - 1
config/src/main/kotlin/org/springframework/security/config/web/servlet/HttpSecurityDsl.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.
@@ -17,6 +17,7 @@
 package org.springframework.security.config.web.servlet
 
 import org.springframework.context.ApplicationContext
+import org.springframework.security.authentication.AuthenticationManager
 import org.springframework.security.config.annotation.web.builders.HttpSecurity
 import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository
 import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository
@@ -63,11 +64,14 @@ operator fun HttpSecurity.invoke(httpConfiguration: HttpSecurityDsl.() -> 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) }
     }
 }

+ 119 - 0
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);
+		}
+
+	}
+
+}

+ 102 - 0
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 {
 

+ 24 - 0
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 {

+ 40 - 1
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()