The ConfigurableJWTProcessor.setJWSKeySelector(JWSKeySelector) is unable to update the reference used by NimbusReactiveJwtDecoder.JwkSetUriReactiveJwtDecoderBuilder.processor(). The processor holds a reference to the default JWSKeySelector created within NimbusReactiveJwtDecoder.JwkSetUriReactiveJwtDecoderBuilder.jwsKeySelector() via a method reference within JwkSetUriReactiveJwtDecoderBuilder.getExpectedJwsAlgorithms() before the ConfigurableJWTProcessorsetJWSKeySelector(JWSKeySelector) is executed.
The following unit test will show that the processor is executing the wrong JWSVerificationKeySelector.isAllowed(JWSAlgorithm) reference.
@ExtendWith(MockitoExtension.class)
class NimbusReactiveJwtDecoderTest {
@Mock private WebClient webClient;
@Mock private RequestHeadersUriSpec<?> requestHeadersUriSpec;
@Mock private ResponseSpec responseSpec;
@Test
void customizeJWSKeySelector() throws Exception {
var jwkSet = new JWKSet(
new RSAKeyGenerator(RSAKeyGenerator.MIN_KEY_SIZE_BITS)
.algorithm(JWSAlgorithm.RS512)
.keyID(UUID.randomUUID().toString())
.keyUse(KeyUse.SIGNATURE)
.generate());
var jwkSource = new ImmutableJWKSet<>(jwkSet);
var jwt = new NimbusJwtEncoder(jwkSource).encode(
JwtEncoderParameters.from(JwsHeader.with(SignatureAlgorithm.RS512).build(),
JwtClaimsSet.builder().issuer("issuer").build()));
// stub WebClient response for ReactiveRemoteJWKSource
Mockito.doReturn(requestHeadersUriSpec).when(webClient).get();
Mockito.doReturn(requestHeadersUriSpec).when(requestHeadersUriSpec).uri(Mockito.anyString());
Mockito.when(requestHeadersUriSpec.retrieve()).thenReturn(responseSpec);
Mockito.when(responseSpec.bodyToMono(String.class)).thenReturn(Mono.just(jwkSet.toString()));
var decoder = NimbusReactiveJwtDecoder
.withJwkSetUri("http://localhost/oauth2/jwks") // use NimbusReactiveJwtDecoder.JwkSetUriReactiveJwtDecoderBuilder
.webClient(webClient) // stub remote call
.jwtProcessorCustomizer(processor -> {
// this reference will not be used by the "Function<JWSAlgorithm, Boolean> expectedJwsAlgorithms" within JwkSetUriReactiveJwtDecoderBuilder.processor()
var jwsKeySelector = new JWSVerificationKeySelector<>(Set.of(JWSAlgorithm.RS512), jwkSource);
// the JwkSetUriReactiveJwtDecoderBuilder.processor() still holds a reference to the JWSVerificationKeySelector instantiated
// within JwkSetUriReactiveJwtDecoderBuilder.jwsKeySelector(), via the method reference created by JwkSetUriReactiveJwtDecoderBuilder.getExpectedJwsAlgorithms(), held before the customizer is executed.
processor.setJWSKeySelector((JWSVerificationKeySelector) jwsKeySelector);
})
.build();
// execute the processor
decoder.decode(jwt.getTokenValue())
.block();
}
}
Comment From: jzheaux
Thanks, @meverden, for the observation. Are you able to submit a PR that addresses the issue? It would be nice to include your test as part of the contribution.
Comment From: meverden
@jzheaux, yes, I'll submit a PR.
Comment From: meverden
@jzheaux , https://github.com/spring-projects/spring-security/pull/12965 has been submitted.