Expected Behavior
It should be possible to override / customize BearerTokenResolver and JwtAuthenticationProvider inside JwtIssuerAuthenticationManagerResolver class, in a multi-tenant environment, so that the end-user can set non-standard behavior that may be desired (for example, custom JWT parsing).
Current Behavior
Customization is not possible at all, end-user is forced to use predefined implementations inside JwtIssuerAuthenticationManagerResolver, and this leads to errors if JWTs contain something uncommon, same if there's something specific with Bearer Tokens.
Context
Please, see also #8535 for the original issue regarding BearerTokenResolver, as the scope of it would ideally be included in a PR that could be a result of this issue.
Additionally, this could be related to #6778, at least to some extent.
The context explanation will be mostly about TrustedIssuerJwtAuthenticationManagerResolver part of the issue, but it's very similar to JwtClaimIssuerConverter part.
Using an external oauth2ResourceServer, in a multi-tenant environment, it should be possible to override / select a custom JwtAuthenticationProvider with a custom JwtAuthenticationConverter, as not all of the JWT tokens are the same, and this leads to errors.
Currently, according to the official documentation (https://docs.spring.io/spring-security/site/docs/current/reference/html5/#oauth2resourceserver-multitenancy), if there is a need to support multi-tenancy by JWT Claim, one can use JwtIssuerAuthenticationManagerResolver that has to be configured by a class extending WebSecurityConfigurerAdapter, however, it comes with an overriden
public AuthenticationManager resolve(String issuer) method in the TrustedIssuerJwtAuthenticationManagerResolver static class, that contains:
1) a predefined, hardcoded JwtAuthenticationProvider,
2) which in turn contains a predefined, hardcoded JwtAuthenticationConverter,
3) which in turn contains a predefined, hardcoded JwtGrantedAuthoritiesConverter, the root of my problems.
Normally, the issue could be solved by configuring a Customizer<JwtConfigurer> within the HttpSecurity DSL, but OAuth2ResourceServerConfigurer runs the validateConfiguration() method that throws an IllegalStateException because, quote:
If an authenticationManagerResolver() is configured, then it takes precedence over any jwt() or opaqueToken() configuration
That's of course understandable, however, there should be a way to configure your own Customizer/JwtGrantedAuthoritiesConverter.
Currently, I have solved my own issue by using my own implementation of a JwtIssuerAuthenticationManagerResolver, that I have used instead of the 'official' one, that basically creates a new JwtAuthenticationConverter and sets my custom JwtGrantedAuthoritiesConverter, but I am willing to work on a PR, to provide a more "official" solution.
Some additional information would be great though: could the solution be based on allowing the end-user to provide his own BearerTokenResolver / JwtAuthenticationProvider, that will take priority over the default one?
Comment From: jzheaux
@AbstractConcept, thanks for the feedback and the ideas. Let's take them one at a time:
provide his one
BearerTokenResolver
I see you've already found #8535 - I look forward to your contribution!
provider his one ...
JwtAuthenticationProvider
Since JwtIssuerAuthenticationManagerResolver is all about converting the JWT's issuer into an AuthenticationManager, I imagine what you'd want here is the ability to configure a strategy that can supply a JwtAuthenticationProvider for a given issuer.
This is the intent behind the AuthenticationManagerResolver<String> constructor. For example, you can do:
@Component
public class MyAuthenticationManagerResolver
implements AuthenticationManagerResolver<String> {
private Map<String, AuthenticationManager> authenticationManagers = ...;
public AuthenticationManager resolve(String issuer) {
return authenticationManagers.get(issuer);
}
public void addTrustedIssuer(String issuer) {
AuthenticationManager authenticationManager = // ... your by-issuer customizations
this.authenticationManagers.put(issuer, authenticationManager);
}
}
// ...
@Autowired
MyAuthenticationManagerResolver resolver;
protected void configure(HttpSecurity http) {
http
// ...
.oauth2ResourceServer(oauth2 -> oauth2
.authenticationManagerResolver(
new JwtIssuerAuthenticationManagerResolver(this.resolver))
);
}
Since I believe this addresses your issue I'll close this ticket, but please re-open if you feel there's more to discuss.