Skip to content

Commit ada75e7

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

File tree

2 files changed

+122
-0
lines changed

2 files changed

+122
-0
lines changed

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

Lines changed: 112 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,108 @@ public void close() {
535545

536546
}
537547

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

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)