mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-03-03 11:59:08 +00:00
Resource Server AuthenticationManager
Making the authentication manager for jwt() and opaqueToken() configurable. Fixes: gh-6832 Fixes: gh-6849
This commit is contained in:
parent
7200fa2dce
commit
1ed9e3a1c6
@ -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 ) {
|
||||
|
@ -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");
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user