Ver código fonte

Resource Server AuthenticationManager

Making the authentication manager for jwt() and opaqueToken()
configurable.

Fixes: gh-6832
Fixes: gh-6849
Josh Cummings 6 anos atrás
pai
commit
1ed9e3a1c6

+ 69 - 29
config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java

@@ -24,6 +24,7 @@ import org.springframework.core.convert.converter.Converter;
 import org.springframework.security.authentication.AbstractAuthenticationToken;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.AuthenticationManagerResolver;
+import org.springframework.security.authentication.AuthenticationProvider;
 import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
 import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
 import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer;
@@ -199,18 +200,6 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
 		BearerTokenResolver bearerTokenResolver = getBearerTokenResolver();
 		this.requestMatcher.setBearerTokenResolver(bearerTokenResolver);
 
-		AuthenticationManagerResolver resolver = this.authenticationManagerResolver;
-		if (resolver == null) {
-			resolver = request -> http.getSharedObject(AuthenticationManager.class);
-		}
-
-		BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(resolver);
-		filter.setBearerTokenResolver(bearerTokenResolver);
-		filter.setAuthenticationEntryPoint(this.authenticationEntryPoint);
-		filter = postProcess(filter);
-
-		http.addFilter(filter);
-
 		if (this.jwtConfigurer != null && this.opaqueTokenConfigurer != null) {
 			throw new IllegalStateException("Spring Security only supports JWTs or Opaque Tokens, not both at the " +
 					"same time");
@@ -225,30 +214,24 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
 					"http.oauth2ResourceServer().opaque().");
 		}
 
-		if (this.jwtConfigurer != null) {
-			JwtDecoder decoder = this.jwtConfigurer.getJwtDecoder();
-			Converter<Jwt, ? extends AbstractAuthenticationToken> jwtAuthenticationConverter =
-					this.jwtConfigurer.getJwtAuthenticationConverter();
-
-			JwtAuthenticationProvider provider =
-					new JwtAuthenticationProvider(decoder);
-			provider.setJwtAuthenticationConverter(jwtAuthenticationConverter);
-			provider = postProcess(provider);
-
-			http.authenticationProvider(provider);
+		AuthenticationManagerResolver resolver = this.authenticationManagerResolver;
+		if (resolver == null) {
+			AuthenticationManager authenticationManager = getAuthenticationManager(http);
+			resolver = request -> authenticationManager;
 		}
 
-		if (this.opaqueTokenConfigurer != null) {
-			OAuth2TokenIntrospectionClient introspectionClient = this.opaqueTokenConfigurer.getIntrospectionClient();
-			OAuth2IntrospectionAuthenticationProvider provider =
-					new OAuth2IntrospectionAuthenticationProvider(introspectionClient);
-			http.authenticationProvider(provider);
-		}
+		BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(resolver);
+		filter.setBearerTokenResolver(bearerTokenResolver);
+		filter.setAuthenticationEntryPoint(this.authenticationEntryPoint);
+		filter = postProcess(filter);
+
+		http.addFilter(filter);
 	}
 
 	public class JwtConfigurer {
 		private final ApplicationContext context;
 
+		private AuthenticationManager authenticationManager;
 		private JwtDecoder decoder;
 
 		private Converter<Jwt, ? extends AbstractAuthenticationToken> jwtAuthenticationConverter =
@@ -258,6 +241,12 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
 			this.context = context;
 		}
 
+		public JwtConfigurer authenticationManager(AuthenticationManager authenticationManager) {
+			Assert.notNull(authenticationManager, "authenticationManager cannot be null");
+			this.authenticationManager = authenticationManager;
+			return this;
+		}
+
 		public JwtConfigurer decoder(JwtDecoder decoder) {
 			this.decoder = decoder;
 			return this;
@@ -290,11 +279,31 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
 
 			return this.decoder;
 		}
+
+		AuthenticationManager getAuthenticationManager(H http) {
+			if (this.authenticationManager != null) {
+				return this.authenticationManager;
+			}
+
+			JwtDecoder decoder = getJwtDecoder();
+			Converter<Jwt, ? extends AbstractAuthenticationToken> jwtAuthenticationConverter =
+					getJwtAuthenticationConverter();
+
+			JwtAuthenticationProvider provider =
+					new JwtAuthenticationProvider(decoder);
+			provider.setJwtAuthenticationConverter(jwtAuthenticationConverter);
+			AuthenticationProvider authenticationProvider = postProcess(provider);
+
+			http.authenticationProvider(authenticationProvider);
+
+			return http.getSharedObject(AuthenticationManager.class);
+		}
 	}
 
 	public class OpaqueTokenConfigurer {
 		private final ApplicationContext context;
 
+		private AuthenticationManager authenticationManager;
 		private String introspectionUri;
 		private String clientId;
 		private String clientSecret;
@@ -304,6 +313,12 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
 			this.context = context;
 		}
 
+		public OpaqueTokenConfigurer authenticationManager(AuthenticationManager authenticationManager) {
+			Assert.notNull(authenticationManager, "authenticationManager cannot be null");
+			this.authenticationManager = authenticationManager;
+			return this;
+		}
+
 		public OpaqueTokenConfigurer introspectionUri(String introspectionUri) {
 			Assert.notNull(introspectionUri, "introspectionUri cannot be null");
 			this.introspectionUri = introspectionUri;
@@ -334,6 +349,19 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
 			}
 			return this.context.getBean(OAuth2TokenIntrospectionClient.class);
 		}
+
+		AuthenticationManager getAuthenticationManager(H http) {
+			if (this.authenticationManager != null) {
+				return this.authenticationManager;
+			}
+
+			OAuth2TokenIntrospectionClient introspectionClient = getIntrospectionClient();
+			OAuth2IntrospectionAuthenticationProvider provider =
+					new OAuth2IntrospectionAuthenticationProvider(introspectionClient);
+			http.authenticationProvider(provider);
+
+			return http.getSharedObject(AuthenticationManager.class);
+		}
 	}
 
 	private void registerDefaultAccessDeniedHandler(H http) {
@@ -370,6 +398,18 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
 		csrf.ignoringRequestMatchers(this.requestMatcher);
 	}
 
+	AuthenticationManager getAuthenticationManager(H http) {
+		if (this.jwtConfigurer != null) {
+			return this.jwtConfigurer.getAuthenticationManager(http);
+		}
+
+		if (this.opaqueTokenConfigurer != null) {
+			return this.opaqueTokenConfigurer.getAuthenticationManager(http);
+		}
+
+		return http.getSharedObject(AuthenticationManager.class);
+	}
+
 	BearerTokenResolver getBearerTokenResolver() {
 		if ( this.bearerTokenResolver == null ) {
 			if ( this.context.getBeanNamesForType(BearerTokenResolver.class).length > 0 ) {

+ 112 - 2
config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java

@@ -65,7 +65,10 @@ import org.springframework.http.ResponseEntity;
 import org.springframework.mock.web.MockHttpServletRequest;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.security.authentication.AbstractAuthenticationToken;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.AuthenticationProvider;
 import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
 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;
@@ -79,8 +82,6 @@ import org.springframework.security.core.userdetails.UserDetailsService;
 import org.springframework.security.oauth2.core.OAuth2Error;
 import org.springframework.security.oauth2.core.OAuth2TokenValidator;
 import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
-import org.springframework.security.oauth2.server.resource.introspection.NimbusOAuth2TokenIntrospectionClient;
-import org.springframework.security.oauth2.server.resource.introspection.OAuth2TokenIntrospectionClient;
 import org.springframework.security.oauth2.jose.jws.JwsAlgorithms;
 import org.springframework.security.oauth2.jwt.Jwt;
 import org.springframework.security.oauth2.jwt.JwtClaimNames;
@@ -90,6 +91,9 @@ import org.springframework.security.oauth2.jwt.JwtTimestampValidator;
 import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
 import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
 import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
+import org.springframework.security.oauth2.server.resource.authentication.OAuth2IntrospectionAuthenticationToken;
+import org.springframework.security.oauth2.server.resource.introspection.NimbusOAuth2TokenIntrospectionClient;
+import org.springframework.security.oauth2.server.resource.introspection.OAuth2TokenIntrospectionClient;
 import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
 import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
 import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver;
@@ -120,8 +124,10 @@ import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
+import static org.springframework.security.oauth2.core.TestOAuth2AccessTokens.noScopes;
 import static org.springframework.security.oauth2.jwt.NimbusJwtDecoder.withJwkSetUri;
 import static org.springframework.security.oauth2.jwt.NimbusJwtDecoder.withPublicKey;
 import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
@@ -153,6 +159,8 @@ public class OAuth2ResourceServerConfigurerTests {
 	private static final String INTROSPECTION_URI = "https://idp.example.com";
 	private static final String CLIENT_ID = "client-id";
 	private static final String CLIENT_SECRET = "client-secret";
+	private static final OAuth2IntrospectionAuthenticationToken INTROSPECTION_AUTHENTICATION_TOKEN =
+			new OAuth2IntrospectionAuthenticationToken(noScopes(), JWT_CLAIMS, Collections.emptyList());
 
 	@Autowired(required = false)
 	MockMvc mvc;
@@ -1015,6 +1023,20 @@ public class OAuth2ResourceServerConfigurerTests {
 				.andExpect(invalidTokenHeader("algorithm"));
 	}
 
+	@Test
+	public void getWhenCustomJwtAuthenticationManagerThenUsed() throws Exception {
+		this.spring.register(JwtAuthenticationManagerConfig.class, BasicController.class).autowire();
+
+		when(bean(AuthenticationProvider.class).authenticate(any(Authentication.class)))
+				.thenReturn(JWT_AUTHENTICATION_TOKEN);
+		this.mvc.perform(get("/authenticated")
+				.with(bearerToken("token")))
+				.andExpect(status().isOk())
+				.andExpect(content().string("mock-test-subject"));
+
+		verifyBean(AuthenticationProvider.class).authenticate(any(Authentication.class));
+	}
+
 	// -- opaque
 
 
@@ -1052,6 +1074,20 @@ public class OAuth2ResourceServerConfigurerTests {
 				.andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, containsString("scope")));
 	}
 
+	@Test
+	public void getWhenCustomIntrospectionAuthenticationManagerThenUsed() throws Exception {
+		this.spring.register(OpaqueTokenAuthenticationManagerConfig.class, BasicController.class).autowire();
+
+		when(bean(AuthenticationProvider.class).authenticate(any(Authentication.class)))
+				.thenReturn(INTROSPECTION_AUTHENTICATION_TOKEN);
+		this.mvc.perform(get("/authenticated")
+				.with(bearerToken("token")))
+				.andExpect(status().isOk())
+				.andExpect(content().string("mock-test-subject"));
+
+		verifyBean(AuthenticationProvider.class).authenticate(any(Authentication.class));
+	}
+
 	@Test
 	public void configureWhenOnlyIntrospectionUrlThenException() throws Exception {
 		assertThatCode(() -> this.spring.register(OpaqueTokenHalfConfiguredConfig.class).autowire())
@@ -1191,6 +1227,30 @@ public class OAuth2ResourceServerConfigurerTests {
 				.andExpect(content().string("basic-user"));
 	}
 
+	// -- authentication manager
+
+	@Test
+	public void getAuthenticationManagerWhenConfiguredAuthenticationManagerThenTakesPrecedence() {
+		ApplicationContext context = mock(ApplicationContext.class);
+		HttpSecurityBuilder http = mock(HttpSecurityBuilder.class);
+
+		OAuth2ResourceServerConfigurer oauth2ResourceServer = new OAuth2ResourceServerConfigurer(context);
+		AuthenticationManager authenticationManager = mock(AuthenticationManager.class);
+		oauth2ResourceServer
+			.jwt()
+				.authenticationManager(authenticationManager)
+				.decoder(mock(JwtDecoder.class));
+		assertThat(oauth2ResourceServer.getAuthenticationManager(http)).isSameAs(authenticationManager);
+
+		oauth2ResourceServer = new OAuth2ResourceServerConfigurer(context);
+		oauth2ResourceServer
+			.opaqueToken()
+				.authenticationManager(authenticationManager)
+				.introspectionClient(mock(OAuth2TokenIntrospectionClient.class));
+		assertThat(oauth2ResourceServer.getAuthenticationManager(http)).isSameAs(authenticationManager);
+		verify(http, never()).authenticationProvider(any(AuthenticationProvider.class));
+	}
+
 	// -- Incorrect Configuration
 
 	@Test
@@ -1622,6 +1682,27 @@ public class OAuth2ResourceServerConfigurerTests {
 		}
 	}
 
+	@EnableWebSecurity
+	static class JwtAuthenticationManagerConfig extends WebSecurityConfigurerAdapter {
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.authorizeRequests()
+					.anyRequest().authenticated()
+					.and()
+				.oauth2ResourceServer()
+					.jwt()
+						.authenticationManager(authenticationProvider()::authenticate);
+			// @formatter:on
+		}
+
+		@Bean
+		public AuthenticationProvider authenticationProvider() {
+			return mock(AuthenticationProvider.class);
+		}
+	}
+
 	@EnableWebSecurity
 	static class CustomJwtValidatorConfig extends WebSecurityConfigurerAdapter {
 		@Autowired
@@ -1735,6 +1816,27 @@ public class OAuth2ResourceServerConfigurerTests {
 		}
 	}
 
+	@EnableWebSecurity
+	static class OpaqueTokenAuthenticationManagerConfig extends WebSecurityConfigurerAdapter {
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.authorizeRequests()
+					.anyRequest().authenticated()
+					.and()
+				.oauth2ResourceServer()
+					.opaqueToken()
+						.authenticationManager(authenticationProvider()::authenticate);
+			// @formatter:on
+		}
+
+		@Bean
+		public AuthenticationProvider authenticationProvider() {
+			return mock(AuthenticationProvider.class);
+		}
+	}
+
 	@EnableWebSecurity
 	static class OpaqueAndJwtConfig extends WebSecurityConfigurerAdapter {
 		@Override
@@ -1954,6 +2056,14 @@ public class OAuth2ResourceServerConfigurerTests {
 				.thenReturn(entity);
 	}
 
+	private <T> T bean(Class<T> beanClass) {
+		return (T) this.spring.getContext().getBean(beanClass);
+	}
+
+	private <T> T verifyBean(Class<T> beanClass) {
+		return (T) verify(this.spring.getContext().getBean(beanClass));
+	}
+
 	private String json(String name) throws IOException {
 		return resource(name + ".json");
 	}