Context
When configuring an Spring application as an OAuth Resource Server and we use Jwt, we can set manually the principalClaimName on JwtAuthenticationConverter object.
Example like this
fun getCustomAuthenticationConverter(authoritiesClaimName : String): Converter<Jwt, Mono<AbstractAuthenticationToken>> {
return ReactiveJwtAuthenticationConverterAdapter(
JwtAuthenticationConverter().apply {
setPrincipalClaimName("anInt")
setJwtGrantedAuthoritiesConverter(GrantedAuthoritiesExtractor(authoritiesClaimName))
}
)
}
Current Behavior
If we use a specific principalClaimName on a JwtAuthenticationConverter and if the value behind the claim name is not a String, a ClassCastException is thrown in the convert method.
@Override
public final AbstractAuthenticationToken convert(Jwt jwt) {
Collection<GrantedAuthority> authorities = extractAuthorities(jwt);
if (this.principalClaimName == null) {
return new JwtAuthenticationToken(jwt, authorities);
}
String name = jwt.getClaim(this.principalClaimName); // this line throw a ClassCastException
return new JwtAuthenticationToken(jwt, authorities, name);
}
We can easily reproduce the situation by adding a test in class JwtAuthenticationConverterTests.
@Test
public void convertWhenPrincipalClaimNameSetWithOtherValueThanString() {
this.jwtAuthenticationConverter.setPrincipalClaimName("user_id");
Jwt jwt = TestJwts.jwt().claim("user_id", 100).build();
//TODO: complete with assertions
}
Expected Behavior
I think we should adapt the code to not lead to a ClassCastException but maybe throw a proper exception which "explain" what the problem is.
Maybe by adapting like this ?
Object claimValue = jwt.getClaim(this.principalClaimName);
Assert.isInstanceOf(String.class, claimValue, "The principal value is not a String");
String name = (String)claimValue;
or maybe with the usage of Jwt.claimAsString(...) method ?
What do you think ? Let me know if I can help.
PS : by the way, regarding this RFC https://tools.ietf.org/html/rfc7519#section-4.1.2 about JWT token, nothing say that a claim value must be a String as it is implemented in JwtAuthenticationToken. Maybe this discussion is out of scope but indeed
Best regards, Olivier
Comment From: jgrandja
@omlip I agree that we should avoid a ClassCastException from being thrown.
I think adding Assert.isInstanceOf() (as suggested) is a reasonable improvement. Would you be interested in submitting a PR for this?
Comment From: omlip
@jgrandja Sure I will, you can assign it to me
Comment From: jgrandja
Thanks @omlip !
Comment From: omlip
@jgrandja PR submitted
Sould not we, by the way, adjust the documentation to mention that it is possible to set manually a principalClaimName and that must be a String ?
Comment From: jgrandja
Thanks @omlip. Agreed, please update the javadoc for setPrincipalClaimName().
Comment From: omlip
Ok will update the PR accordingly.
Comment From: omlip
Hi @jgrandja I was talking about Spring Security Reference documentation. In particular, the following sentence:
The resulting Authentication#getPrincipal, by default, is a Spring Security Jwt object, and Authentication#getName maps to the JWT’s sub property, if one is present.
to be transformed in something like this :
The resulting Authentication#getPrincipal, by default, is a Spring Security Jwt object, and Authentication#getName maps to the JWT’s sub property, if one is present. If none is present, you can still set the principal claim name to any JWT's property by using setPrincipalClaimName method. The value behind the claim must be a String
but also I can update javadoc of course :-)
What do you think ?
br Olivier
Comment From: jgrandja
Sounds good @omlip