Support OIDC issuer uri in OAuth resource server config

pull/14199/head
artsiom 6 years ago committed by Madhura Bhave
parent c827530f4b
commit 0c299bbc1f

@ -21,6 +21,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
* OAuth 2.0 resource server properties.
*
* @author Madhura Bhave
* @author Artsiom Yudovin
* @since 2.1.0
*/
@ConfigurationProperties(prefix = "spring.security.oauth2.resourceserver")
@ -39,6 +40,11 @@ public class OAuth2ResourceServerProperties {
*/
private String jwkSetUri;
/**
* Oidc issuer location.
*/
private String oidcIssuerLocation;
public String getJwkSetUri() {
return this.jwkSetUri;
}
@ -47,6 +53,14 @@ public class OAuth2ResourceServerProperties {
this.jwkSetUri = jwkSetUri;
}
public String getOidcIssuerLocation() {
return this.oidcIssuerLocation;
}
public void setOidcIssuerLocation(String oidcIssuerLocation) {
this.oidcIssuerLocation = oidcIssuerLocation;
}
}
}

@ -0,0 +1,43 @@
/*
* Copyright 2012-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.security.oauth2.resource;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.security.oauth2.jwt.JwtDecoder;
/**
* Condition for creating {@link JwtDecoder} by oidc issuer location.
*
* @author Artsiom Yudovin
*/
public class OidcIssuerLocationCondition implements Condition {
private static final String OIDC_ISSUER_LOCATION = "spring.security.oauth2.resourceserver.jwt.oidc-issuer-location";
private static final String JWT_SET_URI = "spring.security.oauth2.resourceserver.jwt.jwk-set-uri";
@Override
public boolean matches(ConditionContext conditionContext,
AnnotatedTypeMetadata annotatedTypeMetadata) {
Environment environment = conditionContext.getEnvironment();
return environment.containsProperty(OIDC_ISSUER_LOCATION)
&& !environment.containsProperty(JWT_SET_URI);
}
}

@ -18,15 +18,20 @@ package org.springframework.boot.autoconfigure.security.oauth2.resource.servlet;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties;
import org.springframework.boot.autoconfigure.security.oauth2.resource.OidcIssuerLocationCondition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtDecoders;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoderJwkSupport;
/**
* Configures a {@link JwtDecoder} when a JWK Set URI is available.
* Configures a {@link JwtDecoder} when a JWK Set URI is available or Oidc Issuer
* Location.
*
* @author Madhura Bhave
* @author Artsiom Yudovin
*/
@Configuration
class OAuth2ResourceServerJwkConfiguration {
@ -44,4 +49,12 @@ class OAuth2ResourceServerJwkConfiguration {
return new NimbusJwtDecoderJwkSupport(this.properties.getJwt().getJwkSetUri());
}
@Bean
@Conditional(OidcIssuerLocationCondition.class)
@ConditionalOnMissingBean
public JwtDecoder jwtDecoderByOidcIssuerLocation() {
return JwtDecoders
.fromOidcIssuerLocation(this.properties.getJwt().getOidcIssuerLocation());
}
}

@ -15,11 +15,19 @@
*/
package org.springframework.boot.autoconfigure.security.oauth2.resource.servlet;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.Filter;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import org.junit.After;
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.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.context.annotation.Bean;
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.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.oauth2.jwt.JwtDecoder;
@ -44,6 +55,7 @@ import static org.mockito.Mockito.mock;
* Tests for {@link OAuth2ResourceServerAutoConfiguration}.
*
* @author Madhura Bhave
* @author Artsiom Yudovin
*/
public class OAuth2ResourceServerAutoConfigurationTests {
@ -52,6 +64,15 @@ public class OAuth2ResourceServerAutoConfigurationTests {
AutoConfigurations.of(OAuth2ResourceServerAutoConfiguration.class))
.withUserConfiguration(TestConfig.class);
private MockWebServer server;
@After
public void cleanup() throws Exception {
if (this.server != null) {
this.server.shutdown();
}
}
@Test
public void autoConfigurationShouldConfigureResourceServer() {
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
public void autoConfigurationWhenJwkSetUriNullShouldNotFail() {
this.contextRunner
@ -77,6 +138,14 @@ public class OAuth2ResourceServerAutoConfigurationTests {
.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
public void autoConfigurationShouldBeConditionalOnJwtAuthenticationTokenClass() {
this.contextRunner.withPropertyValues(
@ -86,6 +155,15 @@ public class OAuth2ResourceServerAutoConfigurationTests {
.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")
private Filter getBearerTokenFilter(AssertableWebApplicationContext context) {
FilterChainProxy filterChain = (FilterChainProxy) context
@ -98,6 +176,42 @@ public class OAuth2ResourceServerAutoConfigurationTests {
.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
@EnableWebSecurity
static class TestConfig {

@ -539,6 +539,7 @@ content into your application. Rather, pick only the properties that you need.
# SECURITY OAUTH2 RESOURCE SERVER ({sc-spring-boot-autoconfigure}/security/oauth2/resource/OAuth2ResourceServerProperties.{sc-ext}[OAuth2ResourceServerProperties])
spring.security.oauth2.resourceserver.jwt.jwk-set-uri= # JSON Web Key URI to use to verify the JWT token.
spring.security.oauth2.resource.jwt.oidc-issuer-location= # Location for issuer oidc
# ----------------------------------------
# DATA PROPERTIES

Loading…
Cancel
Save