|
|
@ -15,11 +15,19 @@
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
package org.springframework.boot.autoconfigure.security.oauth2.resource.servlet;
|
|
|
|
package org.springframework.boot.autoconfigure.security.oauth2.resource.servlet;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import java.util.Collections;
|
|
|
|
|
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.List;
|
|
|
|
|
|
|
|
import java.util.Map;
|
|
|
|
|
|
|
|
|
|
|
|
import javax.servlet.Filter;
|
|
|
|
import javax.servlet.Filter;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import okhttp3.mockwebserver.MockResponse;
|
|
|
|
|
|
|
|
import okhttp3.mockwebserver.MockWebServer;
|
|
|
|
|
|
|
|
import org.junit.After;
|
|
|
|
import org.junit.Test;
|
|
|
|
import org.junit.Test;
|
|
|
|
|
|
|
|
import org.testcontainers.shaded.com.fasterxml.jackson.core.JsonProcessingException;
|
|
|
|
|
|
|
|
import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
|
|
|
|
|
|
|
|
|
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
|
|
|
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
|
|
|
import org.springframework.boot.test.context.FilteredClassLoader;
|
|
|
|
import org.springframework.boot.test.context.FilteredClassLoader;
|
|
|
@ -27,6 +35,9 @@ import org.springframework.boot.test.context.assertj.AssertableWebApplicationCon
|
|
|
|
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
|
|
|
|
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
|
|
|
|
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.http.HttpHeaders;
|
|
|
|
|
|
|
|
import org.springframework.http.HttpStatus;
|
|
|
|
|
|
|
|
import org.springframework.http.MediaType;
|
|
|
|
import org.springframework.security.config.BeanIds;
|
|
|
|
import org.springframework.security.config.BeanIds;
|
|
|
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
|
|
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
|
|
|
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
|
|
|
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
|
|
@ -44,6 +55,7 @@ import static org.mockito.Mockito.mock;
|
|
|
|
* Tests for {@link OAuth2ResourceServerAutoConfiguration}.
|
|
|
|
* Tests for {@link OAuth2ResourceServerAutoConfiguration}.
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* @author Madhura Bhave
|
|
|
|
* @author Madhura Bhave
|
|
|
|
|
|
|
|
* @author Artsiom Yudovin
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
public class OAuth2ResourceServerAutoConfigurationTests {
|
|
|
|
public class OAuth2ResourceServerAutoConfigurationTests {
|
|
|
|
|
|
|
|
|
|
|
@ -52,6 +64,15 @@ public class OAuth2ResourceServerAutoConfigurationTests {
|
|
|
|
AutoConfigurations.of(OAuth2ResourceServerAutoConfiguration.class))
|
|
|
|
AutoConfigurations.of(OAuth2ResourceServerAutoConfiguration.class))
|
|
|
|
.withUserConfiguration(TestConfig.class);
|
|
|
|
.withUserConfiguration(TestConfig.class);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private MockWebServer server;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@After
|
|
|
|
|
|
|
|
public void cleanup() throws Exception {
|
|
|
|
|
|
|
|
if (this.server != null) {
|
|
|
|
|
|
|
|
this.server.shutdown();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
@Test
|
|
|
|
public void autoConfigurationShouldConfigureResourceServer() {
|
|
|
|
public void autoConfigurationShouldConfigureResourceServer() {
|
|
|
|
this.contextRunner.withPropertyValues(
|
|
|
|
this.contextRunner.withPropertyValues(
|
|
|
@ -63,6 +84,46 @@ public class OAuth2ResourceServerAutoConfigurationTests {
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
|
|
|
public void autoConfigurationShouldConfigureResourceServerOidcIssuerLocation()
|
|
|
|
|
|
|
|
throws Exception {
|
|
|
|
|
|
|
|
this.server = new MockWebServer();
|
|
|
|
|
|
|
|
this.server.start();
|
|
|
|
|
|
|
|
String issuer = this.server.url("").toString();
|
|
|
|
|
|
|
|
String cleanIssuerPath = cleanIssuerPath(issuer);
|
|
|
|
|
|
|
|
setupMockResponse(cleanIssuerPath);
|
|
|
|
|
|
|
|
this.contextRunner.withPropertyValues(
|
|
|
|
|
|
|
|
"spring.security.oauth2.resourceserver.jwt.oidc-issuer-location=http://"
|
|
|
|
|
|
|
|
+ this.server.getHostName() + ":" + this.server.getPort())
|
|
|
|
|
|
|
|
.run((context) -> {
|
|
|
|
|
|
|
|
assertThat(context.getBean(JwtDecoder.class))
|
|
|
|
|
|
|
|
.isInstanceOf(NimbusJwtDecoderJwkSupport.class);
|
|
|
|
|
|
|
|
assertThat(getBearerTokenFilter(context)).isNotNull();
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
|
|
|
public void autoConfigurationShouldConfigureSetUriWithTwoProperties()
|
|
|
|
|
|
|
|
throws Exception {
|
|
|
|
|
|
|
|
this.server = new MockWebServer();
|
|
|
|
|
|
|
|
this.server.start();
|
|
|
|
|
|
|
|
String issuer = this.server.url("").toString();
|
|
|
|
|
|
|
|
String cleanIssuerPath = cleanIssuerPath(issuer);
|
|
|
|
|
|
|
|
setupMockResponse(cleanIssuerPath);
|
|
|
|
|
|
|
|
this.contextRunner.withPropertyValues(
|
|
|
|
|
|
|
|
"spring.security.oauth2.resourceserver.jwt.oidc-issuer-location=http://"
|
|
|
|
|
|
|
|
+ this.server.getHostName() + ":" + this.server.getPort(),
|
|
|
|
|
|
|
|
"spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://jwk-set-uri.com")
|
|
|
|
|
|
|
|
.run((context) -> {
|
|
|
|
|
|
|
|
assertThat(context.getBean(JwtDecoder.class))
|
|
|
|
|
|
|
|
.isInstanceOf(NimbusJwtDecoderJwkSupport.class);
|
|
|
|
|
|
|
|
assertThat(getBearerTokenFilter(context)).isNotNull();
|
|
|
|
|
|
|
|
assertThat(context.containsBean("jwtDecoder")).isTrue();
|
|
|
|
|
|
|
|
assertThat(context.containsBean("jwtDecoderByOidcIssuerLocation"))
|
|
|
|
|
|
|
|
.isFalse();
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
@Test
|
|
|
|
public void autoConfigurationWhenJwkSetUriNullShouldNotFail() {
|
|
|
|
public void autoConfigurationWhenJwkSetUriNullShouldNotFail() {
|
|
|
|
this.contextRunner
|
|
|
|
this.contextRunner
|
|
|
@ -77,6 +138,14 @@ public class OAuth2ResourceServerAutoConfigurationTests {
|
|
|
|
.run((context) -> assertThat(getBearerTokenFilter(context)).isNotNull());
|
|
|
|
.run((context) -> assertThat(getBearerTokenFilter(context)).isNotNull());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
|
|
|
public void jwtDecoderBeanIsConditionalOnMissingBeanOidcIssuerLocation() {
|
|
|
|
|
|
|
|
this.contextRunner.withPropertyValues(
|
|
|
|
|
|
|
|
"spring.security.oauth2.resourceserver.jwt.oidc-issuer-location=http://jwk-oidc-issuer-location.com")
|
|
|
|
|
|
|
|
.withUserConfiguration(JwtDecoderConfig.class)
|
|
|
|
|
|
|
|
.run((context) -> assertThat(getBearerTokenFilter(context)).isNotNull());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
@Test
|
|
|
|
public void autoConfigurationShouldBeConditionalOnJwtAuthenticationTokenClass() {
|
|
|
|
public void autoConfigurationShouldBeConditionalOnJwtAuthenticationTokenClass() {
|
|
|
|
this.contextRunner.withPropertyValues(
|
|
|
|
this.contextRunner.withPropertyValues(
|
|
|
@ -86,6 +155,15 @@ public class OAuth2ResourceServerAutoConfigurationTests {
|
|
|
|
.run((context) -> assertThat(getBearerTokenFilter(context)).isNull());
|
|
|
|
.run((context) -> assertThat(getBearerTokenFilter(context)).isNull());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
|
|
|
public void autoConfigurationShouldBeConditionalOnJwtAuthenticationTokenClassOidcIssuerLocation() {
|
|
|
|
|
|
|
|
this.contextRunner.withPropertyValues(
|
|
|
|
|
|
|
|
"spring.security.oauth2.resourceserver.jwt.oidc-issuer-location=http://jwk-oidc-issuer-location.com")
|
|
|
|
|
|
|
|
.withUserConfiguration(JwtDecoderConfig.class)
|
|
|
|
|
|
|
|
.withClassLoader(new FilteredClassLoader(JwtAuthenticationToken.class))
|
|
|
|
|
|
|
|
.run((context) -> assertThat(getBearerTokenFilter(context)).isNull());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@SuppressWarnings("unchecked")
|
|
|
|
@SuppressWarnings("unchecked")
|
|
|
|
private Filter getBearerTokenFilter(AssertableWebApplicationContext context) {
|
|
|
|
private Filter getBearerTokenFilter(AssertableWebApplicationContext context) {
|
|
|
|
FilterChainProxy filterChain = (FilterChainProxy) context
|
|
|
|
FilterChainProxy filterChain = (FilterChainProxy) context
|
|
|
@ -98,6 +176,42 @@ public class OAuth2ResourceServerAutoConfigurationTests {
|
|
|
|
.orElse(null);
|
|
|
|
.orElse(null);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private String cleanIssuerPath(String issuer) {
|
|
|
|
|
|
|
|
if (issuer.endsWith("/")) {
|
|
|
|
|
|
|
|
return issuer.substring(0, issuer.length() - 1);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return issuer;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private void setupMockResponse(String issuer) throws JsonProcessingException {
|
|
|
|
|
|
|
|
MockResponse mockResponse = new MockResponse()
|
|
|
|
|
|
|
|
.setResponseCode(HttpStatus.OK.value())
|
|
|
|
|
|
|
|
.setBody(new ObjectMapper().writeValueAsString(getResponse(issuer)))
|
|
|
|
|
|
|
|
.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
|
|
|
|
|
|
|
|
this.server.enqueue(mockResponse);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private Map<String, Object> getResponse(String issuer) {
|
|
|
|
|
|
|
|
Map<String, Object> response = new HashMap<>();
|
|
|
|
|
|
|
|
response.put("authorization_endpoint", "https://example.com/o/oauth2/v2/auth");
|
|
|
|
|
|
|
|
response.put("claims_supported", Collections.emptyList());
|
|
|
|
|
|
|
|
response.put("code_challenge_methods_supported", Collections.emptyList());
|
|
|
|
|
|
|
|
response.put("id_token_signing_alg_values_supported", Collections.emptyList());
|
|
|
|
|
|
|
|
response.put("issuer", issuer);
|
|
|
|
|
|
|
|
response.put("jwks_uri", "https://example.com/oauth2/v3/certs");
|
|
|
|
|
|
|
|
response.put("response_types_supported", Collections.emptyList());
|
|
|
|
|
|
|
|
response.put("revocation_endpoint", "https://example.com/o/oauth2/revoke");
|
|
|
|
|
|
|
|
response.put("scopes_supported", Collections.singletonList("openid"));
|
|
|
|
|
|
|
|
response.put("subject_types_supported", Collections.singletonList("public"));
|
|
|
|
|
|
|
|
response.put("grant_types_supported",
|
|
|
|
|
|
|
|
Collections.singletonList("authorization_code"));
|
|
|
|
|
|
|
|
response.put("token_endpoint", "https://example.com/oauth2/v4/token");
|
|
|
|
|
|
|
|
response.put("token_endpoint_auth_methods_supported",
|
|
|
|
|
|
|
|
Collections.singletonList("client_secret_basic"));
|
|
|
|
|
|
|
|
response.put("userinfo_endpoint", "https://example.com/oauth2/v3/userinfo");
|
|
|
|
|
|
|
|
return response;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Configuration
|
|
|
|
@Configuration
|
|
|
|
@EnableWebSecurity
|
|
|
|
@EnableWebSecurity
|
|
|
|
static class TestConfig {
|
|
|
|
static class TestConfig {
|
|
|
|