Skip to content

Commit 2175b77

Browse files
committed
Add JwkSourceJwtDecoderBuilder
- Since JWKSource supercedes the usage of RestOperations and Cache, this commit creates a new builder that only exposes the remaining configuration values PR spring-projectsgh-17046
1 parent f3753a2 commit 2175b77

File tree

2 files changed

+111
-13
lines changed

2 files changed

+111
-13
lines changed

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

Lines changed: 105 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -264,11 +264,12 @@ public static SecretKeyJwtDecoderBuilder withSecretKey(SecretKey secretKey) {
264264
/**
265265
* Use the given <a href="https://tools.ietf.org/html/rfc7517#section-5">JWK Set</a>
266266
* uri.
267-
* @param jwkSetUri the JWK Set uri to use
267+
* @param jwkSource the JWK Set uri to use
268268
* @return a {@link JwkSetUriJwtDecoderBuilder} for further configurations
269+
* @since 7.0
269270
*/
270-
public static JwkSetUriJwtDecoderBuilder withJwkSource(JWKSource<SecurityContext> jwkSetUri) {
271-
return new JwkSetUriJwtDecoderBuilder(jwkSetUri);
271+
public static JwkSourceJwtDecoderBuilder withJwkSource(JWKSource<SecurityContext> jwkSource) {
272+
return new JwkSourceJwtDecoderBuilder(jwkSource);
272273
}
273274

274275
/**
@@ -813,4 +814,105 @@ JWTProcessor<SecurityContext> processor() {
813814

814815
}
815816

817+
/**
818+
* A builder for creating {@link NimbusJwtDecoder} instances based on a
819+
* <a target="_blank" href="https://tools.ietf.org/html/rfc7517#section-5">JWK Set</a>
820+
* uri.
821+
*
822+
* @since 7.0
823+
*/
824+
public static final class JwkSourceJwtDecoderBuilder {
825+
826+
private Set<JWSAlgorithm> defaultAlgorithms = Set.of(JWSAlgorithm.RS256);
827+
828+
private final Set<SignatureAlgorithm> signatureAlgorithms = new HashSet<>();
829+
830+
private final JWKSource<SecurityContext> jwkSource;
831+
832+
private Consumer<ConfigurableJWTProcessor<SecurityContext>> jwtProcessorCustomizer;
833+
834+
private JwkSourceJwtDecoderBuilder(JWKSource<SecurityContext> jwkSource) {
835+
Assert.notNull(jwkSource, "jwkSource cannot be null");
836+
this.jwkSource = jwkSource;
837+
this.jwtProcessorCustomizer = (processor) -> {
838+
};
839+
}
840+
841+
/**
842+
* Append the given signing
843+
* <a href="https://tools.ietf.org/html/rfc7515#section-4.1.1" target=
844+
* "_blank">algorithm</a> to the set of algorithms to use.
845+
* @param signatureAlgorithm the algorithm to use
846+
* @return a {@link JwkSetUriJwtDecoderBuilder} for further configurations
847+
*/
848+
public JwkSourceJwtDecoderBuilder jwsAlgorithm(SignatureAlgorithm signatureAlgorithm) {
849+
Assert.notNull(signatureAlgorithm, "signatureAlgorithm cannot be null");
850+
this.signatureAlgorithms.add(signatureAlgorithm);
851+
return this;
852+
}
853+
854+
/**
855+
* Configure the list of
856+
* <a href="https://tools.ietf.org/html/rfc7515#section-4.1.1" target=
857+
* "_blank">algorithms</a> to use with the given {@link Consumer}.
858+
* @param signatureAlgorithmsConsumer a {@link Consumer} for further configuring
859+
* the algorithm list
860+
* @return a {@link JwkSetUriJwtDecoderBuilder} for further configurations
861+
*/
862+
public JwkSourceJwtDecoderBuilder jwsAlgorithms(Consumer<Set<SignatureAlgorithm>> signatureAlgorithmsConsumer) {
863+
Assert.notNull(signatureAlgorithmsConsumer, "signatureAlgorithmsConsumer cannot be null");
864+
signatureAlgorithmsConsumer.accept(this.signatureAlgorithms);
865+
return this;
866+
}
867+
868+
/**
869+
* Use the given {@link Consumer} to customize the {@link JWTProcessor
870+
* ConfigurableJWTProcessor} before passing it to the build
871+
* {@link NimbusJwtDecoder}.
872+
* @param jwtProcessorCustomizer the callback used to alter the processor
873+
* @return a {@link JwkSetUriJwtDecoderBuilder} for further configurations
874+
* @since 5.4
875+
*/
876+
public JwkSourceJwtDecoderBuilder jwtProcessorCustomizer(
877+
Consumer<ConfigurableJWTProcessor<SecurityContext>> jwtProcessorCustomizer) {
878+
Assert.notNull(jwtProcessorCustomizer, "jwtProcessorCustomizer cannot be null");
879+
this.jwtProcessorCustomizer = jwtProcessorCustomizer;
880+
return this;
881+
}
882+
883+
JWSKeySelector<SecurityContext> jwsKeySelector() {
884+
if (this.signatureAlgorithms.isEmpty()) {
885+
return new JWSVerificationKeySelector<>(this.defaultAlgorithms, this.jwkSource);
886+
}
887+
Set<JWSAlgorithm> jwsAlgorithms = new HashSet<>();
888+
for (SignatureAlgorithm signatureAlgorithm : this.signatureAlgorithms) {
889+
JWSAlgorithm jwsAlgorithm = JWSAlgorithm.parse(signatureAlgorithm.getName());
890+
jwsAlgorithms.add(jwsAlgorithm);
891+
}
892+
return new JWSVerificationKeySelector<>(jwsAlgorithms, this.jwkSource);
893+
}
894+
895+
/**
896+
* Build the configured {@link NimbusJwtDecoder}.
897+
* @return the configured {@link NimbusJwtDecoder}
898+
*/
899+
public NimbusJwtDecoder build() {
900+
return new NimbusJwtDecoder(processor());
901+
}
902+
903+
JWTProcessor<SecurityContext> processor() {
904+
ConfigurableJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>();
905+
// Spring Security validates the claim set independent from Nimbus
906+
jwtProcessor.setJWSTypeVerifier((header, context) -> {
907+
});
908+
jwtProcessor.setJWSKeySelector(jwsKeySelector());
909+
// Spring Security validates the claim set independent from Nimbus
910+
jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> {
911+
});
912+
this.jwtProcessorCustomizer.accept(jwtProcessor);
913+
return jwtProcessor;
914+
}
915+
916+
}
917+
816918
}

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

Lines changed: 6 additions & 10 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.JWK;
4546
import com.nimbusds.jose.jwk.JWKSet;
4647
import com.nimbusds.jose.jwk.source.JWKSource;
4748
import com.nimbusds.jose.proc.BadJOSEException;
@@ -558,20 +559,15 @@ public void decodeWhenUsingSecretKeyWithKidThenStillUsesKey() throws Exception {
558559
// @formatter:on
559560
}
560561

561-
// gh-7056
562562
@Test
563-
public void decodeWhenUsingJwkSource() throws Exception {
564-
JWKSource<SecurityContext> source = (a, b) -> {
565-
try {
566-
return JWKSet.parse(JWK_SET).getKeys();
567-
}
568-
catch (ParseException ex) {
569-
throw new RuntimeException(ex);
570-
}
571-
};
563+
public void withJwkSourceWhenDefaultsThenUses() throws Exception {
564+
List<JWK> jwks = JWKSet.parse(JWK_SET).getKeys();
565+
JWKSource<SecurityContext> source = mock(JWKSource.class);
566+
given(source.get(any(), any())).willReturn(jwks);
572567
NimbusJwtDecoder decoder = NimbusJwtDecoder.withJwkSource(source).build();
573568
Jwt jwt = decoder.decode(SIGNED_JWT);
574569
assertThat(jwt.getClaimAsString("sub")).isEqualTo("test-subject");
570+
verify(source).get(any(), any());
575571
}
576572

577573
// gh-8730

0 commit comments

Comments
 (0)