Expected Behavior

Allow to supply custom: * Converter<ServerWebExchange, Mono<String>> issuerConverter

into JwtIssuerReactiveAuthenticationManagerResolver

Current Behavior

private final Converter<ServerWebExchange, Mono<String>> issuerConverter is hardcoded and there is no way to supply own implementation.

public final class JwtIssuerReactiveAuthenticationManagerResolver
        implements ReactiveAuthenticationManagerResolver<ServerWebExchange> {

    private final ReactiveAuthenticationManagerResolver<String> issuerAuthenticationManagerResolver;
    private final Converter<ServerWebExchange, Mono<String>> issuerConverter = new JwtClaimIssuerConverter();

Context

I am migrating my project from single tenant to multi tenant using Spring Security. I used to do

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
    ...
             .oauth2ResourceServer(server -> server
                        .jwt(jwt -> jwt.jwtAuthenticationConverter(
                                new CustomTokenConverter()
                        )))
    }

    @Bean
    ReactiveJwtDecoder jwtDecoder() {
        return ReactiveJwtDecoders.fromOidcIssuerLocation(securityConfigurationProperties.getIssuerLocation());
    }

and now with following implementation

    @Autowired
    ReactiveAuthenticationManagerResolver<ServerWebExchange> reactiveAuthenticationManagerResolver;

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
            ...
           .oauth2ResourceServer(server -> server.authenticationManagerResolver(reactiveAuthenticationManagerResolver))
    }

    @Bean
    ReactiveAuthenticationManagerResolver<ServerWebExchange> reactiveAuthenticationManagerResolver() {
        return new JwtIssuerReactiveAuthenticationManagerResolver(securityConfigurationProperties.getIssuers());
    }

I can't use

CustomTokenConverter implements Converter<Jwt, Mono<AbstractAuthenticationToken>> 

anymore.

I need to copy entire JwtIssuerReactiveAuthenticationManagerResolver class and: * inject own implementation of issuerConverter

Comment From: bbednarek

After digging into the code I managed to solve it by implementing


import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.ReactiveAuthenticationManagerResolver;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoders;
import org.springframework.security.oauth2.server.resource.authentication.JwtReactiveAuthenticationManager;
import reactor.core.publisher.Mono;

import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Collectors;

public class TrustedIssuerOidcAuthenticationManagerResolver implements ReactiveAuthenticationManagerResolver<String> {

    private final Map<String, String> issuers;
    private final Converter<Jwt, Mono<AbstractAuthenticationToken>> jwtAuthenticationConverter;

    private final Map<String, Mono<ReactiveAuthenticationManager>> authenticationManagers = new ConcurrentHashMap<>();

    public TrustedIssuerOidcAuthenticationManagerResolver(
            Collection<String> trustedIssuers,
            Converter<Jwt, Mono<AbstractAuthenticationToken>> jwtAuthenticationConverter
    ) {
        this.issuers = trustedIssuers.stream().collect(Collectors.toMap(Function.identity(), Function.identity()));
        this.jwtAuthenticationConverter = jwtAuthenticationConverter;
    }

    @Override
    public Mono<ReactiveAuthenticationManager> resolve(String issuer) {
        return this.authenticationManagers.computeIfAbsent(issuer, this::fromTenant);
    }

    private Mono<ReactiveAuthenticationManager> fromTenant(String issuer) {
        return Mono.justOrEmpty(this.issuers.get(issuer))
                .map(ReactiveJwtDecoders::fromOidcIssuerLocation)
                .map(JwtReactiveAuthenticationManager::new)
                .map(it -> {
                    it.setJwtAuthenticationConverter(jwtAuthenticationConverter);
                    return it;
                });

    }
}

and passing it into JwtIssuerReactiveAuthenticationManagerResolver

   @Bean
    ReactiveAuthenticationManagerResolver<ServerWebExchange> reactiveAuthenticationManagerResolver() {
        return new JwtIssuerReactiveAuthenticationManagerResolver(
            new TrustedIssuerOidcAuthenticationManagerResolver(
                    securityConfigurationProperties.getIssuers(),
                    new OrgUserTokenConverter()
            )
        );
    }