Expected Behavior
If I want to support multi-issuer authentication for a resource server, I can instantiate a JwtIssuerReactiveAuthenticationManagerResolver and provide a list of providers. I should be able to further customize this class, for example to set the granted authorities converter on the JwtReactiveAuthenticationManager.
Current Behavior
While it is possible to provide a custom ReactiveAuthenticationManagerResolver<String> implementation to the JwtIssuerReactiveAuthenticationManagerResolver class constructor, it needs to be written from scratch because:
TrustedIssuerJwtAuthenticationManagerResolveris defined statically inside thefinalclassJwtIssuerReactiveAuthenticationManagerResolver; it is not possible to access this class in any way.TrustedIssuerJwtAuthenticationManagerResolverhas no composition or extensible methods that would allow me to customize theJwtReactiveAuthenticationManager; it is all baked into theresolve(...)method.
Context I've rarely seen a case where at least some amount of granted authorities conversion is required when working with authorization in applications. In AWS, for example, Cognito provides group membership in a custom group claim. Other providers have similar levels of customization in their claims, and it is often a requirement to map those claims to roles--which a granted authority converter is great for. Multi-tenancy I've seen less often, but it has often included these requirements in addition to multi-tenancy itsself.
To at least customize the granted authorities converter, simply setting the converter if it was provided is a decent-enough workaround (but ofc this example below is a modified copy of the original code, so aside from my minor modifications is largely the work of the original authors):
// Copy of existing code, with modifications below
class ComposableTrustedIssuerJwtAuthenticationManagerResolver
implements ReactiveAuthenticationManagerResolver<String> {
private final Map<String, Mono<ReactiveAuthenticationManager>> authenticationManagers = new ConcurrentHashMap<>();
private final Predicate<String> trustedIssuer;
private final Converter<Jwt, Mono<AbstractAuthenticationToken>> grantedAuthoritiesConverter; // Add this
public ComposableTrustedIssuerJwtAuthenticationManagerResolver(Predicate<String> trustedIssuer,
Converter<Jwt, Mono<AbstractAuthenticationToken>> grantedAuthoritiesConverter) {
this.trustedIssuer = trustedIssuer;
this.grantedAuthoritiesConverter = grantedAuthoritiesConverter;
}
@Override
public Mono<ReactiveAuthenticationManager> resolve(String issuer) {
if (!this.trustedIssuer.test(issuer)) {
return Mono.empty();
}
return this.authenticationManagers.computeIfAbsent(issuer,
(k) -> Mono.<ReactiveAuthenticationManager>fromCallable(() -> {
JwtReactiveAuthenticationManager manager = new JwtReactiveAuthenticationManager(
ReactiveJwtDecoders.fromIssuerLocation(k)
);
if(grantedAuthoritiesConverter != null) {
manager.setJwtAuthenticationConverter(grantedAuthoritiesConverter); // add this
}
return manager;
})
.subscribeOn(Schedulers.boundedElastic())
.cache((manager) -> Duration.ofMillis(Long.MAX_VALUE), (ex) -> Duration.ZERO, () -> Duration.ZERO)
);
}
}
Of course, it's be really nice to be able to customize the JwtReactiveAuthenticationManager completely, or to be able to provide a different JwtDecoder (for example, one that wraps the default but provides better logging for e.g. authentication failure reasons in a development environment...but arguably that could be filed as a separate issue, if at all, as that could be easily abused to print sensitive information to the logs)
Comment From: jzheaux
Thanks for the suggestion, @TinaTiel!
This appears to be a duplicate of #9096. Please see my comment about additional constructors and suggested usage.
I'm going to close this; however, if you feel like your situation is distinct after reading that ticket, please feel to reopen to discuss things further. Otherwise, I'd encourage you to post any further comments over there.
Comment From: TinaTiel
Hi, thanks for taking a look at this. I'd comment on this issue you linked, but I see you closed that one too so I'll comment here.
What is distinctive about this one is the use of Reactive stuff. It's easy to mess up the integration with non-reactive parts, and what this reactive implementation does is a bit...tricky/weird to me? I've been using and learning reactive stuff for months, and there's still plenty I've not touched or that I find intimidating. Would be nice to have this tucked away in here so I don't need to worry about a core security feature working in a wonky way due to a subtle misunderstanding of schedulers.
Anyway, if constructor telescoping is the problem, then why not a builder to solve that problem?