Favor RestOperations in Resource Server Configurer

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

Fixes: gh-6104
This commit is contained in:
Josh Cummings 2019-01-29 15:43:09 -07:00
parent c4b17475d9
commit 5c2ee09bc3
No known key found for this signature in database
GPG Key ID: 49EF60DD7FF83443

View File

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