Skip to content

Commit 2560b54

Browse files
committed
Add configuration support for Opaque Token authentication
Closes gh-15872
1 parent 8d44e31 commit 2560b54

12 files changed

+541
-154
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/OAuth2ResourceServerProperties.java

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import java.io.InputStream;
2020
import java.nio.charset.StandardCharsets;
2121

22+
import javax.annotation.PostConstruct;
23+
2224
import org.springframework.boot.context.properties.ConfigurationProperties;
2325
import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException;
2426
import org.springframework.core.io.Resource;
@@ -41,6 +43,32 @@ public Jwt getJwt() {
4143
return this.jwt;
4244
}
4345

46+
private final OpaqueToken opaqueToken = new OpaqueToken();
47+
48+
public OpaqueToken getOpaqueToken() {
49+
return this.opaqueToken;
50+
}
51+
52+
@PostConstruct
53+
public void validate() {
54+
if (this.getOpaqueToken().getIntrospectionUri() != null) {
55+
if (this.getJwt().getJwkSetUri() != null) {
56+
handleError("jwt.jwk-set-uri");
57+
}
58+
if (this.getJwt().getIssuerUri() != null) {
59+
handleError("jwt.issuer-uri");
60+
}
61+
if (this.getJwt().getPublicKeyLocation() != null) {
62+
handleError("jwt.public-key-location");
63+
}
64+
}
65+
}
66+
67+
private void handleError(String property) {
68+
throw new IllegalStateException(
69+
"Only one of " + property + " and opaque-token.introspection-uri should be configured.");
70+
}
71+
4472
public static class Jwt {
4573

4674
/**
@@ -109,4 +137,47 @@ public String readPublicKey() throws IOException {
109137

110138
}
111139

140+
public static class OpaqueToken {
141+
142+
/**
143+
* Client id used to authenticate with the token introspection endpoint.
144+
*/
145+
private String clientId;
146+
147+
/**
148+
* Client secret used to authenticate with the token introspection endpoint.
149+
*/
150+
private String clientSecret;
151+
152+
/**
153+
* OAuth 2.0 endpoint through which token introspection is accomplished.
154+
*/
155+
private String introspectionUri;
156+
157+
public String getClientId() {
158+
return this.clientId;
159+
}
160+
161+
public void setClientId(String clientId) {
162+
this.clientId = clientId;
163+
}
164+
165+
public String getClientSecret() {
166+
return this.clientSecret;
167+
}
168+
169+
public void setClientSecret(String clientSecret) {
170+
this.clientSecret = clientSecret;
171+
}
172+
173+
public String getIntrospectionUri() {
174+
return this.introspectionUri;
175+
}
176+
177+
public void setIntrospectionUri(String introspectionUri) {
178+
this.introspectionUri = introspectionUri;
179+
}
180+
181+
}
182+
112183
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerAutoConfiguration.java

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
2828
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
2929
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
30+
import org.springframework.security.oauth2.server.resource.authentication.OAuth2IntrospectionAuthenticationToken;
31+
import org.springframework.security.oauth2.server.resource.introspection.ReactiveOAuth2TokenIntrospectionClient;
3032

3133
/**
3234
* {@link EnableAutoConfiguration Auto-configuration} for Reactive OAuth2 resource server
@@ -38,10 +40,24 @@
3840
@Configuration(proxyBeanMethods = false)
3941
@AutoConfigureBefore(ReactiveSecurityAutoConfiguration.class)
4042
@EnableConfigurationProperties(OAuth2ResourceServerProperties.class)
41-
@ConditionalOnClass({ EnableWebFluxSecurity.class, BearerTokenAuthenticationToken.class, ReactiveJwtDecoder.class })
43+
@ConditionalOnClass({ EnableWebFluxSecurity.class })
4244
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
43-
@Import({ ReactiveOAuth2ResourceServerJwkConfiguration.class,
44-
ReactiveOAuth2ResourceServerWebSecurityConfiguration.class })
4545
public class ReactiveOAuth2ResourceServerAutoConfiguration {
4646

47+
@Configuration(proxyBeanMethods = false)
48+
@ConditionalOnClass({ BearerTokenAuthenticationToken.class, ReactiveJwtDecoder.class })
49+
@Import({ ReactiveOAuth2ResourceServerJwkConfiguration.JwtConfiguration.class,
50+
ReactiveOAuth2ResourceServerJwkConfiguration.WebSecurityConfiguration.class })
51+
static class JwtConfiguration {
52+
53+
}
54+
55+
@Configuration(proxyBeanMethods = false)
56+
@ConditionalOnClass({ OAuth2IntrospectionAuthenticationToken.class, ReactiveOAuth2TokenIntrospectionClient.class })
57+
@Import({ ReactiveOAuth2ResourceServerOpaqueTokenConfiguration.OpaqueTokenIntrospectionClientConfiguration.class,
58+
ReactiveOAuth2ResourceServerOpaqueTokenConfiguration.WebSecurityConfiguration.class })
59+
static class OpaqueTokenConfiguration {
60+
61+
}
62+
4763
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerJwkConfiguration.java

Lines changed: 49 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.security.spec.X509EncodedKeySpec;
2121
import java.util.Base64;
2222

23+
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
2324
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
2425
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
2526
import org.springframework.boot.autoconfigure.security.oauth2.resource.IssuerUriCondition;
@@ -28,52 +29,73 @@
2829
import org.springframework.context.annotation.Bean;
2930
import org.springframework.context.annotation.Conditional;
3031
import org.springframework.context.annotation.Configuration;
32+
import org.springframework.security.config.web.server.ServerHttpSecurity;
3133
import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder;
3234
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
3335
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoders;
36+
import org.springframework.security.web.server.SecurityWebFilterChain;
3437

3538
/**
3639
* Configures a {@link ReactiveJwtDecoder} when a JWK Set URI, OpenID Connect Issuer URI
37-
* or Public Key configuration is available.
40+
* or Public Key configuration is available. Also configures a
41+
* {@link SecurityWebFilterChain} if a {@link ReactiveJwtDecoder} bean is found.
3842
*
3943
* @author Madhura Bhave
4044
* @author Artsiom Yudovin
4145
*/
4246
@Configuration(proxyBeanMethods = false)
4347
class ReactiveOAuth2ResourceServerJwkConfiguration {
4448

45-
private final OAuth2ResourceServerProperties.Jwt properties;
49+
@Configuration(proxyBeanMethods = false)
50+
@ConditionalOnMissingBean(ReactiveJwtDecoder.class)
51+
static class JwtConfiguration {
4652

47-
ReactiveOAuth2ResourceServerJwkConfiguration(OAuth2ResourceServerProperties properties) {
48-
this.properties = properties.getJwt();
49-
}
53+
private final OAuth2ResourceServerProperties.Jwt properties;
5054

51-
@Bean
52-
@ConditionalOnProperty(name = "spring.security.oauth2.resourceserver.jwt.jwk-set-uri")
53-
@ConditionalOnMissingBean
54-
public ReactiveJwtDecoder jwtDecoder() {
55-
return new NimbusReactiveJwtDecoder(this.properties.getJwkSetUri());
56-
}
55+
JwtConfiguration(OAuth2ResourceServerProperties properties) {
56+
this.properties = properties.getJwt();
57+
}
5758

58-
@Bean
59-
@Conditional(KeyValueCondition.class)
60-
@ConditionalOnMissingBean
61-
public NimbusReactiveJwtDecoder jwtDecoderByPublicKeyValue() throws Exception {
62-
RSAPublicKey publicKey = (RSAPublicKey) KeyFactory.getInstance("RSA")
63-
.generatePublic(new X509EncodedKeySpec(getKeySpec(this.properties.readPublicKey())));
64-
return NimbusReactiveJwtDecoder.withPublicKey(publicKey).build();
65-
}
59+
@Bean
60+
@ConditionalOnProperty(name = "spring.security.oauth2.resourceserver.jwt.jwk-set-uri")
61+
public ReactiveJwtDecoder jwtDecoder() {
62+
return new NimbusReactiveJwtDecoder(this.properties.getJwkSetUri());
63+
}
64+
65+
@Bean
66+
@Conditional(KeyValueCondition.class)
67+
public NimbusReactiveJwtDecoder jwtDecoderByPublicKeyValue() throws Exception {
68+
RSAPublicKey publicKey = (RSAPublicKey) KeyFactory.getInstance("RSA")
69+
.generatePublic(new X509EncodedKeySpec(getKeySpec(this.properties.readPublicKey())));
70+
return NimbusReactiveJwtDecoder.withPublicKey(publicKey).build();
71+
}
72+
73+
private byte[] getKeySpec(String keyValue) {
74+
keyValue = keyValue.replace("-----BEGIN PUBLIC KEY-----", "").replace("-----END PUBLIC KEY-----", "");
75+
return Base64.getMimeDecoder().decode(keyValue);
76+
}
77+
78+
@Bean
79+
@Conditional(IssuerUriCondition.class)
80+
public ReactiveJwtDecoder jwtDecoderByIssuerUri() {
81+
return ReactiveJwtDecoders.fromOidcIssuerLocation(this.properties.getIssuerUri());
82+
}
6683

67-
private byte[] getKeySpec(String keyValue) {
68-
keyValue = keyValue.replace("-----BEGIN PUBLIC KEY-----", "").replace("-----END PUBLIC KEY-----", "");
69-
return Base64.getMimeDecoder().decode(keyValue);
7084
}
7185

72-
@Bean
73-
@Conditional(IssuerUriCondition.class)
74-
@ConditionalOnMissingBean
75-
public ReactiveJwtDecoder jwtDecoderByIssuerUri() {
76-
return ReactiveJwtDecoders.fromOidcIssuerLocation(this.properties.getIssuerUri());
86+
@Configuration(proxyBeanMethods = false)
87+
@ConditionalOnMissingBean(SecurityWebFilterChain.class)
88+
static class WebSecurityConfiguration {
89+
90+
@Bean
91+
@ConditionalOnBean(ReactiveJwtDecoder.class)
92+
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http,
93+
ReactiveJwtDecoder jwtDecoder) {
94+
http.authorizeExchange().anyExchange().authenticated().and().oauth2ResourceServer().jwt()
95+
.jwtDecoder(jwtDecoder);
96+
return http.build();
97+
}
98+
7799
}
78100

79101
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright 2012-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.security.oauth2.resource.reactive;
18+
19+
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
20+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
21+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
22+
import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties;
23+
import org.springframework.context.annotation.Bean;
24+
import org.springframework.context.annotation.Configuration;
25+
import org.springframework.security.config.web.server.ServerHttpSecurity;
26+
import org.springframework.security.oauth2.server.resource.introspection.NimbusReactiveOAuth2TokenIntrospectionClient;
27+
import org.springframework.security.oauth2.server.resource.introspection.ReactiveOAuth2TokenIntrospectionClient;
28+
import org.springframework.security.web.server.SecurityWebFilterChain;
29+
30+
/**
31+
* Configures a {@link ReactiveOAuth2TokenIntrospectionClient} when a token introspection
32+
* endpoint is available. Also configures a {@link SecurityWebFilterChain} if a
33+
* {@link ReactiveOAuth2TokenIntrospectionClient} bean is found.
34+
*
35+
* @author Madhura Bhave
36+
*/
37+
class ReactiveOAuth2ResourceServerOpaqueTokenConfiguration {
38+
39+
@Configuration(proxyBeanMethods = false)
40+
@ConditionalOnMissingBean(ReactiveOAuth2TokenIntrospectionClient.class)
41+
static class OpaqueTokenIntrospectionClientConfiguration {
42+
43+
@Bean
44+
@ConditionalOnProperty(name = "spring.security.oauth2.resourceserver.opaque-token.introspection-uri")
45+
public NimbusReactiveOAuth2TokenIntrospectionClient oAuth2TokenIntrospectionClient(
46+
OAuth2ResourceServerProperties properties) {
47+
OAuth2ResourceServerProperties.OpaqueToken opaqueToken = properties.getOpaqueToken();
48+
return new NimbusReactiveOAuth2TokenIntrospectionClient(opaqueToken.getIntrospectionUri(),
49+
opaqueToken.getClientId(), opaqueToken.getClientSecret());
50+
}
51+
52+
}
53+
54+
@Configuration(proxyBeanMethods = false)
55+
@ConditionalOnMissingBean(SecurityWebFilterChain.class)
56+
static class WebSecurityConfiguration {
57+
58+
@Bean
59+
@ConditionalOnBean(ReactiveOAuth2TokenIntrospectionClient.class)
60+
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
61+
http.authorizeExchange().anyExchange().authenticated().and().oauth2ResourceServer().opaqueToken();
62+
return http.build();
63+
}
64+
65+
}
66+
67+
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerWebSecurityConfiguration.java

Lines changed: 0 additions & 44 deletions
This file was deleted.

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerAutoConfiguration.java

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,36 @@
2626
import org.springframework.context.annotation.Import;
2727
import org.springframework.security.oauth2.jwt.JwtDecoder;
2828
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
29+
import org.springframework.security.oauth2.server.resource.authentication.OAuth2IntrospectionAuthenticationToken;
30+
import org.springframework.security.oauth2.server.resource.introspection.OAuth2TokenIntrospectionClient;
2931

3032
/**
31-
* {@link EnableAutoConfiguration Auto-configuration} for OAuth resource server support.
33+
* {@link EnableAutoConfiguration Auto-configuration} for OAuth2 resource server support.
3234
*
3335
* @author Madhura Bhave
3436
* @since 2.1.0
3537
*/
3638
@Configuration(proxyBeanMethods = false)
3739
@AutoConfigureBefore(SecurityAutoConfiguration.class)
3840
@EnableConfigurationProperties(OAuth2ResourceServerProperties.class)
39-
@ConditionalOnClass({ JwtAuthenticationToken.class, JwtDecoder.class })
4041
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
41-
@Import({ OAuth2ResourceServerJwtConfiguration.class, OAuth2ResourceServerWebSecurityConfiguration.class })
42+
4243
public class OAuth2ResourceServerAutoConfiguration {
4344

45+
@Configuration(proxyBeanMethods = false)
46+
@ConditionalOnClass({ JwtAuthenticationToken.class, JwtDecoder.class })
47+
@Import({ OAuth2ResourceServerJwtConfiguration.JwtDecoderConfiguration.class,
48+
OAuth2ResourceServerJwtConfiguration.OAuth2WebSecurityConfigurerAdapter.class })
49+
static class JwtConfiguration {
50+
51+
}
52+
53+
@Configuration(proxyBeanMethods = false)
54+
@ConditionalOnClass({ OAuth2IntrospectionAuthenticationToken.class, OAuth2TokenIntrospectionClient.class })
55+
@Import({ OAuth2ResourceServerOpaqueTokenConfiguration.OpaqueTokenIntrospectionClientConfiguration.class,
56+
OAuth2ResourceServerOpaqueTokenConfiguration.OAuth2WebSecurityConfigurerAdapter.class })
57+
static class OpaqueTokenConfiguration {
58+
59+
}
60+
4461
}

0 commit comments

Comments
 (0)