1
0
mirror of synced 2026-05-22 21:33:16 +00:00

Merge branch '6.5.x' into 7.0.x

This commit is contained in:
Josh Cummings
2026-04-15 18:24:59 -06:00
4 changed files with 63 additions and 7 deletions
@@ -232,7 +232,8 @@ public final class NimbusJwtDecoder implements JwtDecoder {
.getConfigurationForIssuerLocation(issuer, rest); .getConfigurationForIssuerLocation(issuer, rest);
JwtDecoderProviderConfigurationUtils.validateIssuer(configuration, issuer); JwtDecoderProviderConfigurationUtils.validateIssuer(configuration, issuer);
return configuration.get("jwks_uri").toString(); return configuration.get("jwks_uri").toString();
}, JwtDecoderProviderConfigurationUtils::getJWSAlgorithms); }, JwtDecoderProviderConfigurationUtils::getJWSAlgorithms)
.validator(JwtValidators.createDefaultWithIssuer(issuer));
} }
/** /**
@@ -301,6 +302,8 @@ public final class NimbusJwtDecoder implements JwtDecoder {
private Consumer<ConfigurableJWTProcessor<SecurityContext>> jwtProcessorCustomizer; private Consumer<ConfigurableJWTProcessor<SecurityContext>> jwtProcessorCustomizer;
private OAuth2TokenValidator<Jwt> validator = JwtValidators.createDefault();
private JwkSetUriJwtDecoderBuilder(String jwkSetUri) { private JwkSetUriJwtDecoderBuilder(String jwkSetUri) {
Assert.hasText(jwkSetUri, "jwkSetUri cannot be empty"); Assert.hasText(jwkSetUri, "jwkSetUri cannot be empty");
this.jwkSetUri = (rest) -> jwkSetUri; this.jwkSetUri = (rest) -> jwkSetUri;
@@ -441,6 +444,12 @@ public final class NimbusJwtDecoder implements JwtDecoder {
return this; return this;
} }
JwkSetUriJwtDecoderBuilder validator(OAuth2TokenValidator<Jwt> validator) {
Assert.notNull(validator, "validator cannot be null");
this.validator = validator;
return this;
}
JWSKeySelector<SecurityContext> jwsKeySelector(JWKSource<SecurityContext> jwkSource) { JWSKeySelector<SecurityContext> jwsKeySelector(JWKSource<SecurityContext> jwkSource) {
if (this.signatureAlgorithms.isEmpty()) { if (this.signatureAlgorithms.isEmpty()) {
return new JWSVerificationKeySelector<>(this.defaultAlgorithms.apply(jwkSource), jwkSource); return new JWSVerificationKeySelector<>(this.defaultAlgorithms.apply(jwkSource), jwkSource);
@@ -479,7 +488,9 @@ public final class NimbusJwtDecoder implements JwtDecoder {
* @return the configured {@link NimbusJwtDecoder} * @return the configured {@link NimbusJwtDecoder}
*/ */
public NimbusJwtDecoder build() { public NimbusJwtDecoder build() {
return new NimbusJwtDecoder(processor()); NimbusJwtDecoder decoder = new NimbusJwtDecoder(processor());
decoder.setJwtValidator(this.validator);
return decoder;
} }
private static final class SpringJWKSource<C extends SecurityContext> implements JWKSetSource<C> { private static final class SpringJWKSource<C extends SecurityContext> implements JWKSetSource<C> {
@@ -241,7 +241,8 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
} }
return Mono.just(configuration.get("jwks_uri").toString()); return Mono.just(configuration.get("jwks_uri").toString());
}), }),
ReactiveJwtDecoderProviderConfigurationUtils::getJWSAlgorithms); ReactiveJwtDecoderProviderConfigurationUtils::getJWSAlgorithms)
.validator(JwtValidators.createDefaultWithIssuer(issuer));
} }
/** /**
@@ -332,6 +333,8 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
private BiFunction<ReactiveRemoteJWKSource, ConfigurableJWTProcessor<JWKSecurityContext>, Mono<ConfigurableJWTProcessor<JWKSecurityContext>>> jwtProcessorCustomizer; private BiFunction<ReactiveRemoteJWKSource, ConfigurableJWTProcessor<JWKSecurityContext>, Mono<ConfigurableJWTProcessor<JWKSecurityContext>>> jwtProcessorCustomizer;
private OAuth2TokenValidator<Jwt> validator = JwtValidators.createDefault();
private JwkSetUriReactiveJwtDecoderBuilder(String jwkSetUri) { private JwkSetUriReactiveJwtDecoderBuilder(String jwkSetUri) {
Assert.hasText(jwkSetUri, "jwkSetUri cannot be empty"); Assert.hasText(jwkSetUri, "jwkSetUri cannot be empty");
this.jwkSetUri = (web) -> Mono.just(jwkSetUri); this.jwkSetUri = (web) -> Mono.just(jwkSetUri);
@@ -456,6 +459,11 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
return this; return this;
} }
JwkSetUriReactiveJwtDecoderBuilder validator(OAuth2TokenValidator<Jwt> validator) {
this.validator = validator;
return this;
}
JwkSetUriReactiveJwtDecoderBuilder jwtProcessorCustomizer( JwkSetUriReactiveJwtDecoderBuilder jwtProcessorCustomizer(
BiFunction<ReactiveRemoteJWKSource, ConfigurableJWTProcessor<JWKSecurityContext>, Mono<ConfigurableJWTProcessor<JWKSecurityContext>>> jwtProcessorCustomizer) { BiFunction<ReactiveRemoteJWKSource, ConfigurableJWTProcessor<JWKSecurityContext>, Mono<ConfigurableJWTProcessor<JWKSecurityContext>>> jwtProcessorCustomizer) {
Assert.notNull(jwtProcessorCustomizer, "jwtProcessorCustomizer cannot be null"); Assert.notNull(jwtProcessorCustomizer, "jwtProcessorCustomizer cannot be null");
@@ -468,7 +476,9 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
* @return the configured {@link NimbusReactiveJwtDecoder} * @return the configured {@link NimbusReactiveJwtDecoder}
*/ */
public NimbusReactiveJwtDecoder build() { public NimbusReactiveJwtDecoder build() {
return new NimbusReactiveJwtDecoder(processor()); NimbusReactiveJwtDecoder decoder = new NimbusReactiveJwtDecoder(processor());
decoder.setJwtValidator(this.validator);
return decoder;
} }
Mono<JWSKeySelector<JWKSecurityContext>> jwsKeySelector(ReactiveRemoteJWKSource source) { Mono<JWSKeySelector<JWKSecurityContext>> jwsKeySelector(ReactiveRemoteJWKSource source) {
@@ -332,7 +332,10 @@ public class NimbusJwtDecoderTests {
.willReturn(new ResponseEntity<>(Map.of("issuer", issuer, "jwks_uri", issuer + "/jwks"), HttpStatus.OK)); .willReturn(new ResponseEntity<>(Map.of("issuer", issuer, "jwks_uri", issuer + "/jwks"), HttpStatus.OK));
given(restOperations.exchange(any(RequestEntity.class), eq(String.class))) given(restOperations.exchange(any(RequestEntity.class), eq(String.class)))
.willReturn(new ResponseEntity<>(JWK_SET, HttpStatus.OK)); .willReturn(new ResponseEntity<>(JWK_SET, HttpStatus.OK));
JwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuer).restOperations(restOperations).build(); NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuer)
.restOperations(restOperations)
.build();
jwtDecoder.setJwtValidator(JwtValidators.createDefault());
Jwt jwt = jwtDecoder.decode(SIGNED_JWT); Jwt jwt = jwtDecoder.decode(SIGNED_JWT);
assertThat(jwt.hasClaim(JwtClaimNames.EXP)).isNotNull(); assertThat(jwt.hasClaim(JwtClaimNames.EXP)).isNotNull();
} }
@@ -350,6 +353,18 @@ public class NimbusJwtDecoderTests {
assertThat(jwt.hasClaim(JwtClaimNames.EXP)).isNotNull(); assertThat(jwt.hasClaim(JwtClaimNames.EXP)).isNotNull();
} }
@Test
public void decodeWhenIssuerLocationThenRejectsMismatchingIssuers() {
String issuer = "https://example.org/wrong-issuer";
RestOperations restOperations = mock(RestOperations.class);
given(restOperations.exchange(any(RequestEntity.class), any(ParameterizedTypeReference.class)))
.willReturn(new ResponseEntity<>(Map.of("issuer", issuer, "jwks_uri", issuer + "/jwks"), HttpStatus.OK));
given(restOperations.exchange(any(RequestEntity.class), eq(String.class)))
.willReturn(new ResponseEntity<>(JWK_SET, HttpStatus.OK));
JwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuer).restOperations(restOperations).build();
assertThatExceptionOfType(JwtValidationException.class).isThrownBy(() -> jwtDecoder.decode(SIGNED_JWT));
}
@Test @Test
public void withJwkSetUriWhenNullOrEmptyThenThrowsException() { public void withJwkSetUriWhenNullOrEmptyThenThrowsException() {
// @formatter:off // @formatter:off
@@ -617,11 +617,31 @@ public class NimbusReactiveJwtDecoderTests {
given(responseSpec.bodyToMono(any(ParameterizedTypeReference.class))) given(responseSpec.bodyToMono(any(ParameterizedTypeReference.class)))
.willReturn(Mono.just(Map.of("issuer", issuer, "jwks_uri", issuer + "/jwks"))); .willReturn(Mono.just(Map.of("issuer", issuer, "jwks_uri", issuer + "/jwks")));
given(spec.retrieve()).willReturn(responseSpec); given(spec.retrieve()).willReturn(responseSpec);
NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withIssuerLocation(issuer)
.webClient(webClient)
.build();
jwtDecoder.setJwtValidator(JwtValidators.createDefault());
Jwt jwt = jwtDecoder.decode(this.messageReadToken).block();
assertThat(jwt.hasClaim(JwtClaimNames.EXP)).isNotNull();
}
@Test
public void decodeWhenIssuerLocationThenRejectsMismatchingIssuers() {
String issuer = "https://example.org/wrong-issuer";
WebClient real = WebClient.builder().build();
WebClient.RequestHeadersUriSpec spec = spy(real.get());
WebClient webClient = spy(WebClient.class);
given(webClient.get()).willReturn(spec);
WebClient.ResponseSpec responseSpec = mock(WebClient.ResponseSpec.class);
given(responseSpec.bodyToMono(String.class)).willReturn(Mono.just(this.jwkSet));
given(responseSpec.bodyToMono(any(ParameterizedTypeReference.class)))
.willReturn(Mono.just(Map.of("issuer", issuer, "jwks_uri", issuer + "/jwks")));
given(spec.retrieve()).willReturn(responseSpec);
ReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withIssuerLocation(issuer) ReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withIssuerLocation(issuer)
.webClient(webClient) .webClient(webClient)
.build(); .build();
Jwt jwt = jwtDecoder.decode(this.messageReadToken).block(); assertThatExceptionOfType(JwtValidationException.class)
assertThat(jwt.hasClaim(JwtClaimNames.EXP)).isNotNull(); .isThrownBy(() -> jwtDecoder.decode(this.messageReadToken).block());
} }
@Test @Test