From 5c2ee09bc3a0fe0a14b0aa86d1946542c4ca2aab Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Tue, 29 Jan 2019 15:43:09 -0700 Subject: [PATCH] Favor RestOperations in Resource Server Configurer Also polished exposure of the JWK Set Uri for the tests where MockWebServer is preferred. Fixes: gh-6104 --- .../OAuth2ResourceServerConfigurerTests.java | 290 +++++++++++------- 1 file changed, 178 insertions(+), 112 deletions(-) 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 1f856fb195..2abf49d559 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 @@ -19,7 +19,6 @@ package org.springframework.security.config.annotation.web.configurers.oauth2.se import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; -import java.lang.reflect.Field; import java.security.KeyFactory; import java.security.interfaces.RSAPublicKey; import java.security.spec.X509EncodedKeySpec; @@ -34,6 +33,8 @@ import java.util.Map; import java.util.stream.Collectors; import javax.annotation.PreDestroy; +import com.nimbusds.jose.proc.SecurityContext; +import com.nimbusds.jwt.proc.JWTProcessor; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import org.hamcrest.core.AllOf; @@ -43,20 +44,25 @@ import org.hamcrest.core.StringStartsWith; import org.junit.Rule; import org.junit.Test; -import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.NoUniqueBeanDefinitionException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.context.ApplicationContext; +import org.springframework.context.EnvironmentAware; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.convert.converter.Converter; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.Environment; +import org.springframework.core.env.PropertySource; import org.springframework.core.io.ClassPathResource; -import org.springframework.data.util.ReflectionUtils; import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.http.RequestEntity; +import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.authentication.AbstractAuthenticationToken; @@ -79,6 +85,7 @@ import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtClaimNames; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.JwtException; +import org.springframework.security.oauth2.jwt.JwtProcessors; import org.springframework.security.oauth2.jwt.JwtTimestampValidator; import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; @@ -102,6 +109,7 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestOperations; import org.springframework.web.context.support.GenericWebApplicationContext; import static org.assertj.core.api.Assertions.assertThat; @@ -110,10 +118,10 @@ import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.core.StringStartsWith.startsWith; 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.verify; import static org.mockito.Mockito.when; -import static org.springframework.security.oauth2.jwt.JwtProcessors.withJwkSetUri; import static org.springframework.security.oauth2.jwt.JwtProcessors.withPublicKey; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; @@ -145,7 +153,7 @@ public class OAuth2ResourceServerConfigurerTests { MockMvc mvc; @Autowired(required = false) - MockWebServer authz; + MockWebServer web; @Rule public final SpringTestRule spring = new SpringTestRule(); @@ -154,8 +162,8 @@ public class OAuth2ResourceServerConfigurerTests { public void getWhenUsingDefaultsWithValidBearerTokenThenAcceptsRequest() throws Exception { - this.spring.register(WebServerConfig.class, DefaultConfig.class, BasicController.class).autowire(); - this.authz.enqueue(this.jwks("Default")); + this.spring.register(RestOperationsConfig.class, DefaultConfig.class, BasicController.class).autowire(); + mockRestOperations(jwks("Default")); String token = this.token("ValidNoScopes"); this.mvc.perform(get("/").with(bearerToken(token))) @@ -163,12 +171,24 @@ public class OAuth2ResourceServerConfigurerTests { .andExpect(content().string("ok")); } + @Test + public void getWhenUsingJwkSetUriThenAcceptsRequest() throws Exception { + this.spring.register(WebServerConfig.class, JwkSetUriConfig.class, BasicController.class).autowire(); + mockWebServer(jwks("Default")); + String token = this.token("ValidNoScopes"); + + this.mvc.perform(get("/").with(bearerToken(token))) + .andExpect(status().isOk()) + .andExpect(content().string("ok")); + } + + @Test public void getWhenUsingDefaultsWithExpiredBearerTokenThenInvalidToken() throws Exception { - this.spring.register(WebServerConfig.class, DefaultConfig.class, BasicController.class).autowire(); - this.authz.enqueue(this.jwks("Default")); + this.spring.register(RestOperationsConfig.class, DefaultConfig.class, BasicController.class).autowire(); + mockRestOperations(jwks("Default")); String token = this.token("Expired"); this.mvc.perform(get("/").with(bearerToken(token))) @@ -180,8 +200,8 @@ public class OAuth2ResourceServerConfigurerTests { public void getWhenUsingDefaultsWithBadJwkEndpointThenInvalidToken() throws Exception { - this.spring.register(WebServerConfig.class, DefaultConfig.class).autowire(); - this.authz.enqueue(new MockResponse().setBody("malformed")); + this.spring.register(RestOperationsConfig.class, DefaultConfig.class).autowire(); + mockRestOperations("malformed"); String token = this.token("ValidNoScopes"); this.mvc.perform(get("/").with(bearerToken(token))) @@ -193,8 +213,8 @@ public class OAuth2ResourceServerConfigurerTests { public void getWhenUsingDefaultsWithUnavailableJwkEndpointThenInvalidToken() throws Exception { - this.spring.register(WebServerConfig.class, DefaultConfig.class).autowire(); - this.authz.shutdown(); + this.spring.register(WebServerConfig.class, JwkSetUriConfig.class).autowire(); + this.web.shutdown(); String token = this.token("ValidNoScopes"); this.mvc.perform(get("/").with(bearerToken(token))) @@ -206,7 +226,7 @@ public class OAuth2ResourceServerConfigurerTests { public void getWhenUsingDefaultsWithMalformedBearerTokenThenInvalidToken() throws Exception { - this.spring.register(DefaultConfig.class).autowire(); + this.spring.register(JwkSetUriConfig.class).autowire(); this.mvc.perform(get("/").with(bearerToken("an\"invalid\"token"))) .andExpect(status().isUnauthorized()) @@ -217,8 +237,8 @@ public class OAuth2ResourceServerConfigurerTests { public void getWhenUsingDefaultsWithMalformedPayloadThenInvalidToken() throws Exception { - this.spring.register(WebServerConfig.class, DefaultConfig.class).autowire(); - this.authz.enqueue(this.jwks("Default")); + this.spring.register(RestOperationsConfig.class, DefaultConfig.class).autowire(); + mockRestOperations(jwks("Default")); String token = this.token("MalformedPayload"); this.mvc.perform(get("/").with(bearerToken(token))) @@ -230,7 +250,7 @@ public class OAuth2ResourceServerConfigurerTests { public void getWhenUsingDefaultsWithUnsignedBearerTokenThenInvalidToken() throws Exception { - this.spring.register(DefaultConfig.class).autowire(); + this.spring.register(JwkSetUriConfig.class).autowire(); String token = this.token("Unsigned"); this.mvc.perform(get("/").with(bearerToken(token))) @@ -242,8 +262,8 @@ public class OAuth2ResourceServerConfigurerTests { public void getWhenUsingDefaultsWithBearerTokenBeforeNotBeforeThenInvalidToken() throws Exception { - this.spring.register(WebServerConfig.class, DefaultConfig.class).autowire(); - this.authz.enqueue(this.jwks("Default")); + this.spring.register(RestOperationsConfig.class, DefaultConfig.class).autowire(); + this.mockRestOperations(jwks("Default")); String token = this.token("TooEarly"); this.mvc.perform(get("/").with(bearerToken(token))) @@ -255,7 +275,7 @@ public class OAuth2ResourceServerConfigurerTests { public void getWhenUsingDefaultsWithBearerTokenInTwoPlacesThenInvalidRequest() throws Exception { - this.spring.register(DefaultConfig.class).autowire(); + this.spring.register(JwkSetUriConfig.class).autowire(); this.mvc.perform(get("/") .with(bearerToken("token")) @@ -268,7 +288,7 @@ public class OAuth2ResourceServerConfigurerTests { public void getWhenUsingDefaultsWithBearerTokenInTwoParametersThenInvalidRequest() throws Exception { - this.spring.register(DefaultConfig.class).autowire(); + this.spring.register(JwkSetUriConfig.class).autowire(); MultiValueMap params = new LinkedMultiValueMap<>(); params.add("access_token", "token1"); @@ -284,7 +304,7 @@ public class OAuth2ResourceServerConfigurerTests { public void postWhenUsingDefaultsWithBearerTokenAsFormParameterThenIgnoresToken() throws Exception { - this.spring.register(DefaultConfig.class).autowire(); + this.spring.register(JwkSetUriConfig.class).autowire(); this.mvc.perform(post("/") // engage csrf .with(bearerToken("token").asParam())) @@ -308,7 +328,7 @@ public class OAuth2ResourceServerConfigurerTests { public void getWhenUsingDefaultsWithNoBearerTokenThenUnauthorized() throws Exception { - this.spring.register(DefaultConfig.class).autowire(); + this.spring.register(JwkSetUriConfig.class).autowire(); this.mvc.perform(get("/")) .andExpect(status().isUnauthorized()) @@ -319,8 +339,8 @@ public class OAuth2ResourceServerConfigurerTests { public void getWhenUsingDefaultsWithSufficientlyScopedBearerTokenThenAcceptsRequest() throws Exception { - this.spring.register(WebServerConfig.class, DefaultConfig.class, BasicController.class).autowire(); - this.authz.enqueue(this.jwks("Default")); + this.spring.register(RestOperationsConfig.class, DefaultConfig.class, BasicController.class).autowire(); + mockRestOperations(jwks("Default")); String token = this.token("ValidMessageReadScope"); this.mvc.perform(get("/requires-read-scope") @@ -333,8 +353,8 @@ public class OAuth2ResourceServerConfigurerTests { public void getWhenUsingDefaultsWithInsufficientScopeThenInsufficientScopeError() throws Exception { - this.spring.register(WebServerConfig.class, DefaultConfig.class, BasicController.class).autowire(); - this.authz.enqueue(this.jwks("Default")); + this.spring.register(RestOperationsConfig.class, DefaultConfig.class, BasicController.class).autowire(); + mockRestOperations(jwks("Default")); String token = this.token("ValidNoScopes"); this.mvc.perform(get("/requires-read-scope") @@ -347,8 +367,8 @@ public class OAuth2ResourceServerConfigurerTests { public void getWhenUsingDefaultsWithInsufficientScpThenInsufficientScopeError() throws Exception { - this.spring.register(WebServerConfig.class, DefaultConfig.class, BasicController.class).autowire(); - this.authz.enqueue(this.jwks("Default")); + this.spring.register(RestOperationsConfig.class, DefaultConfig.class, BasicController.class).autowire(); + mockRestOperations(jwks("Default")); String token = this.token("ValidMessageWriteScp"); this.mvc.perform(get("/requires-read-scope") @@ -361,8 +381,8 @@ public class OAuth2ResourceServerConfigurerTests { public void getWhenUsingDefaultsAndAuthorizationServerHasNoMatchingKeyThenInvalidToken() throws Exception { - this.spring.register(WebServerConfig.class, DefaultConfig.class).autowire(); - this.authz.enqueue(this.jwks("Empty")); + this.spring.register(RestOperationsConfig.class, DefaultConfig.class).autowire(); + mockRestOperations(jwks("Empty")); String token = this.token("ValidNoScopes"); this.mvc.perform(get("/") @@ -375,8 +395,8 @@ public class OAuth2ResourceServerConfigurerTests { public void getWhenUsingDefaultsAndAuthorizationServerHasMultipleMatchingKeysThenOk() throws Exception { - this.spring.register(WebServerConfig.class, DefaultConfig.class, BasicController.class).autowire(); - this.authz.enqueue(this.jwks("TwoKeys")); + this.spring.register(RestOperationsConfig.class, DefaultConfig.class, BasicController.class).autowire(); + mockRestOperations(jwks("TwoKeys")); String token = this.token("ValidNoScopes"); this.mvc.perform(get("/authenticated") @@ -389,8 +409,8 @@ public class OAuth2ResourceServerConfigurerTests { public void getWhenUsingDefaultsAndKeyMatchesByKidThenOk() throws Exception { - this.spring.register(WebServerConfig.class, DefaultConfig.class, BasicController.class).autowire(); - this.authz.enqueue(this.jwks("TwoKeys")); + this.spring.register(RestOperationsConfig.class, DefaultConfig.class, BasicController.class).autowire(); + mockRestOperations(jwks("TwoKeys")); String token = this.token("Kid"); this.mvc.perform(get("/authenticated") @@ -405,8 +425,8 @@ public class OAuth2ResourceServerConfigurerTests { public void getWhenUsingMethodSecurityWithValidBearerTokenThenAcceptsRequest() throws Exception { - this.spring.register(WebServerConfig.class, MethodSecurityConfig.class, BasicController.class).autowire(); - this.authz.enqueue(this.jwks("Default")); + this.spring.register(RestOperationsConfig.class, MethodSecurityConfig.class, BasicController.class).autowire(); + mockRestOperations(jwks("Default")); String token = this.token("ValidMessageReadScope"); this.mvc.perform(get("/ms-requires-read-scope") @@ -419,8 +439,8 @@ public class OAuth2ResourceServerConfigurerTests { public void getWhenUsingMethodSecurityWithValidBearerTokenHavingScpAttributeThenAcceptsRequest() throws Exception { - this.spring.register(WebServerConfig.class, MethodSecurityConfig.class, BasicController.class).autowire(); - this.authz.enqueue(this.jwks("Default")); + this.spring.register(RestOperationsConfig.class, MethodSecurityConfig.class, BasicController.class).autowire(); + mockRestOperations(jwks("Default")); String token = this.token("ValidMessageReadScp"); this.mvc.perform(get("/ms-requires-read-scope") @@ -433,8 +453,8 @@ public class OAuth2ResourceServerConfigurerTests { public void getWhenUsingMethodSecurityWithInsufficientScopeThenInsufficientScopeError() throws Exception { - this.spring.register(WebServerConfig.class, MethodSecurityConfig.class, BasicController.class).autowire(); - this.authz.enqueue(this.jwks("Default")); + this.spring.register(RestOperationsConfig.class, MethodSecurityConfig.class, BasicController.class).autowire(); + mockRestOperations(jwks("Default")); String token = this.token("ValidNoScopes"); this.mvc.perform(get("/ms-requires-read-scope") @@ -448,8 +468,8 @@ public class OAuth2ResourceServerConfigurerTests { public void getWhenUsingMethodSecurityWithInsufficientScpThenInsufficientScopeError() throws Exception { - this.spring.register(WebServerConfig.class, MethodSecurityConfig.class, BasicController.class).autowire(); - this.authz.enqueue(this.jwks("Default")); + this.spring.register(RestOperationsConfig.class, MethodSecurityConfig.class, BasicController.class).autowire(); + mockRestOperations(jwks("Default")); String token = this.token("ValidMessageWriteScp"); this.mvc.perform(get("/ms-requires-read-scope") @@ -462,8 +482,8 @@ public class OAuth2ResourceServerConfigurerTests { public void getWhenUsingMethodSecurityWithDenyAllThenInsufficientScopeError() throws Exception { - this.spring.register(WebServerConfig.class, MethodSecurityConfig.class, BasicController.class).autowire(); - this.authz.enqueue(this.jwks("Default")); + this.spring.register(RestOperationsConfig.class, MethodSecurityConfig.class, BasicController.class).autowire(); + mockRestOperations(jwks("Default")); String token = this.token("ValidMessageReadScope"); this.mvc.perform(get("/ms-deny") @@ -478,8 +498,8 @@ public class OAuth2ResourceServerConfigurerTests { public void postWhenUsingDefaultsWithValidBearerTokenAndNoCsrfTokenThenOk() throws Exception { - this.spring.register(WebServerConfig.class, DefaultConfig.class, BasicController.class).autowire(); - this.authz.enqueue(this.jwks("Default")); + this.spring.register(RestOperationsConfig.class, DefaultConfig.class, BasicController.class).autowire(); + mockRestOperations(jwks("Default")); String token = this.token("ValidNoScopes"); this.mvc.perform(post("/authenticated") @@ -492,7 +512,7 @@ public class OAuth2ResourceServerConfigurerTests { public void postWhenUsingDefaultsWithNoBearerTokenThenCsrfDenies() throws Exception { - this.spring.register(DefaultConfig.class).autowire(); + this.spring.register(JwkSetUriConfig.class).autowire(); this.mvc.perform(post("/authenticated")) .andExpect(status().isForbidden()) @@ -503,8 +523,8 @@ public class OAuth2ResourceServerConfigurerTests { public void postWhenUsingDefaultsWithExpiredBearerTokenAndNoCsrfThenInvalidToken() throws Exception { - this.spring.register(WebServerConfig.class, DefaultConfig.class).autowire(); - this.authz.enqueue(this.jwks("Default")); + this.spring.register(RestOperationsConfig.class, DefaultConfig.class).autowire(); + mockRestOperations(jwks("Default")); String token = this.token("Expired"); this.mvc.perform(post("/authenticated") @@ -519,8 +539,8 @@ public class OAuth2ResourceServerConfigurerTests { public void requestWhenDefaultConfiguredThenSessionIsNotCreated() throws Exception { - this.spring.register(WebServerConfig.class, DefaultConfig.class, BasicController.class).autowire(); - this.authz.enqueue(this.jwks("Default")); + this.spring.register(RestOperationsConfig.class, DefaultConfig.class, BasicController.class).autowire(); + mockRestOperations(jwks("Default")); String token = this.token("ValidNoScopes"); MvcResult result = this.mvc.perform(get("/") @@ -535,7 +555,7 @@ public class OAuth2ResourceServerConfigurerTests { public void requestWhenUsingDefaultsAndNoBearerTokenThenSessionIsCreated() throws Exception { - this.spring.register(DefaultConfig.class, BasicController.class).autowire(); + this.spring.register(JwkSetUriConfig.class, BasicController.class).autowire(); MvcResult result = this.mvc.perform(get("/")) .andExpect(status().isUnauthorized()) @@ -548,8 +568,8 @@ public class OAuth2ResourceServerConfigurerTests { public void requestWhenSessionManagementConfiguredThenUserConfigurationOverrides() throws Exception { - this.spring.register(WebServerConfig.class, AlwaysSessionCreationConfig.class, BasicController.class).autowire(); - this.authz.enqueue(this.jwks("Default")); + this.spring.register(RestOperationsConfig.class, AlwaysSessionCreationConfig.class, BasicController.class).autowire(); + mockRestOperations(jwks("Default")); String token = this.token("ValidNoScopes"); MvcResult result = this.mvc.perform(get("/") @@ -868,8 +888,8 @@ public class OAuth2ResourceServerConfigurerTests { public void requestWhenCustomJwtValidatorFailsThenCorrespondingErrorMessage() throws Exception { - this.spring.register(WebServerConfig.class, CustomJwtValidatorConfig.class).autowire(); - this.authz.enqueue(this.jwks("Default")); + this.spring.register(RestOperationsConfig.class, CustomJwtValidatorConfig.class).autowire(); + mockRestOperations(jwks("Default")); String token = this.token("ValidNoScopes"); OAuth2TokenValidator jwtValidator = @@ -890,8 +910,8 @@ public class OAuth2ResourceServerConfigurerTests { public void requestWhenClockSkewSetThenTimestampWindowRelaxedAccordingly() throws Exception { - this.spring.register(WebServerConfig.class, UnexpiredJwtClockSkewConfig.class, BasicController.class).autowire(); - this.authz.enqueue(this.jwks("Default")); + this.spring.register(RestOperationsConfig.class, UnexpiredJwtClockSkewConfig.class, BasicController.class).autowire(); + mockRestOperations(jwks("Default")); String token = this.token("ExpiresAt4687177990"); this.mvc.perform(get("/") @@ -903,8 +923,8 @@ public class OAuth2ResourceServerConfigurerTests { public void requestWhenClockSkewSetButJwtStillTooLateThenReportsExpired() throws Exception { - this.spring.register(WebServerConfig.class, ExpiredJwtClockSkewConfig.class, BasicController.class).autowire(); - this.authz.enqueue(this.jwks("Default")); + this.spring.register(RestOperationsConfig.class, ExpiredJwtClockSkewConfig.class, BasicController.class).autowire(); + mockRestOperations(jwks("Default")); String token = this.token("ExpiresAt4687177990"); this.mvc.perform(get("/") @@ -1067,8 +1087,8 @@ public class OAuth2ResourceServerConfigurerTests { public void getWhenAlsoUsingHttpBasicThenCorrectProviderEngages() throws Exception { - this.spring.register(WebServerConfig.class, BasicAndResourceServerConfig.class, BasicController.class).autowire(); - this.authz.enqueue(this.jwks("Default")); + this.spring.register(RestOperationsConfig.class, BasicAndResourceServerConfig.class, BasicController.class).autowire(); + mockRestOperations(jwks("Default")); String token = this.token("ValidNoScopes"); this.mvc.perform(get("/authenticated") @@ -1104,7 +1124,25 @@ public class OAuth2ResourceServerConfigurerTests { @EnableWebSecurity static class DefaultConfig extends WebSecurityConfigurerAdapter { - @Value("${mock.jwk-set-uri:https://example.org}") String uri; + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests() + .antMatchers("/requires-read-scope").access("hasAuthority('SCOPE_message:read')") + .anyRequest().authenticated() + .and() + .oauth2ResourceServer() + .jwt(); + // @formatter:on + } + } + + @EnableWebSecurity + static class JwkSetUriConfig extends WebSecurityConfigurerAdapter { + @Value("${mockwebserver.url:https://example.org}") + String jwkSetUri; @Override protected void configure(HttpSecurity http) throws Exception { @@ -1116,14 +1154,15 @@ public class OAuth2ResourceServerConfigurerTests { .and() .oauth2ResourceServer() .jwt() - .jwkSetUri(this.uri); + .jwkSetUri(this.jwkSetUri); // @formatter:on } } @EnableWebSecurity static class CsrfDisabledConfig extends WebSecurityConfigurerAdapter { - @Value("${mock.jwk-set-uri:https://example.org}") String uri; + @Value("${mockwebserver.url:https://example.org}") + String jwkSetUri; @Override protected void configure(HttpSecurity http) throws Exception { @@ -1136,7 +1175,7 @@ public class OAuth2ResourceServerConfigurerTests { .csrf().disable() .oauth2ResourceServer() .jwt() - .jwkSetUri(this.uri); + .jwkSetUri(this.jwkSetUri); // @formatter:on } } @@ -1144,8 +1183,6 @@ public class OAuth2ResourceServerConfigurerTests { @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) static class MethodSecurityConfig extends WebSecurityConfigurerAdapter { - @Value("${mock.jwk-set-uri:https://example.org}") String uri; - @Override protected void configure(HttpSecurity http) throws Exception { // @formatter:off @@ -1154,8 +1191,7 @@ public class OAuth2ResourceServerConfigurerTests { .anyRequest().authenticated() .and() .oauth2ResourceServer() - .jwt() - .jwkSetUri(this.uri); + .jwt(); // @formatter:on } } @@ -1303,8 +1339,6 @@ public class OAuth2ResourceServerConfigurerTests { @EnableWebSecurity static class BasicAndResourceServerConfig extends WebSecurityConfigurerAdapter { - @Value("${mock.jwk-set-uri:https://example.org}") String uri; - @Override protected void configure(HttpSecurity http) throws Exception { // @formatter:off @@ -1315,8 +1349,7 @@ public class OAuth2ResourceServerConfigurerTests { .httpBasic() .and() .oauth2ResourceServer() - .jwt() - .jwkSetUri(this.uri); + .jwt(); // @formatter:on } @@ -1365,8 +1398,6 @@ public class OAuth2ResourceServerConfigurerTests { @EnableWebSecurity static class AlwaysSessionCreationConfig extends WebSecurityConfigurerAdapter { - @Value("${mock.jwk-set-uri}") String uri; - @Override protected void configure(HttpSecurity http) throws Exception { // @formatter:off @@ -1375,8 +1406,7 @@ public class OAuth2ResourceServerConfigurerTests { .sessionCreationPolicy(SessionCreationPolicy.ALWAYS) .and() .oauth2ResourceServer() - .jwt() - .jwkSetUri(this.uri); + .jwt(); // @formatter:on } } @@ -1498,20 +1528,19 @@ public class OAuth2ResourceServerConfigurerTests { @EnableWebSecurity static class CustomJwtValidatorConfig extends WebSecurityConfigurerAdapter { - @Value("${mock.jwk-set-uri}") String uri; + @Autowired + NimbusJwtDecoder jwtDecoder; private final OAuth2TokenValidator jwtValidator = mock(OAuth2TokenValidator.class); @Override protected void configure(HttpSecurity http) throws Exception { - NimbusJwtDecoder jwtDecoder = new NimbusJwtDecoder(withJwkSetUri(this.uri).build()); - jwtDecoder.setJwtValidator(this.jwtValidator); + this.jwtDecoder.setJwtValidator(this.jwtValidator); // @formatter:off http .oauth2ResourceServer() - .jwt() - .decoder(jwtDecoder); + .jwt(); // @formatter:on } @@ -1522,7 +1551,8 @@ public class OAuth2ResourceServerConfigurerTests { @EnableWebSecurity static class UnexpiredJwtClockSkewConfig extends WebSecurityConfigurerAdapter { - @Value("${mock.jwk-set-uri}") String uri; + @Autowired + NimbusJwtDecoder jwtDecoder; @Override protected void configure(HttpSecurity http) throws Exception { @@ -1531,21 +1561,20 @@ public class OAuth2ResourceServerConfigurerTests { JwtTimestampValidator jwtValidator = new JwtTimestampValidator(Duration.ofHours(1)); jwtValidator.setClock(nearlyAnHourFromTokenExpiry); - NimbusJwtDecoder jwtDecoder = new NimbusJwtDecoder(withJwkSetUri(this.uri).build()); - jwtDecoder.setJwtValidator(jwtValidator); + this.jwtDecoder.setJwtValidator(jwtValidator); // @formatter:off http .oauth2ResourceServer() - .jwt() - .decoder(jwtDecoder); + .jwt(); // @formatter:on } } @EnableWebSecurity static class ExpiredJwtClockSkewConfig extends WebSecurityConfigurerAdapter { - @Value("${mock.jwk-set-uri}") String uri; + @Autowired + NimbusJwtDecoder jwtDecoder; @Override protected void configure(HttpSecurity http) throws Exception { @@ -1554,14 +1583,12 @@ public class OAuth2ResourceServerConfigurerTests { JwtTimestampValidator jwtValidator = new JwtTimestampValidator(Duration.ofHours(1)); jwtValidator.setClock(justOverOneHourAfterExpiry); - NimbusJwtDecoder jwtDecoder = new NimbusJwtDecoder(withJwkSetUri(this.uri).build()); - jwtDecoder.setJwtValidator(jwtValidator); + this.jwtDecoder.setJwtValidator(jwtValidator); // @formatter:off http .oauth2ResourceServer() - .jwt() - .decoder(jwtDecoder); + .jwt(); } } @@ -1643,7 +1670,7 @@ public class OAuth2ResourceServerConfigurerTests { } @Configuration - static class WebServerConfig implements BeanPostProcessor { + static class WebServerConfig implements BeanPostProcessor, EnvironmentAware { private final MockWebServer server = new MockWebServer(); @PreDestroy @@ -1651,21 +1678,51 @@ public class OAuth2ResourceServerConfigurerTests { this.server.shutdown(); } + @Override + public void setEnvironment(Environment environment) { + if (environment instanceof ConfigurableEnvironment) { + ((ConfigurableEnvironment) environment) + .getPropertySources().addFirst(new MockWebServerPropertySource()); + } + } + @Bean - public MockWebServer authz() { + public MockWebServer web() { return this.server; } - @Override - public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { - if (bean instanceof WebSecurityConfigurerAdapter) { - Field f = ReflectionUtils.findField(bean.getClass(), field -> - field.getAnnotation(Value.class) != null); - if (f != null) { - ReflectionUtils.setField(f, bean, this.server.url("/.well-known/jwks.json").toString()); + private class MockWebServerPropertySource extends PropertySource { + + public MockWebServerPropertySource() { + super("mockwebserver"); + } + + @Override + public Object getProperty(String name) { + if ("mockwebserver.url".equals(name)) { + return WebServerConfig.this.server.url("/.well-known/jwks.json").toString(); + } else { + return null; } } - return null; + } + } + + @Configuration + static class RestOperationsConfig { + RestOperations rest = mock(RestOperations.class); + + @Bean + RestOperations rest() { + return this.rest; + } + + @Bean + NimbusJwtDecoder jwtDecoder() { + JWTProcessor jwtProcessor = + JwtProcessors.withJwkSetUri("https://example.org/.well-known/jwks.json") + .restOperations(this.rest).build(); + return new NimbusJwtDecoder(jwtProcessor); } } @@ -1733,16 +1790,25 @@ public class OAuth2ResourceServerConfigurerTests { (StringUtils.hasText(scope) ? ", scope=\"" + scope + "\"" : "")); } - private String token(String name) throws IOException { - return resource(name + ".token"); - } - - private MockResponse jwks(String name) throws IOException { - String response = resource(name + ".jwks"); - return new MockResponse() + private void mockWebServer(String response) { + this.web.enqueue(new MockResponse() .setResponseCode(200) .setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) - .setBody(response); + .setBody(response)); + } + + private void mockRestOperations(String response) { + RestOperations rest = this.spring.getContext().getBean(RestOperations.class); + when(rest.exchange(any(RequestEntity.class), eq(String.class))) + .thenReturn(new ResponseEntity<>(response, HttpStatus.OK)); + } + + private String jwks(String name) throws IOException { + return resource(name + ".jwks"); + } + + private String token(String name) throws IOException { + return resource(name + ".token"); } private String resource(String suffix) throws IOException {