Support both JWT and Opaque token configuration for resource server
Closes gh-19426
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* 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 {
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* 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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* 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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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 {
|
||||
|
||||
Reference in New Issue
Block a user