Bearer Token Resolver Configuration

This introduces #bearerTokenResolver(BearerTokenResolver) to the
Resource Server DSL, allowing users to configure the resolver to allow
the access token as part of the request body or a query parameter. It
also allows the user to replace the resolver with a completely custom
one.

This also introduces the same ability by exposing a bean of type
BearerTokenResolver

Fixes: gh-5496
This commit is contained in:
Josh Cummings
2018-07-20 14:05:27 -06:00
committed by Joe Grandja
parent ba29b363fc
commit 6a45ecd4bb
3 changed files with 260 additions and 15 deletions

View File

@@ -66,6 +66,8 @@ import org.springframework.security.oauth2.jwt.JwtClaimNames;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoderJwkSupport;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
@@ -82,9 +84,11 @@ import org.springframework.web.context.support.GenericWebApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.hamcrest.CoreMatchers.containsString;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
@@ -526,6 +530,134 @@ public class OAuth2ResourceServerConfigurerTests {
assertThat(result.getRequest().getSession(false)).isNotNull();
}
// -- custom bearer token resolver
@Test
public void requestWhenBearerTokenResolverAllowsRequestBodyThenEitherHeaderOrRequestBodyIsAccepted()
throws Exception {
this.spring.register(AllowBearerTokenInRequestBodyConfig.class, JwtDecoderConfig.class,
BasicController.class).autowire();
JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class);
when(decoder.decode(anyString())).thenReturn(JWT);
this.mvc.perform(get("/authenticated")
.with(bearerToken(JWT_TOKEN)))
.andExpect(status().isOk())
.andExpect(content().string(JWT_SUBJECT));
this.mvc.perform(post("/authenticated")
.param("access_token", JWT_TOKEN))
.andExpect(status().isOk())
.andExpect(content().string(JWT_SUBJECT));
}
@Test
public void requestWhenBearerTokenResolverAllowsQueryParameterThenEitherHeaderOrQueryParameterIsAccepted()
throws Exception {
this.spring.register(AllowBearerTokenAsQueryParameterConfig.class, JwtDecoderConfig.class,
BasicController.class).autowire();
JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class);
when(decoder.decode(anyString())).thenReturn(JWT);
this.mvc.perform(get("/authenticated")
.with(bearerToken(JWT_TOKEN)))
.andExpect(status().isOk())
.andExpect(content().string(JWT_SUBJECT));
this.mvc.perform(get("/authenticated")
.param("access_token", JWT_TOKEN))
.andExpect(status().isOk())
.andExpect(content().string(JWT_SUBJECT));
}
@Test
public void requestWhenBearerTokenResolverAllowsRequestBodyAndRequestContainsTwoTokensThenInvalidRequest()
throws Exception {
this.spring.register(AllowBearerTokenInRequestBodyConfig.class, JwtDecoderConfig.class,
BasicController.class).autowire();
JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class);
when(decoder.decode(anyString())).thenReturn(JWT);
this.mvc.perform(post("/authenticated")
.param("access_token", JWT_TOKEN)
.with(bearerToken(JWT_TOKEN))
.with(csrf()))
.andExpect(status().isBadRequest())
.andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, containsString("invalid_request")));
}
@Test
public void requestWhenBearerTokenResolverAllowsQueryParameterAndRequestContainsTwoTokensThenInvalidRequest()
throws Exception {
this.spring.register(AllowBearerTokenAsQueryParameterConfig.class, JwtDecoderConfig.class,
BasicController.class).autowire();
JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class);
when(decoder.decode(anyString())).thenReturn(JWT);
this.mvc.perform(get("/authenticated")
.with(bearerToken(JWT_TOKEN))
.param("access_token", JWT_TOKEN))
.andExpect(status().isBadRequest())
.andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, containsString("invalid_request")));
}
@Test
public void getBearerTokenResolverWhenDuplicateResolverBeansAndAnotherOnTheDslThenTheDslOneIsUsed() {
BearerTokenResolver resolverBean = mock(BearerTokenResolver.class);
BearerTokenResolver resolver = mock(BearerTokenResolver.class);
GenericWebApplicationContext context = new GenericWebApplicationContext();
context.registerBean("resolverOne", BearerTokenResolver.class, () -> resolverBean);
context.registerBean("resolverTwo", BearerTokenResolver.class, () -> resolverBean);
this.spring.context(context).autowire();
OAuth2ResourceServerConfigurer oauth2 = new OAuth2ResourceServerConfigurer(context);
oauth2.bearerTokenResolver(resolver);
assertThat(oauth2.getBearerTokenResolver()).isEqualTo(resolver);
}
@Test
public void getBearerTokenResolverWhenDuplicateResolverBeansThenWiringException() {
assertThatCode(() -> this.spring.register(MultipleBearerTokenResolverBeansConfig.class).autowire())
.isInstanceOf(BeanCreationException.class)
.hasRootCauseInstanceOf(NoUniqueBeanDefinitionException.class);
}
@Test
public void getBearerTokenResolverWhenResolverBeanAndAnotherOnTheDslThenTheDslOneIsUsed() {
BearerTokenResolver resolver = mock(BearerTokenResolver.class);
BearerTokenResolver resolverBean = mock(BearerTokenResolver.class);
GenericWebApplicationContext context = new GenericWebApplicationContext();
context.registerBean(BearerTokenResolver.class, () -> resolverBean);
this.spring.context(context).autowire();
OAuth2ResourceServerConfigurer oauth2 = new OAuth2ResourceServerConfigurer(context);
oauth2.bearerTokenResolver(resolver);
assertThat(oauth2.getBearerTokenResolver()).isEqualTo(resolver);
}
@Test
public void getBearerTokenResolverWhenNoResolverSpecifiedThenTheDefaultIsUsed() {
ApplicationContext context =
this.spring.context(new GenericWebApplicationContext()).getContext();
OAuth2ResourceServerConfigurer oauth2 = new OAuth2ResourceServerConfigurer(context);
assertThat(oauth2.getBearerTokenResolver()).isInstanceOf(DefaultBearerTokenResolver.class);
}
// -- custom jwt decoder
@Test
@@ -563,8 +695,10 @@ public class OAuth2ResourceServerConfigurerTests {
@Test
public void getJwtDecoderWhenConfiguredWithDecoderAndJwkSetUriThenLastOneWins() {
ApplicationContext context = mock(ApplicationContext.class);
OAuth2ResourceServerConfigurer.JwtConfigurer jwtConfigurer =
new OAuth2ResourceServerConfigurer().new JwtConfigurer(null);
new OAuth2ResourceServerConfigurer(context).jwt();
JwtDecoder decoder = mock(JwtDecoder.class);
@@ -574,7 +708,7 @@ public class OAuth2ResourceServerConfigurerTests {
assertThat(jwtConfigurer.getJwtDecoder()).isEqualTo(decoder);
jwtConfigurer =
new OAuth2ResourceServerConfigurer().new JwtConfigurer(null);
new OAuth2ResourceServerConfigurer(context).jwt();
jwtConfigurer.decoder(decoder);
jwtConfigurer.jwkSetUri(JWK_SET_URI);
@@ -593,7 +727,7 @@ public class OAuth2ResourceServerConfigurerTests {
when(context.getBean(JwtDecoder.class)).thenReturn(decoderBean);
OAuth2ResourceServerConfigurer.JwtConfigurer jwtConfigurer =
new OAuth2ResourceServerConfigurer().new JwtConfigurer(context);
new OAuth2ResourceServerConfigurer(context).jwt();
jwtConfigurer.decoder(decoder);
assertThat(jwtConfigurer.getJwtDecoder()).isEqualTo(decoder);
@@ -607,7 +741,7 @@ public class OAuth2ResourceServerConfigurerTests {
when(context.getBean(JwtDecoder.class)).thenReturn(decoder);
OAuth2ResourceServerConfigurer.JwtConfigurer jwtConfigurer =
new OAuth2ResourceServerConfigurer().new JwtConfigurer(context);
new OAuth2ResourceServerConfigurer(context).jwt();
jwtConfigurer.jwkSetUri(JWK_SET_URI);
@@ -627,7 +761,7 @@ public class OAuth2ResourceServerConfigurerTests {
this.spring.context(context).autowire();
OAuth2ResourceServerConfigurer.JwtConfigurer jwtConfigurer =
new OAuth2ResourceServerConfigurer().new JwtConfigurer(context);
new OAuth2ResourceServerConfigurer(context).jwt();
jwtConfigurer.decoder(decoder);
assertThat(jwtConfigurer.getJwtDecoder()).isEqualTo(decoder);
@@ -644,7 +778,7 @@ public class OAuth2ResourceServerConfigurerTests {
this.spring.context(context).autowire();
OAuth2ResourceServerConfigurer.JwtConfigurer jwtConfigurer =
new OAuth2ResourceServerConfigurer().new JwtConfigurer(context);
new OAuth2ResourceServerConfigurer(context).jwt();
assertThatCode(() -> jwtConfigurer.getJwtDecoder())
.isInstanceOf(NoUniqueBeanDefinitionException.class);
@@ -833,6 +967,82 @@ public class OAuth2ResourceServerConfigurerTests {
}
}
@EnableWebSecurity
static class AllowBearerTokenInRequestBodyConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.oauth2()
.resourceServer()
.bearerTokenResolver(allowRequestBody())
.jwt();
// @formatter:on
}
private BearerTokenResolver allowRequestBody() {
DefaultBearerTokenResolver resolver = new DefaultBearerTokenResolver();
resolver.setAllowFormEncodedBodyParameter(true);
return resolver;
}
}
@EnableWebSecurity
static class AllowBearerTokenAsQueryParameterConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.oauth2()
.resourceServer()
.jwt();
// @formatter:on
}
@Bean
BearerTokenResolver allowQueryParameter() {
DefaultBearerTokenResolver resolver = new DefaultBearerTokenResolver();
resolver.setAllowUriQueryParameter(true);
return resolver;
}
}
@EnableWebSecurity
static class MultipleBearerTokenResolverBeansConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.oauth2()
.resourceServer()
.jwt();
// @formatter:on
}
@Bean
BearerTokenResolver resolverOne() {
DefaultBearerTokenResolver resolver = new DefaultBearerTokenResolver();
resolver.setAllowUriQueryParameter(true);
return resolver;
}
@Bean
BearerTokenResolver resolverTwo() {
DefaultBearerTokenResolver resolver = new DefaultBearerTokenResolver();
resolver.setAllowFormEncodedBodyParameter(true);
return resolver;
}
}
@EnableWebSecurity
static class CustomJwtDecoderOnDsl extends WebSecurityConfigurerAdapter {
JwtDecoder decoder = mock(JwtDecoder.class);
@@ -877,6 +1087,14 @@ public class OAuth2ResourceServerConfigurerTests {
}
}
@Configuration
static class JwtDecoderConfig {
@Bean
public JwtDecoder jwtDecoder() {
return mock(JwtDecoder.class);
}
}
@RestController
static class BasicController {
@GetMapping("/")