Skip to content

Commit ee65627

Browse files
Mushtaq Ahmedsnicoll
authored andcommitted
Add support for aud claim in resource server
See gh-29084
1 parent 9025d1d commit ee65627

File tree

5 files changed

+158
-4
lines changed

5 files changed

+158
-4
lines changed

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
*
3232
* @author Madhura Bhave
3333
* @author Artsiom Yudovin
34+
* @author Mushtaq Ahmed
3435
* @since 2.1.0
3536
*/
3637
@ConfigurationProperties(prefix = "spring.security.oauth2.resourceserver")
@@ -71,6 +72,11 @@ public static class Jwt {
7172
*/
7273
private Resource publicKeyLocation;
7374

75+
/**
76+
* Identifies the recipients that the JWT is intended for.
77+
*/
78+
private String audience;
79+
7480
public String getJwkSetUri() {
7581
return this.jwkSetUri;
7682
}
@@ -103,6 +109,14 @@ public void setPublicKeyLocation(Resource publicKeyLocation) {
103109
this.publicKeyLocation = publicKeyLocation;
104110
}
105111

112+
public String getAudience() {
113+
return this.audience;
114+
}
115+
116+
public void setAudience(String audience) {
117+
this.audience = audience;
118+
}
119+
106120
public String readPublicKey() throws IOException {
107121
String key = "spring.security.oauth2.resourceserver.public-key-location";
108122
Assert.notNull(this.publicKeyLocation, "PublicKeyLocation must not be null");

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

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
import java.security.KeyFactory;
2020
import java.security.interfaces.RSAPublicKey;
2121
import java.security.spec.X509EncodedKeySpec;
22+
import java.util.ArrayList;
2223
import java.util.Base64;
24+
import java.util.List;
2325

2426
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
2527
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -32,8 +34,14 @@
3234
import org.springframework.context.annotation.Configuration;
3335
import org.springframework.security.config.web.server.ServerHttpSecurity;
3436
import org.springframework.security.config.web.server.ServerHttpSecurity.OAuth2ResourceServerSpec;
37+
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
38+
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
3539
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
36-
import org.springframework.security.oauth2.jwt.JwtValidators;
40+
import org.springframework.security.oauth2.jwt.Jwt;
41+
import org.springframework.security.oauth2.jwt.JwtClaimNames;
42+
import org.springframework.security.oauth2.jwt.JwtClaimValidator;
43+
import org.springframework.security.oauth2.jwt.JwtIssuerValidator;
44+
import org.springframework.security.oauth2.jwt.JwtTimestampValidator;
3745
import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder;
3846
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
3947
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoders;
@@ -49,6 +57,7 @@
4957
* @author Artsiom Yudovin
5058
* @author HaiTao Zhang
5159
* @author Anastasiia Losieva
60+
* @author Mushtaq Ahmed
5261
*/
5362
@Configuration(proxyBeanMethods = false)
5463
class ReactiveOAuth2ResourceServerJwkConfiguration {
@@ -69,10 +78,18 @@ ReactiveJwtDecoder jwtDecoder() {
6978
NimbusReactiveJwtDecoder nimbusReactiveJwtDecoder = NimbusReactiveJwtDecoder
7079
.withJwkSetUri(this.properties.getJwkSetUri())
7180
.jwsAlgorithm(SignatureAlgorithm.from(this.properties.getJwsAlgorithm())).build();
81+
List<OAuth2TokenValidator<Jwt>> validators = new ArrayList<>();
82+
validators.add(new JwtTimestampValidator());
7283
String issuerUri = this.properties.getIssuerUri();
7384
if (issuerUri != null) {
74-
nimbusReactiveJwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(issuerUri));
85+
validators.add(new JwtIssuerValidator(issuerUri));
7586
}
87+
String audience = this.properties.getAudience();
88+
if (audience != null) {
89+
validators.add(new JwtClaimValidator<List<String>>(JwtClaimNames.AUD,
90+
(aud) -> aud != null && aud.contains(audience)));
91+
}
92+
nimbusReactiveJwtDecoder.setJwtValidator(new DelegatingOAuth2TokenValidator<>(validators));
7693
return nimbusReactiveJwtDecoder;
7794
}
7895

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

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
import java.security.KeyFactory;
2020
import java.security.interfaces.RSAPublicKey;
2121
import java.security.spec.X509EncodedKeySpec;
22+
import java.util.ArrayList;
2223
import java.util.Base64;
24+
import java.util.List;
2325

2426
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
2527
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -33,10 +35,16 @@
3335
import org.springframework.context.annotation.Configuration;
3436
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
3537
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
38+
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
39+
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
3640
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
41+
import org.springframework.security.oauth2.jwt.Jwt;
42+
import org.springframework.security.oauth2.jwt.JwtClaimNames;
43+
import org.springframework.security.oauth2.jwt.JwtClaimValidator;
3744
import org.springframework.security.oauth2.jwt.JwtDecoder;
3845
import org.springframework.security.oauth2.jwt.JwtDecoders;
39-
import org.springframework.security.oauth2.jwt.JwtValidators;
46+
import org.springframework.security.oauth2.jwt.JwtIssuerValidator;
47+
import org.springframework.security.oauth2.jwt.JwtTimestampValidator;
4048
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
4149
import org.springframework.security.oauth2.jwt.SupplierJwtDecoder;
4250
import org.springframework.security.web.SecurityFilterChain;
@@ -49,6 +57,7 @@
4957
* @author Madhura Bhave
5058
* @author Artsiom Yudovin
5159
* @author HaiTao Zhang
60+
* @author Mushtaq Ahmed
5261
*/
5362
@Configuration(proxyBeanMethods = false)
5463
class OAuth2ResourceServerJwtConfiguration {
@@ -68,10 +77,18 @@ static class JwtDecoderConfiguration {
6877
JwtDecoder jwtDecoderByJwkKeySetUri() {
6978
NimbusJwtDecoder nimbusJwtDecoder = NimbusJwtDecoder.withJwkSetUri(this.properties.getJwkSetUri())
7079
.jwsAlgorithm(SignatureAlgorithm.from(this.properties.getJwsAlgorithm())).build();
80+
List<OAuth2TokenValidator<Jwt>> validators = new ArrayList<>();
81+
validators.add(new JwtTimestampValidator());
7182
String issuerUri = this.properties.getIssuerUri();
7283
if (issuerUri != null) {
73-
nimbusJwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(issuerUri));
84+
validators.add(new JwtIssuerValidator(issuerUri));
7485
}
86+
String audience = this.properties.getAudience();
87+
if (audience != null) {
88+
validators.add(new JwtClaimValidator<List<String>>(JwtClaimNames.AUD,
89+
(aud) -> aud != null && aud.contains(audience)));
90+
}
91+
nimbusJwtDecoder.setJwtValidator(new DelegatingOAuth2TokenValidator<>(validators));
7592
return nimbusJwtDecoder;
7693
}
7794

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

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@
5050
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
5151
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
5252
import org.springframework.security.oauth2.jwt.Jwt;
53+
import org.springframework.security.oauth2.jwt.JwtClaimValidator;
5354
import org.springframework.security.oauth2.jwt.JwtIssuerValidator;
55+
import org.springframework.security.oauth2.jwt.JwtTimestampValidator;
5456
import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder;
5557
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
5658
import org.springframework.security.oauth2.jwt.SupplierReactiveJwtDecoder;
@@ -74,6 +76,7 @@
7476
* @author Artsiom Yudovin
7577
* @author HaiTao Zhang
7678
* @author Anastasiia Losieva
79+
* @author Mushtaq Ahmed
7780
*/
7881
class ReactiveOAuth2ResourceServerAutoConfigurationTests {
7982

@@ -387,6 +390,56 @@ void autoConfigurationShouldConfigureResourceServerUsingJwkSetUriAndIssuerUri()
387390
});
388391
}
389392

393+
@SuppressWarnings("unchecked")
394+
@Test
395+
void autoConfigurationShouldNotConfigureIssuerUriAndAudienceJwtValidatorIfPropertyNotConfigured() throws Exception {
396+
this.server = new MockWebServer();
397+
this.server.start();
398+
String path = "test";
399+
String issuer = this.server.url(path).toString();
400+
String cleanIssuerPath = cleanIssuerPath(issuer);
401+
setupMockResponse(cleanIssuerPath);
402+
this.contextRunner
403+
.withPropertyValues("spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://jwk-set-uri.com")
404+
.run((context) -> {
405+
assertThat(context).hasSingleBean(ReactiveJwtDecoder.class);
406+
ReactiveJwtDecoder reactiveJwtDecoder = context.getBean(ReactiveJwtDecoder.class);
407+
DelegatingOAuth2TokenValidator<Jwt> jwtValidator = (DelegatingOAuth2TokenValidator<Jwt>) ReflectionTestUtils
408+
.getField(reactiveJwtDecoder, "jwtValidator");
409+
Collection<OAuth2TokenValidator<Jwt>> tokenValidators = (Collection<OAuth2TokenValidator<Jwt>>) ReflectionTestUtils
410+
.getField(jwtValidator, "tokenValidators");
411+
assertThat(tokenValidators).hasExactlyElementsOfTypes(JwtTimestampValidator.class);
412+
assertThat(tokenValidators).doesNotHaveAnyElementsOfTypes(JwtClaimValidator.class);
413+
assertThat(tokenValidators).doesNotHaveAnyElementsOfTypes(JwtIssuerValidator.class);
414+
});
415+
}
416+
417+
@SuppressWarnings("unchecked")
418+
@Test
419+
void autoConfigurationShouldConfigureIssuerAndAudienceJwtValidatorIfPropertyProvided() throws Exception {
420+
this.server = new MockWebServer();
421+
this.server.start();
422+
String path = "test";
423+
String issuer = this.server.url(path).toString();
424+
String cleanIssuerPath = cleanIssuerPath(issuer);
425+
setupMockResponse(cleanIssuerPath);
426+
this.contextRunner
427+
.withPropertyValues("spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://jwk-set-uri.com",
428+
"spring.security.oauth2.resourceserver.jwt.issuer-uri=http://" + this.server.getHostName() + ":"
429+
+ this.server.getPort() + "/" + path,
430+
"spring.security.oauth2.resourceserver.jwt.audience=http://test-audience.com")
431+
.run((context) -> {
432+
assertThat(context).hasSingleBean(ReactiveJwtDecoder.class);
433+
ReactiveJwtDecoder reactiveJwtDecoder = context.getBean(ReactiveJwtDecoder.class);
434+
DelegatingOAuth2TokenValidator<Jwt> jwtValidator = (DelegatingOAuth2TokenValidator<Jwt>) ReflectionTestUtils
435+
.getField(reactiveJwtDecoder, "jwtValidator");
436+
Collection<OAuth2TokenValidator<Jwt>> tokenValidators = (Collection<OAuth2TokenValidator<Jwt>>) ReflectionTestUtils
437+
.getField(jwtValidator, "tokenValidators");
438+
assertThat(tokenValidators).hasAtLeastOneElementOfType(JwtIssuerValidator.class);
439+
assertThat(tokenValidators).hasAtLeastOneElementOfType(JwtClaimValidator.class);
440+
});
441+
}
442+
390443
private void assertFilterConfiguredWithJwtAuthenticationManager(AssertableReactiveWebApplicationContext context) {
391444
MatcherSecurityWebFilterChain filterChain = (MatcherSecurityWebFilterChain) context
392445
.getBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN);

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

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,10 @@
4848
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
4949
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
5050
import org.springframework.security.oauth2.jwt.Jwt;
51+
import org.springframework.security.oauth2.jwt.JwtClaimValidator;
5152
import org.springframework.security.oauth2.jwt.JwtDecoder;
5253
import org.springframework.security.oauth2.jwt.JwtIssuerValidator;
54+
import org.springframework.security.oauth2.jwt.JwtTimestampValidator;
5355
import org.springframework.security.oauth2.jwt.SupplierJwtDecoder;
5456
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
5557
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
@@ -68,6 +70,7 @@
6870
* @author Madhura Bhave
6971
* @author Artsiom Yudovin
7072
* @author HaiTao Zhang
73+
* @author Mushtaq Ahmed
7174
*/
7275
class OAuth2ResourceServerAutoConfigurationTests {
7376

@@ -404,6 +407,56 @@ void autoConfigurationShouldConfigureResourceServerUsingJwkSetUriAndIssuerUri()
404407
});
405408
}
406409

410+
@SuppressWarnings("unchecked")
411+
@Test
412+
void autoConfigurationShouldNotConfigureIssuerUriAndAudienceJwtValidatorIfPropertyNotConfigured() throws Exception {
413+
this.server = new MockWebServer();
414+
this.server.start();
415+
String path = "test";
416+
String issuer = this.server.url(path).toString();
417+
String cleanIssuerPath = cleanIssuerPath(issuer);
418+
setupMockResponse(cleanIssuerPath);
419+
this.contextRunner
420+
.withPropertyValues("spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://jwk-set-uri.com")
421+
.run((context) -> {
422+
assertThat(context).hasSingleBean(JwtDecoder.class);
423+
JwtDecoder jwtDecoder = context.getBean(JwtDecoder.class);
424+
DelegatingOAuth2TokenValidator<Jwt> jwtValidator = (DelegatingOAuth2TokenValidator<Jwt>) ReflectionTestUtils
425+
.getField(jwtDecoder, "jwtValidator");
426+
Collection<OAuth2TokenValidator<Jwt>> tokenValidators = (Collection<OAuth2TokenValidator<Jwt>>) ReflectionTestUtils
427+
.getField(jwtValidator, "tokenValidators");
428+
assertThat(tokenValidators).hasExactlyElementsOfTypes(JwtTimestampValidator.class);
429+
assertThat(tokenValidators).doesNotHaveAnyElementsOfTypes(JwtClaimValidator.class);
430+
assertThat(tokenValidators).doesNotHaveAnyElementsOfTypes(JwtIssuerValidator.class);
431+
});
432+
}
433+
434+
@SuppressWarnings("unchecked")
435+
@Test
436+
void autoConfigurationShouldConfigureAudienceAndIssuerJwtValidatorIfPropertyProvided() throws Exception {
437+
this.server = new MockWebServer();
438+
this.server.start();
439+
String path = "test";
440+
String issuer = this.server.url(path).toString();
441+
String cleanIssuerPath = cleanIssuerPath(issuer);
442+
setupMockResponse(cleanIssuerPath);
443+
this.contextRunner
444+
.withPropertyValues("spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://jwk-set-uri.com",
445+
"spring.security.oauth2.resourceserver.jwt.issuer-uri=http://" + this.server.getHostName() + ":"
446+
+ this.server.getPort() + "/" + path,
447+
"spring.security.oauth2.resourceserver.jwt.audience=http://test-audience.com")
448+
.run((context) -> {
449+
assertThat(context).hasSingleBean(JwtDecoder.class);
450+
JwtDecoder jwtDecoder = context.getBean(JwtDecoder.class);
451+
DelegatingOAuth2TokenValidator<Jwt> jwtValidator = (DelegatingOAuth2TokenValidator<Jwt>) ReflectionTestUtils
452+
.getField(jwtDecoder, "jwtValidator");
453+
Collection<OAuth2TokenValidator<Jwt>> tokenValidators = (Collection<OAuth2TokenValidator<Jwt>>) ReflectionTestUtils
454+
.getField(jwtValidator, "tokenValidators");
455+
assertThat(tokenValidators).hasAtLeastOneElementOfType(JwtIssuerValidator.class);
456+
assertThat(tokenValidators).hasAtLeastOneElementOfType(JwtClaimValidator.class);
457+
});
458+
}
459+
407460
@Test
408461
void jwtSecurityConfigurerBacksOffWhenSecurityFilterChainBeanIsPresent() {
409462
this.contextRunner

0 commit comments

Comments
 (0)