I have a simple Security filter chain configured for multitenancy. However, I cannot add my customer jwtConverter.
Here is the setup
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http, Converter<Jwt, Mono<AbstractAuthenticationToken>> jwtAuthenticationConverter) {
JwtIssuerReactiveAuthenticationManagerResolver authenticationManagerResolver =
JwtIssuerReactiveAuthenticationManagerResolver
.fromTrustedIssuers("issuer1", "issuer2");
http
.securityContextRepository(NoOpServerSecurityContextRepository.getInstance())
.csrf(ServerHttpSecurity.CsrfSpec::disable)
.authorizeExchange(spec -> spec
.pathMatchers(
"/api/v1/profile/public/**",
"/api/docs/**",
"/swagger/**"
).permitAll()
.anyExchange()
.authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2.authenticationManagerResolver(authenticationManagerResolver));
return http.build();
}
When I add jwtConverter
.oauth2ResourceServer(oauth2 ->
oauth2
.jwt(jwtSpec -> jwtSpec.jwtAuthenticationConverter(jwtAuthenticationConverter))
.authenticationManagerResolver(authenticationManagerResolver)
)
I'm getting an error If an authenticationManagerResolver() is configured, then it takes precedence over any jwt() or opaqueToken() configuration
How do I resolve this in spring webflux 3.2.1?
Well, after a bit of cross checking on past raised github issues on the same, I've managed to create a custom JwtIssuerReactiveAuthenticationManagerResolver
with a CustomAuthenticationManager
that has authenticate
method which can be extended to allow a customAuthentication converter.
Here is my solution
Simple CustomAuthenticationConverter
public class CustomAuthenticationConverter {
private static final String ROLES = "roleName";
private static final String ROLE_PREFIX = "ROLE_";
public Mono<Authentication> convertJwtToAuthentication(Jwt jwt) {
Collection<GrantedAuthority> authorities = extractAuthoritiesFromJwt(jwt);
return Mono.just(new JwtAuthenticationToken(jwt, authorities, jwt.getClaimAsString("userPublicId")));
}
private Collection<GrantedAuthority> extractAuthoritiesFromJwt(Jwt jwt) {
var realmRoles = realmRoles(jwt);
return realmRoles
.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toSet());
}
private List<String> realmRoles(Jwt jwt) {
return Optional.of(ROLE_PREFIX.concat(jwt.getClaimAsString(ROLES)))
.map(List::of)
.orElse(emptyList());
}
}
CustomAuthenticationManager
public class CustomAuthenticationManager implements ReactiveAuthenticationManager {
private final ReactiveJwtDecoder decoder;
public CustomAuthenticationManager(ReactiveJwtDecoder decoder) {
this.decoder = decoder;
}
@Override
public Mono<Authentication> authenticate(Authentication authentication) {
// Implement custom authentication logic here
BearerTokenAuthenticationToken token = (BearerTokenAuthenticationToken) authentication;
Mono<Jwt> jwtMono = decoder.decode(token.getToken());
return jwtMono.flatMap(jwt -> new CustomAuthenticationConverter().convertJwtToAuthentication(jwt));
}
}
JwtIssuerReactiveAuthenticationManagerResolver
@Bean
public JwtIssuerReactiveAuthenticationManagerResolver jwtIssuerReactiveAuthenticationManagerResolver() {
Map<String, ReactiveAuthenticationManager> managers = new HashMap<>();
for (String issuer : trustedIssuers) {
ReactiveJwtDecoder decoder = ReactiveJwtDecoders.fromIssuerLocation(issuer);
managers.put(issuer, new CustomAuthenticationManager(decoder));
}
return new JwtIssuerReactiveAuthenticationManagerResolver(issuer -> Mono.justOrEmpty(managers.get(issuer)));
}
The add the resolver in SecurityWebFilterChain
.oauth2ResourceServer(oauth2 -> oauth2.authenticationManagerResolver(jwtIssuerReactiveAuthenticationManagerResolver()));
And ofcourse you'd need to have a list of trustedIssuers
.