|
@@ -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("/")
|