Skip to content

Add possibility to customize JwkSource of NimbusJwtDecoder #17046

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,16 @@ public static SecretKeyJwtDecoderBuilder withSecretKey(SecretKey secretKey) {
return new SecretKeyJwtDecoderBuilder(secretKey);
}

/**
* Use the given {@code JWKSource} to create a JwkSourceJwtDecoderBuilder.
* @param jwkSource the JWK Source to use
* @return a {@link JwkSetUriJwtDecoderBuilder} for further configurations
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add @since 7.0

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it be possible to backport this PR to the 6.5er branch?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, we cannot backport it to 6.x as we only add features in minor branches. Since there is no 6.6 planned, 7.0 is the next available release.

* @since 7.0
*/
public static JwkSourceJwtDecoderBuilder withJwkSource(JWKSource<SecurityContext> jwkSource) {
return new JwkSourceJwtDecoderBuilder(jwkSource);
}

/**
* A builder for creating {@link NimbusJwtDecoder} instances based on a
* <a target="_blank" href="https://tools.ietf.org/html/rfc7517#section-5">JWK Set</a>
Expand Down Expand Up @@ -535,6 +545,108 @@ public void close() {

}

/**
* A builder for creating {@link NimbusJwtDecoder} instances based on a
* {@code JWKSource}.
*/
public static final class JwkSourceJwtDecoderBuilder {

private static final JOSEObjectTypeVerifier<SecurityContext> NO_TYPE_VERIFIER = (header, context) -> {
};

private final Function<JWKSource<SecurityContext>, Set<JWSAlgorithm>> defaultAlgorithms = (source) -> Set
.of(JWSAlgorithm.RS256);

private final JOSEObjectTypeVerifier<SecurityContext> typeVerifier = NO_TYPE_VERIFIER;

private final Set<SignatureAlgorithm> signatureAlgorithms = new HashSet<>();

private Consumer<ConfigurableJWTProcessor<SecurityContext>> jwtProcessorCustomizer;

private final JWKSource<SecurityContext> jwkSource;

private JwkSourceJwtDecoderBuilder(JWKSource<SecurityContext> jwkSource) {
Assert.notNull(jwkSource, "jwkSource cannot be null");
this.jwkSource = jwkSource;
this.jwtProcessorCustomizer = (processor) -> {
};
}

/**
* Append the given signing
* <a href="https://tools.ietf.org/html/rfc7515#section-4.1.1" target=
* "_blank">algorithm</a> to the set of algorithms to use.
* @param signatureAlgorithm the algorithm to use
* @return a {@link JwkSourceJwtDecoderBuilder } for further configurations
*/
public JwkSourceJwtDecoderBuilder jwsAlgorithm(SignatureAlgorithm signatureAlgorithm) {
Assert.notNull(signatureAlgorithm, "signatureAlgorithm cannot be null");
this.signatureAlgorithms.add(signatureAlgorithm);
return this;
}

/**
* Configure the list of
* <a href="https://tools.ietf.org/html/rfc7515#section-4.1.1" target=
* "_blank">algorithms</a> to use with the given {@link Consumer}.
* @param signatureAlgorithmsConsumer a {@link Consumer} for further configuring
* the algorithm list
* @return a {@link JwkSourceJwtDecoderBuilder } for further configurations
*/
public JwkSourceJwtDecoderBuilder jwsAlgorithms(Consumer<Set<SignatureAlgorithm>> signatureAlgorithmsConsumer) {
Assert.notNull(signatureAlgorithmsConsumer, "signatureAlgorithmsConsumer cannot be null");
signatureAlgorithmsConsumer.accept(this.signatureAlgorithms);
return this;
}

/**
* Use the given {@link Consumer} to customize the {@link JWTProcessor
* ConfigurableJWTProcessor} before passing it to the build
* {@link NimbusJwtDecoder}.
* @param jwtProcessorCustomizer the callback used to alter the processor
* @return a {@link JwkSourceJwtDecoderBuilder } for further configurations
* @since 5.4
*/
public JwkSourceJwtDecoderBuilder jwtProcessorCustomizer(
Consumer<ConfigurableJWTProcessor<SecurityContext>> jwtProcessorCustomizer) {
Assert.notNull(jwtProcessorCustomizer, "jwtProcessorCustomizer cannot be null");
this.jwtProcessorCustomizer = jwtProcessorCustomizer;
return this;
}

JWSKeySelector<SecurityContext> jwsKeySelector(JWKSource<SecurityContext> jwkSource) {
if (this.signatureAlgorithms.isEmpty()) {
return new JWSVerificationKeySelector<>(this.defaultAlgorithms.apply(jwkSource), jwkSource);
}
Set<JWSAlgorithm> jwsAlgorithms = new HashSet<>();
for (SignatureAlgorithm signatureAlgorithm : this.signatureAlgorithms) {
JWSAlgorithm jwsAlgorithm = JWSAlgorithm.parse(signatureAlgorithm.getName());
jwsAlgorithms.add(jwsAlgorithm);
}
return new JWSVerificationKeySelector<>(jwsAlgorithms, jwkSource);
}

JWTProcessor<SecurityContext> processor() {
ConfigurableJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>();
jwtProcessor.setJWSTypeVerifier(this.typeVerifier);
jwtProcessor.setJWSKeySelector(jwsKeySelector(this.jwkSource));
// Spring Security validates the claim set independent from Nimbus
jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> {
});
this.jwtProcessorCustomizer.accept(jwtProcessor);
return jwtProcessor;
}

/**
* Build the configured {@link NimbusJwtDecoder}.
* @return the configured {@link NimbusJwtDecoder}
*/
public NimbusJwtDecoder build() {
return new NimbusJwtDecoder(processor());
}

}

/**
* A builder for creating {@link NimbusJwtDecoder} instances based on a public key.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -557,6 +558,15 @@ public void decodeWhenUsingSecretKeyWithKidThenStillUsesKey() throws Exception {
// @formatter:on
}

@Test
public void withJwkSourceWhenDefaultsThenUsesProvidedJwkSource() throws Exception {
JWKSource<SecurityContext> source = mock(JWKSource.class);
given(source.get(any(), any())).willReturn(JWKSet.parse(JWK_SET).getKeys());
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 {
Expand Down