Skip to content

Commit a0eb563

Browse files
committed
Add builder to create NimbusJwtDecoder with JwkSource
Signed-off-by: Mark Bonnekessel <[email protected]>
1 parent 5517d8f commit a0eb563

File tree

2 files changed

+120
-0
lines changed

2 files changed

+120
-0
lines changed

oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,16 @@ public static SecretKeyJwtDecoderBuilder withSecretKey(SecretKey secretKey) {
261261
return new SecretKeyJwtDecoderBuilder(secretKey);
262262
}
263263

264+
/**
265+
* Use the given {@code JWKSource} to create a JwkSourceJwtDecoderBuilder.
266+
* @param jwkSource the JWK Source to use
267+
* @return a {@link JwkSetUriJwtDecoderBuilder} for further configurations
268+
* @since 7.0
269+
*/
270+
public static JwkSourceJwtDecoderBuilder withJwkSource(JWKSource<SecurityContext> jwkSource) {
271+
return new JwkSourceJwtDecoderBuilder(jwkSource);
272+
}
273+
264274
/**
265275
* A builder for creating {@link NimbusJwtDecoder} instances based on a
266276
* <a target="_blank" href="https://tools.ietf.org/html/rfc7517#section-5">JWK Set</a>
@@ -535,6 +545,106 @@ public void close() {
535545

536546
}
537547

548+
/**
549+
* A builder for creating {@link NimbusJwtDecoder} instances based on a {@code JWKSource}.
550+
*/
551+
public static final class JwkSourceJwtDecoderBuilder {
552+
553+
private static final JOSEObjectTypeVerifier<SecurityContext> NO_TYPE_VERIFIER = (header, context) -> {
554+
};
555+
556+
private final Function<JWKSource<SecurityContext>, Set<JWSAlgorithm>> defaultAlgorithms = (source) -> Set
557+
.of(JWSAlgorithm.RS256);
558+
559+
private final JOSEObjectTypeVerifier<SecurityContext> typeVerifier = NO_TYPE_VERIFIER;
560+
561+
private final Set<SignatureAlgorithm> signatureAlgorithms = new HashSet<>();
562+
563+
private Consumer<ConfigurableJWTProcessor<SecurityContext>> jwtProcessorCustomizer;
564+
565+
private final JWKSource<SecurityContext> jwkSource;
566+
567+
private JwkSourceJwtDecoderBuilder (JWKSource<SecurityContext> jwkSource) {
568+
Assert.notNull(jwkSource, "jwkSource cannot be null");
569+
this.jwkSource = jwkSource;
570+
this.jwtProcessorCustomizer = (processor) -> {
571+
};
572+
}
573+
574+
/**
575+
* Append the given signing
576+
* <a href="https://tools.ietf.org/html/rfc7515#section-4.1.1" target=
577+
* "_blank">algorithm</a> to the set of algorithms to use.
578+
* @param signatureAlgorithm the algorithm to use
579+
* @return a {@link JwkSourceJwtDecoderBuilder } for further configurations
580+
*/
581+
public JwkSourceJwtDecoderBuilder jwsAlgorithm(SignatureAlgorithm signatureAlgorithm) {
582+
Assert.notNull(signatureAlgorithm, "signatureAlgorithm cannot be null");
583+
this.signatureAlgorithms.add(signatureAlgorithm);
584+
return this;
585+
}
586+
587+
/**
588+
* Configure the list of
589+
* <a href="https://tools.ietf.org/html/rfc7515#section-4.1.1" target=
590+
* "_blank">algorithms</a> to use with the given {@link Consumer}.
591+
* @param signatureAlgorithmsConsumer a {@link Consumer} for further configuring
592+
* the algorithm list
593+
* @return a {@link JwkSourceJwtDecoderBuilder } for further configurations
594+
*/
595+
public JwkSourceJwtDecoderBuilder jwsAlgorithms(Consumer<Set<SignatureAlgorithm>> signatureAlgorithmsConsumer) {
596+
Assert.notNull(signatureAlgorithmsConsumer, "signatureAlgorithmsConsumer cannot be null");
597+
signatureAlgorithmsConsumer.accept(this.signatureAlgorithms);
598+
return this;
599+
}
600+
601+
/**
602+
* Use the given {@link Consumer} to customize the {@link JWTProcessor
603+
* ConfigurableJWTProcessor} before passing it to the build
604+
* {@link NimbusJwtDecoder}.
605+
* @param jwtProcessorCustomizer the callback used to alter the processor
606+
* @return a {@link JwkSourceJwtDecoderBuilder } for further configurations
607+
* @since 5.4
608+
*/
609+
public JwkSourceJwtDecoderBuilder jwtProcessorCustomizer(
610+
Consumer<ConfigurableJWTProcessor<SecurityContext>> jwtProcessorCustomizer) {
611+
Assert.notNull(jwtProcessorCustomizer, "jwtProcessorCustomizer cannot be null");
612+
this.jwtProcessorCustomizer = jwtProcessorCustomizer;
613+
return this;
614+
}
615+
616+
JWSKeySelector<SecurityContext> jwsKeySelector(JWKSource<SecurityContext> jwkSource) {
617+
if (this.signatureAlgorithms.isEmpty()) {
618+
return new JWSVerificationKeySelector<>(this.defaultAlgorithms.apply(jwkSource), jwkSource);
619+
}
620+
Set<JWSAlgorithm> jwsAlgorithms = new HashSet<>();
621+
for (SignatureAlgorithm signatureAlgorithm : this.signatureAlgorithms) {
622+
JWSAlgorithm jwsAlgorithm = JWSAlgorithm.parse(signatureAlgorithm.getName());
623+
jwsAlgorithms.add(jwsAlgorithm);
624+
}
625+
return new JWSVerificationKeySelector<>(jwsAlgorithms, jwkSource);
626+
}
627+
628+
JWTProcessor<SecurityContext> processor() {
629+
ConfigurableJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>();
630+
jwtProcessor.setJWSTypeVerifier(this.typeVerifier);
631+
jwtProcessor.setJWSKeySelector(jwsKeySelector(jwkSource));
632+
// Spring Security validates the claim set independent from Nimbus
633+
jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> {
634+
});
635+
this.jwtProcessorCustomizer.accept(jwtProcessor);
636+
return jwtProcessor;
637+
}
638+
639+
/**
640+
* Build the configured {@link NimbusJwtDecoder}.
641+
* @return the configured {@link NimbusJwtDecoder}
642+
*/
643+
public NimbusJwtDecoder build() {
644+
return new NimbusJwtDecoder(processor());
645+
}
646+
}
647+
538648
/**
539649
* A builder for creating {@link NimbusJwtDecoder} instances based on a public key.
540650
*/

oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoderTests.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import com.nimbusds.jose.JWSSigner;
4343
import com.nimbusds.jose.crypto.MACSigner;
4444
import com.nimbusds.jose.crypto.RSASSASigner;
45+
import com.nimbusds.jose.jwk.JWKSet;
4546
import com.nimbusds.jose.jwk.source.JWKSource;
4647
import com.nimbusds.jose.proc.BadJOSEException;
4748
import com.nimbusds.jose.proc.DefaultJOSEObjectTypeVerifier;
@@ -557,6 +558,15 @@ public void decodeWhenUsingSecretKeyWithKidThenStillUsesKey() throws Exception {
557558
// @formatter:on
558559
}
559560

561+
@Test
562+
public void withJwkSourceWhenDefaultsThenUsesProvidedJwkSource() throws Exception {
563+
JWKSource<SecurityContext> source = mock(JWKSource.class);
564+
given(source.get(any(), any())).willReturn(JWKSet.parse(JWK_SET).getKeys());
565+
NimbusJwtDecoder decoder = NimbusJwtDecoder.withJwkSource(source).build();
566+
Jwt jwt = decoder.decode(SIGNED_JWT);
567+
assertThat(jwt.getClaimAsString("sub")).isEqualTo("test-subject");
568+
}
569+
560570
// gh-8730
561571
@Test
562572
public void withSecretKeyWhenUsingCustomTypeHeaderThenSuccessfullyDecodes() throws Exception {

0 commit comments

Comments
 (0)