diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java index eb5efbabecb..eacb6664d32 100644 --- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java +++ b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java @@ -261,6 +261,16 @@ public static SecretKeyJwtDecoderBuilder withSecretKey(SecretKey secretKey) { return new SecretKeyJwtDecoderBuilder(secretKey); } + /** + * Use the given JWK Set + * uri. + * @param jwkSetUri the JWK Set uri to use + * @return a {@link JwkSetUriJwtDecoderBuilder} for further configurations + */ + public static JwkSetUriJwtDecoderBuilder withJwkSource(JWKSource jwkSetUri) { + return new JwkSetUriJwtDecoderBuilder(jwkSetUri); + } + /** * A builder for creating {@link NimbusJwtDecoder} instances based on a * JWK Set @@ -274,7 +284,7 @@ public static final class JwkSetUriJwtDecoderBuilder { private static final JOSEObjectTypeVerifier NO_TYPE_VERIFIER = (header, context) -> { }; - private final Function jwkSetUri; + private Function jwkSetUri; private Function, Set> defaultAlgorithms = (source) -> Set .of(JWSAlgorithm.RS256); @@ -289,6 +299,8 @@ public static final class JwkSetUriJwtDecoderBuilder { private Consumer> jwtProcessorCustomizer; + private JWKSource jwkSource; + private JwkSetUriJwtDecoderBuilder(String jwkSetUri) { Assert.hasText(jwkSetUri, "jwkSetUri cannot be empty"); this.jwkSetUri = (rest) -> jwkSetUri; @@ -306,6 +318,13 @@ private JwkSetUriJwtDecoderBuilder(Function jwkSetUri, }; } + private JwkSetUriJwtDecoderBuilder(JWKSource jwkSource) { + Assert.notNull(jwkSource, "jwkSource cannot be null"); + this.jwkSource = jwkSource; + this.jwtProcessorCustomizer = (processor) -> { + }; + } + /** * Whether to use Nimbus's typ header verification. This is {@code true} by * default, however it may change to {@code false} in a future major release. @@ -436,6 +455,9 @@ JWSKeySelector jwsKeySelector(JWKSource jwkSou } JWKSource jwkSource() { + if (this.jwkSource != null) { + return this.jwkSource; + } String jwkSetUri = this.jwkSetUri.apply(this.restOperations); return JWKSourceBuilder.create(new SpringJWKSource<>(this.restOperations, this.cache, jwkSetUri)) .refreshAheadCache(false) diff --git a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoderTests.java b/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoderTests.java index 9b7805a0d89..f0ba7196de4 100644 --- a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoderTests.java +++ b/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoderTests.java @@ -42,6 +42,7 @@ import com.nimbusds.jose.JWSSigner; import com.nimbusds.jose.crypto.MACSigner; import com.nimbusds.jose.crypto.RSASSASigner; +import com.nimbusds.jose.jwk.JWKSet; import com.nimbusds.jose.jwk.source.JWKSource; import com.nimbusds.jose.proc.BadJOSEException; import com.nimbusds.jose.proc.DefaultJOSEObjectTypeVerifier; @@ -559,6 +560,22 @@ public void decodeWhenUsingSecretKeyWithKidThenStillUsesKey() throws Exception { // @formatter:on } + // gh-7056 + @Test + public void decodeWhenUsingJwkSource() throws Exception { + JWKSource source = (a, b) -> { + try { + return JWKSet.parse(JWK_SET).getKeys(); + } + catch (ParseException ex) { + throw new RuntimeException(ex); + } + }; + NimbusJwtDecoder decoder = NimbusJwtDecoder.withJwkSource(source).build(); + Jwt jwt = decoder.decode(SIGNED_JWT); + assertThat(jwt.getClaimAsString("sub")).isEqualTo("test-subject"); + } + // gh-8730 @Test public void withSecretKeyWhenUsingCustomTypeHeaderThenSuccessfullyDecodes() throws Exception {