Skip to content

Commit c2e95ee

Browse files
committed
Support both JWT and Opaque token configuration for resource server
Closes gh-19426
1 parent 8659102 commit c2e95ee

File tree

7 files changed

+140
-126
lines changed

7 files changed

+140
-126
lines changed

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

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,8 +19,6 @@
1919
import java.io.InputStream;
2020
import java.nio.charset.StandardCharsets;
2121

22-
import javax.annotation.PostConstruct;
23-
2422
import org.springframework.boot.context.properties.ConfigurationProperties;
2523
import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException;
2624
import org.springframework.core.io.Resource;
@@ -49,26 +47,6 @@ public Opaquetoken getOpaquetoken() {
4947
return this.opaqueToken;
5048
}
5149

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 opaquetoken.introspection-uri should be configured.");
70-
}
71-
7250
public static class Jwt {
7351

7452
/**

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

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -26,9 +26,6 @@
2626
import org.springframework.context.annotation.Configuration;
2727
import org.springframework.context.annotation.Import;
2828
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
29-
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
30-
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
31-
import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector;
3229

3330
/**
3431
* {@link EnableAutoConfiguration Auto-configuration} for Reactive OAuth2 resource server
@@ -42,22 +39,8 @@
4239
@EnableConfigurationProperties(OAuth2ResourceServerProperties.class)
4340
@ConditionalOnClass({ EnableWebFluxSecurity.class })
4441
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
42+
@Import({ ReactiveOAuth2ResourceServerConfiguration.JwtConfiguration.class,
43+
ReactiveOAuth2ResourceServerConfiguration.OpaqueTokenConfiguration.class })
4544
public class ReactiveOAuth2ResourceServerAutoConfiguration {
4645

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({ BearerTokenAuthenticationToken.class, ReactiveOpaqueTokenIntrospector.class })
57-
@Import({ ReactiveOAuth2ResourceServerOpaqueTokenConfiguration.OpaqueTokenIntrospectionClientConfiguration.class,
58-
ReactiveOAuth2ResourceServerOpaqueTokenConfiguration.WebSecurityConfiguration.class })
59-
static class OpaqueTokenConfiguration {
60-
61-
}
62-
6346
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright 2012-2020 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.ConditionalOnClass;
20+
import org.springframework.context.annotation.Configuration;
21+
import org.springframework.context.annotation.Import;
22+
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
23+
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
24+
import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector;
25+
26+
/**
27+
* Configuration classes for OAuth2 Resource Server These should be {@code @Import} in a
28+
* regular auto-configuration class to guarantee their order of execution.
29+
*
30+
* @author Madhura Bhave
31+
*/
32+
class ReactiveOAuth2ResourceServerConfiguration {
33+
34+
@Configuration(proxyBeanMethods = false)
35+
@ConditionalOnClass({ BearerTokenAuthenticationToken.class, ReactiveJwtDecoder.class })
36+
@Import({ ReactiveOAuth2ResourceServerJwkConfiguration.JwtConfiguration.class,
37+
ReactiveOAuth2ResourceServerJwkConfiguration.WebSecurityConfiguration.class })
38+
static class JwtConfiguration {
39+
40+
}
41+
42+
@Configuration(proxyBeanMethods = false)
43+
@ConditionalOnClass({ BearerTokenAuthenticationToken.class, ReactiveOpaqueTokenIntrospector.class })
44+
@Import({ ReactiveOAuth2ResourceServerOpaqueTokenConfiguration.OpaqueTokenIntrospectionClientConfiguration.class,
45+
ReactiveOAuth2ResourceServerOpaqueTokenConfiguration.WebSecurityConfiguration.class })
46+
static class OpaqueTokenConfiguration {
47+
48+
}
49+
50+
}
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,18 +17,13 @@
1717

1818
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
1919
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
20-
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
2120
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
2221
import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties;
2322
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
2423
import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration;
2524
import org.springframework.boot.context.properties.EnableConfigurationProperties;
2625
import org.springframework.context.annotation.Configuration;
2726
import org.springframework.context.annotation.Import;
28-
import org.springframework.security.oauth2.jwt.JwtDecoder;
29-
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
30-
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
31-
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
3227

3328
/**
3429
* {@link EnableAutoConfiguration Auto-configuration} for OAuth2 resource server support.
@@ -40,23 +35,8 @@
4035
@AutoConfigureBefore({ SecurityAutoConfiguration.class, UserDetailsServiceAutoConfiguration.class })
4136
@EnableConfigurationProperties(OAuth2ResourceServerProperties.class)
4237
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
43-
38+
@Import({ Oauth2ResourceServerConfiguration.JwtConfiguration.class,
39+
Oauth2ResourceServerConfiguration.OpaqueTokenConfiguration.class })
4440
public class OAuth2ResourceServerAutoConfiguration {
4541

46-
@Configuration(proxyBeanMethods = false)
47-
@ConditionalOnClass({ JwtAuthenticationToken.class, JwtDecoder.class })
48-
@Import({ OAuth2ResourceServerJwtConfiguration.JwtDecoderConfiguration.class,
49-
OAuth2ResourceServerJwtConfiguration.OAuth2WebSecurityConfigurerAdapter.class })
50-
static class JwtConfiguration {
51-
52-
}
53-
54-
@Configuration(proxyBeanMethods = false)
55-
@ConditionalOnClass({ BearerTokenAuthenticationToken.class, OpaqueTokenIntrospector.class })
56-
@Import({ OAuth2ResourceServerOpaqueTokenConfiguration.OpaqueTokenIntrospectionClientConfiguration.class,
57-
OAuth2ResourceServerOpaqueTokenConfiguration.OAuth2WebSecurityConfigurerAdapter.class })
58-
static class OpaqueTokenConfiguration {
59-
60-
}
61-
6242
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright 2012-2020 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.servlet;
18+
19+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
20+
import org.springframework.context.annotation.Configuration;
21+
import org.springframework.context.annotation.Import;
22+
import org.springframework.security.oauth2.jwt.JwtDecoder;
23+
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
24+
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
25+
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
26+
27+
/**
28+
* Configuration classes for OAuth2 Resource Server
29+
* These should be {@code @Import} in a regular auto-configuration class to guarantee
30+
* their order of execution.
31+
*
32+
* @author Madhura Bhave
33+
*/
34+
class Oauth2ResourceServerConfiguration {
35+
36+
@Configuration(proxyBeanMethods = false)
37+
@ConditionalOnClass({ JwtAuthenticationToken.class, JwtDecoder.class })
38+
@Import({ OAuth2ResourceServerJwtConfiguration.JwtDecoderConfiguration.class,
39+
OAuth2ResourceServerJwtConfiguration.OAuth2WebSecurityConfigurerAdapter.class })
40+
static class JwtConfiguration {
41+
42+
}
43+
44+
@Configuration(proxyBeanMethods = false)
45+
@ConditionalOnClass({ BearerTokenAuthenticationToken.class, OpaqueTokenIntrospector.class })
46+
@Import({ OAuth2ResourceServerOpaqueTokenConfiguration.OpaqueTokenIntrospectionClientConfiguration.class,
47+
OAuth2ResourceServerOpaqueTokenConfiguration.OAuth2WebSecurityConfigurerAdapter.class })
48+
static class OpaqueTokenConfiguration {
49+
50+
}
51+
52+
}

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerAutoConfigurationTests.java

Lines changed: 14 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,20 @@ void autoConfigurationWhenIntrospectionUriAvailableShouldConfigureIntrospectionC
288288
});
289289
}
290290

291+
@Test
292+
void autoConfigurationWhenJwkSetUriAndIntrospectionUriAvailable() {
293+
this.contextRunner
294+
.withPropertyValues("spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://jwk-set-uri.com",
295+
"spring.security.oauth2.resourceserver.opaquetoken.introspection-uri=https://check-token.com",
296+
"spring.security.oauth2.resourceserver.opaquetoken.client-id=my-client-id",
297+
"spring.security.oauth2.resourceserver.opaquetoken.client-secret=my-client-secret")
298+
.run((context) -> {
299+
assertThat(context).hasSingleBean(ReactiveOpaqueTokenIntrospector.class);
300+
assertThat(context).hasSingleBean(ReactiveJwtDecoder.class);
301+
assertFilterConfiguredWithJwtAuthenticationManager(context);
302+
});
303+
}
304+
291305
@Test
292306
void opaqueTokenIntrospectorIsConditionalOnMissingBean() {
293307
this.contextRunner
@@ -320,36 +334,6 @@ void autoConfigurationWhenIntrospectionUriAvailableShouldBeConditionalOnClass()
320334
.run((context) -> assertThat(context).doesNotHaveBean(ReactiveOpaqueTokenIntrospector.class));
321335
}
322336

323-
@Test
324-
void autoConfigurationWhenBothJwkSetUriAndTokenIntrospectionUriSetShouldFail() {
325-
this.contextRunner
326-
.withPropertyValues(
327-
"spring.security.oauth2.resourceserver.opaquetoken.introspection-uri=https://check-token.com",
328-
"spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://jwk-set-uri.com")
329-
.run((context) -> assertThat(context).hasFailed().getFailure().hasMessageContaining(
330-
"Only one of jwt.jwk-set-uri and opaquetoken.introspection-uri should be configured."));
331-
}
332-
333-
@Test
334-
void autoConfigurationWhenBothJwtIssuerUriAndTokenIntrospectionUriSetShouldFail() {
335-
this.contextRunner
336-
.withPropertyValues(
337-
"spring.security.oauth2.resourceserver.opaquetoken.introspection-uri=https://check-token.com",
338-
"spring.security.oauth2.resourceserver.jwt.issuer-uri=https://jwk-oidc-issuer-location.com")
339-
.run((context) -> assertThat(context).hasFailed().getFailure().hasMessageContaining(
340-
"Only one of jwt.issuer-uri and opaquetoken.introspection-uri should be configured."));
341-
}
342-
343-
@Test
344-
void autoConfigurationWhenBothJwtKeyLocationAndTokenIntrospectionUriSetShouldFail() {
345-
this.contextRunner
346-
.withPropertyValues(
347-
"spring.security.oauth2.resourceserver.opaquetoken.introspection-uri=https://check-token.com",
348-
"spring.security.oauth2.resourceserver.jwt.public-key-location=classpath:public-key-location")
349-
.run((context) -> assertThat(context).hasFailed().getFailure().hasMessageContaining(
350-
"Only one of jwt.public-key-location and opaquetoken.introspection-uri should be configured."));
351-
}
352-
353337
@SuppressWarnings("unchecked")
354338
@Test
355339
void autoConfigurationShouldConfigureResourceServerUsingJwkSetUriAndIssuerUri() throws Exception {

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerAutoConfigurationTests.java

Lines changed: 17 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import org.springframework.security.oauth2.jwt.JwtDecoder;
4949
import org.springframework.security.oauth2.jwt.JwtIssuerValidator;
5050
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
51+
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
5152
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
5253
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
5354
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter;
@@ -273,6 +274,22 @@ void autoConfigurationShouldBeConditionalOnJwtDecoderClass() {
273274
.run((context) -> assertThat(getBearerTokenFilter(context)).isNull());
274275
}
275276

277+
@Test
278+
void autoConfigurationWhenJwkSetUriAndIntrospectionUriAvailable() {
279+
this.contextRunner
280+
.withPropertyValues("spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://jwk-set-uri.com",
281+
"spring.security.oauth2.resourceserver.opaquetoken.introspection-uri=https://check-token.com",
282+
"spring.security.oauth2.resourceserver.opaquetoken.client-id=my-client-id",
283+
"spring.security.oauth2.resourceserver.opaquetoken.client-secret=my-client-secret")
284+
.run((context) -> {
285+
assertThat(context).hasSingleBean(OpaqueTokenIntrospector.class);
286+
assertThat(context).hasSingleBean(JwtDecoder.class);
287+
assertThat(getBearerTokenFilter(context))
288+
.extracting("authenticationManagerResolver.arg$1.providers").asList()
289+
.hasAtLeastOneElementOfType(JwtAuthenticationProvider.class);
290+
});
291+
}
292+
276293
@Test
277294
void autoConfigurationWhenIntrospectionUriAvailableShouldConfigureIntrospectionClient() {
278295
this.contextRunner
@@ -305,36 +322,6 @@ void autoConfigurationWhenIntrospectionUriAvailableShouldBeConditionalOnClass()
305322
.run((context) -> assertThat(context).doesNotHaveBean(OpaqueTokenIntrospector.class));
306323
}
307324

308-
@Test
309-
void autoConfigurationWhenBothJwkSetUriAndTokenIntrospectionUriSetShouldFail() {
310-
this.contextRunner
311-
.withPropertyValues(
312-
"spring.security.oauth2.resourceserver.opaquetoken.introspection-uri=https://check-token.com",
313-
"spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://jwk-set-uri.com")
314-
.run((context) -> assertThat(context).hasFailed().getFailure().hasMessageContaining(
315-
"Only one of jwt.jwk-set-uri and opaquetoken.introspection-uri should be configured."));
316-
}
317-
318-
@Test
319-
void autoConfigurationWhenBothJwtIssuerUriAndTokenIntrospectionUriSetShouldFail() {
320-
this.contextRunner
321-
.withPropertyValues(
322-
"spring.security.oauth2.resourceserver.opaquetoken.introspection-uri=https://check-token.com",
323-
"spring.security.oauth2.resourceserver.jwt.issuer-uri=https://jwk-oidc-issuer-location.com")
324-
.run((context) -> assertThat(context).hasFailed().getFailure().hasMessageContaining(
325-
"Only one of jwt.issuer-uri and opaquetoken.introspection-uri should be configured."));
326-
}
327-
328-
@Test
329-
void autoConfigurationWhenBothJwtKeyLocationAndTokenIntrospectionUriSetShouldFail() {
330-
this.contextRunner
331-
.withPropertyValues(
332-
"spring.security.oauth2.resourceserver.opaquetoken.introspection-uri=https://check-token.com",
333-
"spring.security.oauth2.resourceserver.jwt.public-key-location=classpath:public-key-location")
334-
.run((context) -> assertThat(context).hasFailed().getFailure().hasMessageContaining(
335-
"Only one of jwt.public-key-location and opaquetoken.introspection-uri should be configured."));
336-
}
337-
338325
@SuppressWarnings("unchecked")
339326
@Test
340327
void autoConfigurationShouldConfigureResourceServerUsingJwkSetUriAndIssuerUri() throws Exception {

0 commit comments

Comments
 (0)