diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java index 4939247b6c..2d4ffb0cbd 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java +++ b/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 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 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 jwtAuthenticationConverter = @@ -258,6 +241,12 @@ public final class OAuth2ResourceServerConfigurer 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 0 ) { 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 c36712b9b1..dcedddf93f 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 @@ -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 bean(Class beanClass) { + return (T) this.spring.getContext().getBean(beanClass); + } + + private T verifyBean(Class beanClass) { + return (T) verify(this.spring.getContext().getBean(beanClass)); + } + private String json(String name) throws IOException { return resource(name + ".json"); }