Skip to content

Commit d31fff1

Browse files
jrehwaldtjzheaux
authored andcommitted
Add Post-Processor for JWTProcessor Configuration
Extends all existing builders in NimbusJwtDecoder and NimbusReactiveJwtDecoder with a post-processor hook to apply changes on the JWTProcessor used for token verification. Test cases added show how this is used to configure the JWTProcessor to allow additional JWT typ headers. Closes gh-8730
1 parent 3c2a97e commit d31fff1

File tree

4 files changed

+280
-0
lines changed

4 files changed

+280
-0
lines changed

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

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,10 +219,12 @@ public static final class JwkSetUriJwtDecoderBuilder {
219219
private Set<SignatureAlgorithm> signatureAlgorithms = new HashSet<>();
220220
private RestOperations restOperations = new RestTemplate();
221221
private Cache cache;
222+
private Consumer<ConfigurableJWTProcessor<SecurityContext>> jwtProcessorCustomizer;
222223

223224
private JwkSetUriJwtDecoderBuilder(String jwkSetUri) {
224225
Assert.hasText(jwkSetUri, "jwkSetUri cannot be empty");
225226
this.jwkSetUri = jwkSetUri;
227+
this.jwtProcessorCustomizer = (processor) -> {};
226228
}
227229

228230
/**
@@ -282,6 +284,20 @@ public JwkSetUriJwtDecoderBuilder cache(Cache cache) {
282284
return this;
283285
}
284286

287+
/**
288+
* Use the given {@link Consumer} to customize the {@link JWTProcessor ConfigurableJWTProcessor} before
289+
* passing it to the build {@link NimbusJwtDecoder}.
290+
*
291+
* @param jwtProcessorCustomizer the callback used to alter the processor
292+
* @return a {@link JwkSetUriJwtDecoderBuilder} for further configurations
293+
* @since 5.4
294+
*/
295+
public JwkSetUriJwtDecoderBuilder jwtProcessorCustomizer(Consumer<ConfigurableJWTProcessor<SecurityContext>> jwtProcessorCustomizer) {
296+
Assert.notNull(jwtProcessorCustomizer, "jwtProcessorCustomizer cannot be null");
297+
this.jwtProcessorCustomizer = jwtProcessorCustomizer;
298+
return this;
299+
}
300+
285301
JWSKeySelector<SecurityContext> jwsKeySelector(JWKSource<SecurityContext> jwkSource) {
286302
if (this.signatureAlgorithms.isEmpty()) {
287303
return new JWSVerificationKeySelector<>(JWSAlgorithm.RS256, jwkSource);
@@ -312,6 +328,8 @@ JWTProcessor<SecurityContext> processor() {
312328
// Spring Security validates the claim set independent from Nimbus
313329
jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> { });
314330

331+
this.jwtProcessorCustomizer.accept(jwtProcessor);
332+
315333
return jwtProcessor;
316334
}
317335

@@ -414,11 +432,13 @@ public Resource retrieveResource(URL url) throws IOException {
414432
public static final class PublicKeyJwtDecoderBuilder {
415433
private JWSAlgorithm jwsAlgorithm;
416434
private RSAPublicKey key;
435+
private Consumer<ConfigurableJWTProcessor<SecurityContext>> jwtProcessorCustomizer;
417436

418437
private PublicKeyJwtDecoderBuilder(RSAPublicKey key) {
419438
Assert.notNull(key, "key cannot be null");
420439
this.jwsAlgorithm = JWSAlgorithm.RS256;
421440
this.key = key;
441+
this.jwtProcessorCustomizer = (processor) -> {};
422442
}
423443

424444
/**
@@ -437,6 +457,20 @@ public PublicKeyJwtDecoderBuilder signatureAlgorithm(SignatureAlgorithm signatur
437457
return this;
438458
}
439459

460+
/**
461+
* Use the given {@link Consumer} to customize the {@link JWTProcessor ConfigurableJWTProcessor} before
462+
* passing it to the build {@link NimbusJwtDecoder}.
463+
*
464+
* @param jwtProcessorCustomizer the callback used to alter the processor
465+
* @return a {@link PublicKeyJwtDecoderBuilder} for further configurations
466+
* @since 5.4
467+
*/
468+
public PublicKeyJwtDecoderBuilder jwtProcessorCustomizer(Consumer<ConfigurableJWTProcessor<SecurityContext>> jwtProcessorCustomizer) {
469+
Assert.notNull(jwtProcessorCustomizer, "jwtProcessorCustomizer cannot be null");
470+
this.jwtProcessorCustomizer = jwtProcessorCustomizer;
471+
return this;
472+
}
473+
440474
JWTProcessor<SecurityContext> processor() {
441475
if (!JWSAlgorithm.Family.RSA.contains(this.jwsAlgorithm)) {
442476
throw new IllegalStateException("The provided key is of type RSA; " +
@@ -452,6 +486,8 @@ JWTProcessor<SecurityContext> processor() {
452486
// Spring Security validates the claim set independent from Nimbus
453487
jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> { });
454488

489+
this.jwtProcessorCustomizer.accept(jwtProcessor);
490+
455491
return jwtProcessor;
456492
}
457493

@@ -471,10 +507,12 @@ public NimbusJwtDecoder build() {
471507
public static final class SecretKeyJwtDecoderBuilder {
472508
private final SecretKey secretKey;
473509
private JWSAlgorithm jwsAlgorithm = JWSAlgorithm.HS256;
510+
private Consumer<ConfigurableJWTProcessor<SecurityContext>> jwtProcessorCustomizer;
474511

475512
private SecretKeyJwtDecoderBuilder(SecretKey secretKey) {
476513
Assert.notNull(secretKey, "secretKey cannot be null");
477514
this.secretKey = secretKey;
515+
this.jwtProcessorCustomizer = (processor) -> {};
478516
}
479517

480518
/**
@@ -494,6 +532,20 @@ public SecretKeyJwtDecoderBuilder macAlgorithm(MacAlgorithm macAlgorithm) {
494532
return this;
495533
}
496534

535+
/**
536+
* Use the given {@link Consumer} to customize the {@link JWTProcessor ConfigurableJWTProcessor} before
537+
* passing it to the build {@link NimbusJwtDecoder}.
538+
*
539+
* @param jwtProcessorCustomizer the callback used to alter the processor
540+
* @return a {@link SecretKeyJwtDecoderBuilder} for further configurations
541+
* @since 5.4
542+
*/
543+
public SecretKeyJwtDecoderBuilder jwtProcessorCustomizer(Consumer<ConfigurableJWTProcessor<SecurityContext>> jwtProcessorCustomizer) {
544+
Assert.notNull(jwtProcessorCustomizer, "jwtProcessorCustomizer cannot be null");
545+
this.jwtProcessorCustomizer = jwtProcessorCustomizer;
546+
return this;
547+
}
548+
497549
/**
498550
* Build the configured {@link NimbusJwtDecoder}.
499551
*
@@ -512,6 +564,8 @@ JWTProcessor<SecurityContext> processor() {
512564
// Spring Security validates the claim set independent from Nimbus
513565
jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> { });
514566

567+
this.jwtProcessorCustomizer.accept(jwtProcessor);
568+
515569
return jwtProcessor;
516570
}
517571
}

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

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import com.nimbusds.jwt.JWTParser;
4545
import com.nimbusds.jwt.PlainJWT;
4646
import com.nimbusds.jwt.SignedJWT;
47+
import com.nimbusds.jwt.proc.ConfigurableJWTProcessor;
4748
import com.nimbusds.jwt.proc.DefaultJWTProcessor;
4849
import com.nimbusds.jwt.proc.JWTProcessor;
4950
import reactor.core.publisher.Flux;
@@ -245,10 +246,12 @@ public static final class JwkSetUriReactiveJwtDecoderBuilder {
245246
private final String jwkSetUri;
246247
private Set<SignatureAlgorithm> signatureAlgorithms = new HashSet<>();
247248
private WebClient webClient = WebClient.create();
249+
private Consumer<ConfigurableJWTProcessor<JWKSecurityContext>> jwtProcessorCustomizer;
248250

249251
private JwkSetUriReactiveJwtDecoderBuilder(String jwkSetUri) {
250252
Assert.hasText(jwkSetUri, "jwkSetUri cannot be empty");
251253
this.jwkSetUri = jwkSetUri;
254+
this.jwtProcessorCustomizer = (processor) -> {};
252255
}
253256

254257
/**
@@ -294,6 +297,20 @@ public JwkSetUriReactiveJwtDecoderBuilder webClient(WebClient webClient) {
294297
return this;
295298
}
296299

300+
/**
301+
* Use the given {@link Consumer} to customize the {@link JWTProcessor ConfigurableJWTProcessor} before
302+
* passing it to the build {@link NimbusReactiveJwtDecoder}.
303+
*
304+
* @param jwtProcessorCustomizer the callback used to alter the processor
305+
* @return a {@link JwkSetUriReactiveJwtDecoderBuilder} for further configurations
306+
* @since 5.4
307+
*/
308+
public JwkSetUriReactiveJwtDecoderBuilder jwtProcessorCustomizer(Consumer<ConfigurableJWTProcessor<JWKSecurityContext>> jwtProcessorCustomizer) {
309+
Assert.notNull(jwtProcessorCustomizer, "jwtProcessorCustomizer cannot be null");
310+
this.jwtProcessorCustomizer = jwtProcessorCustomizer;
311+
return this;
312+
}
313+
297314
/**
298315
* Build the configured {@link NimbusReactiveJwtDecoder}.
299316
*
@@ -323,6 +340,8 @@ Converter<JWT, Mono<JWTClaimsSet>> processor() {
323340
jwtProcessor.setJWSKeySelector(jwsKeySelector);
324341
jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> {});
325342

343+
this.jwtProcessorCustomizer.accept(jwtProcessor);
344+
326345
ReactiveRemoteJWKSource source = new ReactiveRemoteJWKSource(this.jwkSetUri);
327346
source.setWebClient(this.webClient);
328347

@@ -360,11 +379,13 @@ private JWKSelector createSelector(Function<JWSAlgorithm, Boolean> expectedJwsAl
360379
public static final class PublicKeyReactiveJwtDecoderBuilder {
361380
private final RSAPublicKey key;
362381
private JWSAlgorithm jwsAlgorithm;
382+
private Consumer<ConfigurableJWTProcessor<SecurityContext>> jwtProcessorCustomizer;
363383

364384
private PublicKeyReactiveJwtDecoderBuilder(RSAPublicKey key) {
365385
Assert.notNull(key, "key cannot be null");
366386
this.key = key;
367387
this.jwsAlgorithm = JWSAlgorithm.RS256;
388+
this.jwtProcessorCustomizer = (processor) -> {};
368389
}
369390

370391
/**
@@ -382,6 +403,20 @@ public PublicKeyReactiveJwtDecoderBuilder signatureAlgorithm(SignatureAlgorithm
382403
return this;
383404
}
384405

406+
/**
407+
* Use the given {@link Consumer} to customize the {@link JWTProcessor ConfigurableJWTProcessor} before
408+
* passing it to the build {@link NimbusReactiveJwtDecoder}.
409+
*
410+
* @param jwtProcessorCustomizer the callback used to alter the processor
411+
* @return a {@link PublicKeyReactiveJwtDecoderBuilder} for further configurations
412+
* @since 5.4
413+
*/
414+
public PublicKeyReactiveJwtDecoderBuilder jwtProcessorCustomizer(Consumer<ConfigurableJWTProcessor<SecurityContext>> jwtProcessorCustomizer) {
415+
Assert.notNull(jwtProcessorCustomizer, "jwtProcessorCustomizer cannot be null");
416+
this.jwtProcessorCustomizer = jwtProcessorCustomizer;
417+
return this;
418+
}
419+
385420
/**
386421
* Build the configured {@link NimbusReactiveJwtDecoder}.
387422
*
@@ -406,6 +441,8 @@ Converter<JWT, Mono<JWTClaimsSet>> processor() {
406441
// Spring Security validates the claim set independent from Nimbus
407442
jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> { });
408443

444+
this.jwtProcessorCustomizer.accept(jwtProcessor);
445+
409446
return jwt -> Mono.just(createClaimsSet(jwtProcessor, jwt, null));
410447
}
411448
}
@@ -418,10 +455,12 @@ Converter<JWT, Mono<JWTClaimsSet>> processor() {
418455
public static final class SecretKeyReactiveJwtDecoderBuilder {
419456
private final SecretKey secretKey;
420457
private JWSAlgorithm jwsAlgorithm = JWSAlgorithm.HS256;
458+
private Consumer<ConfigurableJWTProcessor<SecurityContext>> jwtProcessorCustomizer;
421459

422460
private SecretKeyReactiveJwtDecoderBuilder(SecretKey secretKey) {
423461
Assert.notNull(secretKey, "secretKey cannot be null");
424462
this.secretKey = secretKey;
463+
this.jwtProcessorCustomizer = (processor) -> {};
425464
}
426465

427466
/**
@@ -441,6 +480,20 @@ public SecretKeyReactiveJwtDecoderBuilder macAlgorithm(MacAlgorithm macAlgorithm
441480
return this;
442481
}
443482

483+
/**
484+
* Use the given {@link Consumer} to customize the {@link JWTProcessor ConfigurableJWTProcessor} before
485+
* passing it to the build {@link NimbusReactiveJwtDecoder}.
486+
*
487+
* @param jwtProcessorCustomizer the callback used to alter the processor
488+
* @return a {@link SecretKeyReactiveJwtDecoderBuilder} for further configurations
489+
* @since 5.4
490+
*/
491+
public SecretKeyReactiveJwtDecoderBuilder jwtProcessorCustomizer(Consumer<ConfigurableJWTProcessor<SecurityContext>> jwtProcessorCustomizer) {
492+
Assert.notNull(jwtProcessorCustomizer, "jwtProcessorCustomizer cannot be null");
493+
this.jwtProcessorCustomizer = jwtProcessorCustomizer;
494+
return this;
495+
}
496+
444497
/**
445498
* Build the configured {@link NimbusReactiveJwtDecoder}.
446499
*
@@ -459,6 +512,8 @@ Converter<JWT, Mono<JWTClaimsSet>> processor() {
459512
// Spring Security validates the claim set independent from Nimbus
460513
jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> { });
461514

515+
this.jwtProcessorCustomizer.accept(jwtProcessor);
516+
462517
return jwt -> Mono.just(createClaimsSet(jwtProcessor, jwt, null));
463518
}
464519
}
@@ -471,10 +526,12 @@ Converter<JWT, Mono<JWTClaimsSet>> processor() {
471526
public static final class JwkSourceReactiveJwtDecoderBuilder {
472527
private final Function<SignedJWT, Flux<JWK>> jwkSource;
473528
private JWSAlgorithm jwsAlgorithm = JWSAlgorithm.RS256;
529+
private Consumer<ConfigurableJWTProcessor<JWKSecurityContext>> jwtProcessorCustomizer;
474530

475531
private JwkSourceReactiveJwtDecoderBuilder(Function<SignedJWT, Flux<JWK>> jwkSource) {
476532
Assert.notNull(jwkSource, "jwkSource cannot be null");
477533
this.jwkSource = jwkSource;
534+
this.jwtProcessorCustomizer = (processor) -> {};
478535
}
479536

480537
/**
@@ -490,6 +547,20 @@ public JwkSourceReactiveJwtDecoderBuilder jwsAlgorithm(JwsAlgorithm jwsAlgorithm
490547
return this;
491548
}
492549

550+
/**
551+
* Use the given {@link Consumer} to customize the {@link JWTProcessor ConfigurableJWTProcessor} before
552+
* passing it to the build {@link NimbusReactiveJwtDecoder}.
553+
*
554+
* @param jwtProcessorCustomizer the callback used to alter the processor
555+
* @return a {@link JwkSourceReactiveJwtDecoderBuilder} for further configurations
556+
* @since 5.4
557+
*/
558+
public JwkSourceReactiveJwtDecoderBuilder jwtProcessorCustomizer(Consumer<ConfigurableJWTProcessor<JWKSecurityContext>> jwtProcessorCustomizer) {
559+
Assert.notNull(jwtProcessorCustomizer, "jwtProcessorCustomizer cannot be null");
560+
this.jwtProcessorCustomizer = jwtProcessorCustomizer;
561+
return this;
562+
}
563+
493564
/**
494565
* Build the configured {@link NimbusReactiveJwtDecoder}.
495566
*
@@ -507,6 +578,8 @@ Converter<JWT, Mono<JWTClaimsSet>> processor() {
507578
jwtProcessor.setJWSKeySelector(jwsKeySelector);
508579
jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> {});
509580

581+
this.jwtProcessorCustomizer.accept(jwtProcessor);
582+
510583
return jwt -> {
511584
if (jwt instanceof SignedJWT) {
512585
return this.jwkSource.apply((SignedJWT) jwt)

0 commit comments

Comments
 (0)