Bläddra i källkod

Favor RestOperations in Resource Server Configurer

Also polished exposure of the JWK Set Uri for the tests where
MockWebServer is preferred.

Fixes: gh-6104
Josh Cummings 6 år sedan
förälder
incheckning
5c2ee09bc3

+ 178 - 112
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<String, String> 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<Jwt> 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<Jwt> 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<SecurityContext> 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 {