Summary
We should add a strategy where Jwt decoders can delegate to other Jwt decoders based upon the algorithm that is in the JWT that was passed into it. This would allow supporting multiple algorithms returned by an IdP as discussed in https://github.com/spring-projects/spring-security/issues/5465#issuecomment-492233620
A possible way to do this would be to have a JwtDecoder implementation that calls other JwtDecoders, though note this might require decoding the JWT twice.
Comment From: dnl50
Would it be possible to implement a JwtDecoder that decodes the header of the given token first to determine the signing algorithm used. The signing algorithm would then be used to find a decoder that supports the signing algorithm.
public class DelegatingJwtDecoder implements JwtDecoder {
private List<DelegatedJwtDecoder> decoders;
@Override
public Jwt decode(String token) throws JwtException {
String decodedHeader = decodeHeader(token);
String signingAlg = getSigningAlgorithm(decodedHeader);
DelegatedJwtDecoder jwtDecoder = findDecoder(signingAlg);
return jwtDecoder.decode(token);
}
private String decodeHeader(String token) {
// decode the Base64 encoded header of the token
}
private String getSigningAlgorithm(String tokenHeader) {
// get the algorithm from the token header
}
private DelegatedJwtDecoder findDecoderSupportingAlgorithm(String algorithm) {
return decoders.stream()
.filter(decoder -> decoder.supportsAlgorithm(algorithm))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("No decoder found that support the signing algorithm!"));
}
}
The DelegatedJwtDecoder would have an additional boolean supportsAlgorithm(String algorithm) method which would receive the signing algorithm the given token was signed with.
public interface DelegatedJwtDecoder extends JwtDecoder {
boolean supportsAlgorithm(String algorithm);
}
Comment From: jgrandja
@dnl50 I believe the example implementation you provided is similar to what @rwinch has suggested. However, I don't believe we need to introduce a new interface DelegatedJwtDecoder with the additional operation supportsAlgorithm().
Instead, we would provide a new implementation of JwtDecoder which would be constructed with a Map of alg to JwtDecoder to allow for this. It might not be a Map but would have to provide this type of mapping.
Comment From: compfantasy
According to spring security reference, the algorithm can be override by providing a JwtDecoder bean, but it doesn't work after I did that because the initialization of NimbusJwtDecoderJwkSupport locates at line 212 of OidcAuthorizationCodeAuthenticationProvider.java
Comment From: jgrandja
@compfantasy The PR #6495 introduced support in 5.2.0.M2 for a configurable JWS algorithm via OidcIdTokenDecoderFactory.setJwsAlgorithmResolver() or ReactiveOidcIdTokenDecoderFactory.setJwsAlgorithmResolver().
NOTE: NimbusJwtDecoderJwkSupport is deprecated in favour of NimbusJwtDecoder, which is used/configured by OidcIdTokenDecoderFactory.
Comment From: compfantasy
@jgrandja Does this mean that I just need upgrade current version of spring-security-oauth2-jose-5.1.4.RELEASE to 5.2.0.M2, then configure jwsAlgorithm in client registration? do you have a simple demo project regarding this change?thanks
Comment From: compfantasy
@jgrandja I already upgraded the version of spring-security-oauth2-client and spring-secuirty-oauth2-core to 5.2.0.M2, I can see there is a class named OidcIdTokenDecoderFactory, but how can i use setJwsAlgorithmResolver() to make JwsAlgorithm working in the spring context? I tried to create a new OidcTokenDecoderFactory bean and set the JwsAlgorithm to RS512, it doesn't work, and I tried to create a new JwtDecoder bean, it doesn't work for me as well, very appreciate if you can help here.
Comment From: jgrandja
@compfantasy Can you please put together a minimal sample so I can see what you have so far and share via a GitHub repository. This will be the most effective way to help resolve this for you.
Comment From: HungUnicorn
@compfantasy Can you please put together a minimal sample so I can see what you have so far and share via a GitHub repository. This will be the most effective way to help resolve this for you.
Do I need a CustomJwtDecoderFactory like
@Bean
public JwtDecoderFactory<ClientRegistration> customJwtDecoderFactory() {
return new CustomJwtDecoderFactory();
}
static class CustomJwtDecoderFactory implements JwtDecoderFactory<ClientRegistration> {
public JwtDecoder createDecoder(ClientRegistration reg) {
//...
return new CustomJwtDecoder();
}
}
then I create different decoders as
JWSKeySelector<SecurityContext> jwsKeySelector =
JWSAlgorithmFamilyJWSKeySelector.fromJWKSetURL(this.jwkSetUrl);
DefaultJWTProcessor<SecurityContext> jwtProcessor =
new DefaultJWTProcessor<>();
jwtProcessor.setJWSKeySelector(jwsKeySelector);
return new NimbusJwtDecoder(jwtProcessor);
the problem is how OAuth2LoginAuthenticationFilter can know which decoder to use given the registration?