Resource Server AuthenticationManager

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

Fixes: gh-6832
Fixes: gh-6849
This commit is contained in:
Josh Cummings 2019-05-07 17:04:51 -06:00
parent 7200fa2dce
commit 1ed9e3a1c6
No known key found for this signature in database
GPG Key ID: 49EF60DD7FF83443
2 changed files with 181 additions and 31 deletions

View File

@ -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 ) {

View File

@ -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");
}