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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -19,8 +19,6 @@ import java.io.IOException; ...@@ -19,8 +19,6 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import javax.annotation.PostConstruct;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException; import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
...@@ -49,26 +47,6 @@ public class OAuth2ResourceServerProperties { ...@@ -49,26 +47,6 @@ public class OAuth2ResourceServerProperties {
return this.opaqueToken; 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 { 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 ...@@ -26,9 +26,6 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; 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 * {@link EnableAutoConfiguration Auto-configuration} for Reactive OAuth2 resource server
...@@ -42,22 +39,8 @@ import org.springframework.security.oauth2.server.resource.introspection.Reactiv ...@@ -42,22 +39,8 @@ import org.springframework.security.oauth2.server.resource.introspection.Reactiv
@EnableConfigurationProperties(OAuth2ResourceServerProperties.class) @EnableConfigurationProperties(OAuth2ResourceServerProperties.class)
@ConditionalOnClass({ EnableWebFluxSecurity.class }) @ConditionalOnClass({ EnableWebFluxSecurity.class })
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@Import({ ReactiveOAuth2ResourceServerConfiguration.JwtConfiguration.class,
ReactiveOAuth2ResourceServerConfiguration.OpaqueTokenConfiguration.class })
public class ReactiveOAuth2ResourceServerAutoConfiguration { 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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; ...@@ -17,7 +17,6 @@ package org.springframework.boot.autoconfigure.security.oauth2.resource.servlet;
import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 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.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties; import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
...@@ -25,10 +24,6 @@ import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServic ...@@ -25,10 +24,6 @@ import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServic
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; 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. * {@link EnableAutoConfiguration Auto-configuration} for OAuth2 resource server support.
...@@ -40,23 +35,8 @@ import org.springframework.security.oauth2.server.resource.introspection.OpaqueT ...@@ -40,23 +35,8 @@ import org.springframework.security.oauth2.server.resource.introspection.OpaqueT
@AutoConfigureBefore({ SecurityAutoConfiguration.class, UserDetailsServiceAutoConfiguration.class }) @AutoConfigureBefore({ SecurityAutoConfiguration.class, UserDetailsServiceAutoConfiguration.class })
@EnableConfigurationProperties(OAuth2ResourceServerProperties.class) @EnableConfigurationProperties(OAuth2ResourceServerProperties.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@Import({ Oauth2ResourceServerConfiguration.JwtConfiguration.class,
Oauth2ResourceServerConfiguration.OpaqueTokenConfiguration.class })
public class OAuth2ResourceServerAutoConfiguration { 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 { ...@@ -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 @Test
void opaqueTokenIntrospectorIsConditionalOnMissingBean() { void opaqueTokenIntrospectorIsConditionalOnMissingBean() {
this.contextRunner this.contextRunner
...@@ -320,36 +334,6 @@ class ReactiveOAuth2ResourceServerAutoConfigurationTests { ...@@ -320,36 +334,6 @@ class ReactiveOAuth2ResourceServerAutoConfigurationTests {
.run((context) -> assertThat(context).doesNotHaveBean(ReactiveOpaqueTokenIntrospector.class)); .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") @SuppressWarnings("unchecked")
@Test @Test
void autoConfigurationShouldConfigureResourceServerUsingJwkSetUriAndIssuerUri() throws Exception { void autoConfigurationShouldConfigureResourceServerUsingJwkSetUriAndIssuerUri() throws Exception {
......
...@@ -48,6 +48,7 @@ import org.springframework.security.oauth2.jwt.Jwt; ...@@ -48,6 +48,7 @@ import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtIssuerValidator; import org.springframework.security.oauth2.jwt.JwtIssuerValidator;
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken; 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.authentication.JwtAuthenticationToken;
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector; import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter; import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter;
...@@ -273,6 +274,22 @@ class OAuth2ResourceServerAutoConfigurationTests { ...@@ -273,6 +274,22 @@ class OAuth2ResourceServerAutoConfigurationTests {
.run((context) -> assertThat(getBearerTokenFilter(context)).isNull()); .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 @Test
void autoConfigurationWhenIntrospectionUriAvailableShouldConfigureIntrospectionClient() { void autoConfigurationWhenIntrospectionUriAvailableShouldConfigureIntrospectionClient() {
this.contextRunner this.contextRunner
...@@ -305,36 +322,6 @@ class OAuth2ResourceServerAutoConfigurationTests { ...@@ -305,36 +322,6 @@ class OAuth2ResourceServerAutoConfigurationTests {
.run((context) -> assertThat(context).doesNotHaveBean(OpaqueTokenIntrospector.class)); .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") @SuppressWarnings("unchecked")
@Test @Test
void autoConfigurationShouldConfigureResourceServerUsingJwkSetUriAndIssuerUri() throws Exception { 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