Commit c2e95ee7 authored by Madhura Bhave's avatar Madhura Bhave

Support both JWT and Opaque token configuration for resource server

Closes gh-19426
parent 86591026
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
......@@ -19,8 +19,6 @@ import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import javax.annotation.PostConstruct;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException;
import org.springframework.core.io.Resource;
......@@ -49,26 +47,6 @@ public class OAuth2ResourceServerProperties {
return this.opaqueToken;
}
@PostConstruct
public void validate() {
if (this.getOpaquetoken().getIntrospectionUri() != null) {
if (this.getJwt().getJwkSetUri() != null) {
handleError("jwt.jwk-set-uri");
}
if (this.getJwt().getIssuerUri() != null) {
handleError("jwt.issuer-uri");
}
if (this.getJwt().getPublicKeyLocation() != null) {
handleError("jwt.public-key-location");
}
}
}
private void handleError(String property) {
throw new IllegalStateException(
"Only one of " + property + " and opaquetoken.introspection-uri should be configured.");
}
public static class Jwt {
/**
......
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
......@@ -26,9 +26,6 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector;
/**
* {@link EnableAutoConfiguration Auto-configuration} for Reactive OAuth2 resource server
......@@ -42,22 +39,8 @@ import org.springframework.security.oauth2.server.resource.introspection.Reactiv
@EnableConfigurationProperties(OAuth2ResourceServerProperties.class)
@ConditionalOnClass({ EnableWebFluxSecurity.class })
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@Import({ ReactiveOAuth2ResourceServerConfiguration.JwtConfiguration.class,
ReactiveOAuth2ResourceServerConfiguration.OpaqueTokenConfiguration.class })
public class ReactiveOAuth2ResourceServerAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ BearerTokenAuthenticationToken.class, ReactiveJwtDecoder.class })
@Import({ ReactiveOAuth2ResourceServerJwkConfiguration.JwtConfiguration.class,
ReactiveOAuth2ResourceServerJwkConfiguration.WebSecurityConfiguration.class })
static class JwtConfiguration {
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ BearerTokenAuthenticationToken.class, ReactiveOpaqueTokenIntrospector.class })
@Import({ ReactiveOAuth2ResourceServerOpaqueTokenConfiguration.OpaqueTokenIntrospectionClientConfiguration.class,
ReactiveOAuth2ResourceServerOpaqueTokenConfiguration.WebSecurityConfiguration.class })
static class OpaqueTokenConfiguration {
}
}
/*
* Copyright 2012-2020 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
*
* https://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.reactive;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector;
/**
* Configuration classes for OAuth2 Resource Server These should be {@code @Import} in a
* regular auto-configuration class to guarantee their order of execution.
*
* @author Madhura Bhave
*/
class ReactiveOAuth2ResourceServerConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ BearerTokenAuthenticationToken.class, ReactiveJwtDecoder.class })
@Import({ ReactiveOAuth2ResourceServerJwkConfiguration.JwtConfiguration.class,
ReactiveOAuth2ResourceServerJwkConfiguration.WebSecurityConfiguration.class })
static class JwtConfiguration {
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ BearerTokenAuthenticationToken.class, ReactiveOpaqueTokenIntrospector.class })
@Import({ ReactiveOAuth2ResourceServerOpaqueTokenConfiguration.OpaqueTokenIntrospectionClientConfiguration.class,
ReactiveOAuth2ResourceServerOpaqueTokenConfiguration.WebSecurityConfiguration.class })
static class OpaqueTokenConfiguration {
}
}
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
......@@ -17,7 +17,6 @@ package org.springframework.boot.autoconfigure.security.oauth2.resource.servlet;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
......@@ -25,10 +24,6 @@ import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServic
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
/**
* {@link EnableAutoConfiguration Auto-configuration} for OAuth2 resource server support.
......@@ -40,23 +35,8 @@ import org.springframework.security.oauth2.server.resource.introspection.OpaqueT
@AutoConfigureBefore({ SecurityAutoConfiguration.class, UserDetailsServiceAutoConfiguration.class })
@EnableConfigurationProperties(OAuth2ResourceServerProperties.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@Import({ Oauth2ResourceServerConfiguration.JwtConfiguration.class,
Oauth2ResourceServerConfiguration.OpaqueTokenConfiguration.class })
public class OAuth2ResourceServerAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ JwtAuthenticationToken.class, JwtDecoder.class })
@Import({ OAuth2ResourceServerJwtConfiguration.JwtDecoderConfiguration.class,
OAuth2ResourceServerJwtConfiguration.OAuth2WebSecurityConfigurerAdapter.class })
static class JwtConfiguration {
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ BearerTokenAuthenticationToken.class, OpaqueTokenIntrospector.class })
@Import({ OAuth2ResourceServerOpaqueTokenConfiguration.OpaqueTokenIntrospectionClientConfiguration.class,
OAuth2ResourceServerOpaqueTokenConfiguration.OAuth2WebSecurityConfigurerAdapter.class })
static class OpaqueTokenConfiguration {
}
}
/*
* Copyright 2012-2020 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
*
* https://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.servlet;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
/**
* Configuration classes for OAuth2 Resource Server
* These should be {@code @Import} in a regular auto-configuration class to guarantee
* their order of execution.
*
* @author Madhura Bhave
*/
class Oauth2ResourceServerConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ JwtAuthenticationToken.class, JwtDecoder.class })
@Import({ OAuth2ResourceServerJwtConfiguration.JwtDecoderConfiguration.class,
OAuth2ResourceServerJwtConfiguration.OAuth2WebSecurityConfigurerAdapter.class })
static class JwtConfiguration {
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ BearerTokenAuthenticationToken.class, OpaqueTokenIntrospector.class })
@Import({ OAuth2ResourceServerOpaqueTokenConfiguration.OpaqueTokenIntrospectionClientConfiguration.class,
OAuth2ResourceServerOpaqueTokenConfiguration.OAuth2WebSecurityConfigurerAdapter.class })
static class OpaqueTokenConfiguration {
}
}
......@@ -288,6 +288,20 @@ class ReactiveOAuth2ResourceServerAutoConfigurationTests {
});
}
@Test
void autoConfigurationWhenJwkSetUriAndIntrospectionUriAvailable() {
this.contextRunner
.withPropertyValues("spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://jwk-set-uri.com",
"spring.security.oauth2.resourceserver.opaquetoken.introspection-uri=https://check-token.com",
"spring.security.oauth2.resourceserver.opaquetoken.client-id=my-client-id",
"spring.security.oauth2.resourceserver.opaquetoken.client-secret=my-client-secret")
.run((context) -> {
assertThat(context).hasSingleBean(ReactiveOpaqueTokenIntrospector.class);
assertThat(context).hasSingleBean(ReactiveJwtDecoder.class);
assertFilterConfiguredWithJwtAuthenticationManager(context);
});
}
@Test
void opaqueTokenIntrospectorIsConditionalOnMissingBean() {
this.contextRunner
......@@ -320,36 +334,6 @@ class ReactiveOAuth2ResourceServerAutoConfigurationTests {
.run((context) -> assertThat(context).doesNotHaveBean(ReactiveOpaqueTokenIntrospector.class));
}
@Test
void autoConfigurationWhenBothJwkSetUriAndTokenIntrospectionUriSetShouldFail() {
this.contextRunner
.withPropertyValues(
"spring.security.oauth2.resourceserver.opaquetoken.introspection-uri=https://check-token.com",
"spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://jwk-set-uri.com")
.run((context) -> assertThat(context).hasFailed().getFailure().hasMessageContaining(
"Only one of jwt.jwk-set-uri and opaquetoken.introspection-uri should be configured."));
}
@Test
void autoConfigurationWhenBothJwtIssuerUriAndTokenIntrospectionUriSetShouldFail() {
this.contextRunner
.withPropertyValues(
"spring.security.oauth2.resourceserver.opaquetoken.introspection-uri=https://check-token.com",
"spring.security.oauth2.resourceserver.jwt.issuer-uri=https://jwk-oidc-issuer-location.com")
.run((context) -> assertThat(context).hasFailed().getFailure().hasMessageContaining(
"Only one of jwt.issuer-uri and opaquetoken.introspection-uri should be configured."));
}
@Test
void autoConfigurationWhenBothJwtKeyLocationAndTokenIntrospectionUriSetShouldFail() {
this.contextRunner
.withPropertyValues(
"spring.security.oauth2.resourceserver.opaquetoken.introspection-uri=https://check-token.com",
"spring.security.oauth2.resourceserver.jwt.public-key-location=classpath:public-key-location")
.run((context) -> assertThat(context).hasFailed().getFailure().hasMessageContaining(
"Only one of jwt.public-key-location and opaquetoken.introspection-uri should be configured."));
}
@SuppressWarnings("unchecked")
@Test
void autoConfigurationShouldConfigureResourceServerUsingJwkSetUriAndIssuerUri() throws Exception {
......
......@@ -48,6 +48,7 @@ import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtIssuerValidator;
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter;
......@@ -273,6 +274,22 @@ class OAuth2ResourceServerAutoConfigurationTests {
.run((context) -> assertThat(getBearerTokenFilter(context)).isNull());
}
@Test
void autoConfigurationWhenJwkSetUriAndIntrospectionUriAvailable() {
this.contextRunner
.withPropertyValues("spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://jwk-set-uri.com",
"spring.security.oauth2.resourceserver.opaquetoken.introspection-uri=https://check-token.com",
"spring.security.oauth2.resourceserver.opaquetoken.client-id=my-client-id",
"spring.security.oauth2.resourceserver.opaquetoken.client-secret=my-client-secret")
.run((context) -> {
assertThat(context).hasSingleBean(OpaqueTokenIntrospector.class);
assertThat(context).hasSingleBean(JwtDecoder.class);
assertThat(getBearerTokenFilter(context))
.extracting("authenticationManagerResolver.arg$1.providers").asList()
.hasAtLeastOneElementOfType(JwtAuthenticationProvider.class);
});
}
@Test
void autoConfigurationWhenIntrospectionUriAvailableShouldConfigureIntrospectionClient() {
this.contextRunner
......@@ -305,36 +322,6 @@ class OAuth2ResourceServerAutoConfigurationTests {
.run((context) -> assertThat(context).doesNotHaveBean(OpaqueTokenIntrospector.class));
}
@Test
void autoConfigurationWhenBothJwkSetUriAndTokenIntrospectionUriSetShouldFail() {
this.contextRunner
.withPropertyValues(
"spring.security.oauth2.resourceserver.opaquetoken.introspection-uri=https://check-token.com",
"spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://jwk-set-uri.com")
.run((context) -> assertThat(context).hasFailed().getFailure().hasMessageContaining(
"Only one of jwt.jwk-set-uri and opaquetoken.introspection-uri should be configured."));
}
@Test
void autoConfigurationWhenBothJwtIssuerUriAndTokenIntrospectionUriSetShouldFail() {
this.contextRunner
.withPropertyValues(
"spring.security.oauth2.resourceserver.opaquetoken.introspection-uri=https://check-token.com",
"spring.security.oauth2.resourceserver.jwt.issuer-uri=https://jwk-oidc-issuer-location.com")
.run((context) -> assertThat(context).hasFailed().getFailure().hasMessageContaining(
"Only one of jwt.issuer-uri and opaquetoken.introspection-uri should be configured."));
}
@Test
void autoConfigurationWhenBothJwtKeyLocationAndTokenIntrospectionUriSetShouldFail() {
this.contextRunner
.withPropertyValues(
"spring.security.oauth2.resourceserver.opaquetoken.introspection-uri=https://check-token.com",
"spring.security.oauth2.resourceserver.jwt.public-key-location=classpath:public-key-location")
.run((context) -> assertThat(context).hasFailed().getFailure().hasMessageContaining(
"Only one of jwt.public-key-location and opaquetoken.introspection-uri should be configured."));
}
@SuppressWarnings("unchecked")
@Test
void autoConfigurationShouldConfigureResourceServerUsingJwkSetUriAndIssuerUri() throws Exception {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment