| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126 | = OAuth 2.0 Resource Server Multi-tenancy[[webflux-oauth2resourceserver-multitenancy]]== Multi-tenancyA resource server is considered multi-tenant when there are multiple strategies for verifying a bearer token, keyed by some tenant identifier.For example, your resource server can accept bearer tokens from two different authorization servers.Alternately, your authorization server can represent a multiplicity of issuers.In each case, two things need to be done and trade-offs are associated with how you choose to do them:. Resolve the tenant.. Propagate the tenant.=== Resolving the Tenant By ClaimOne way to differentiate tenants is by the issuer claim. Since the issuer claim accompanies signed JWTs, you can do so with the `JwtIssuerReactiveAuthenticationManagerResolver`:[tabs]======Java::+[source,java,role="primary"]----JwtIssuerReactiveAuthenticationManagerResolver authenticationManagerResolver = new JwtIssuerReactiveAuthenticationManagerResolver    ("https://idp.example.org/issuerOne", "https://idp.example.org/issuerTwo");http    .authorizeExchange(exchanges -> exchanges        .anyExchange().authenticated()    )    .oauth2ResourceServer(oauth2 -> oauth2        .authenticationManagerResolver(authenticationManagerResolver)    );----Kotlin::+[source,kotlin,role="secondary"]----val customAuthenticationManagerResolver = JwtIssuerReactiveAuthenticationManagerResolver("https://idp.example.org/issuerOne", "https://idp.example.org/issuerTwo")return http {    authorizeExchange {        authorize(anyExchange, authenticated)    }    oauth2ResourceServer {        authenticationManagerResolver = customAuthenticationManagerResolver    }}----======This is nice because the issuer endpoints are loaded lazily.In fact, the corresponding `JwtReactiveAuthenticationManager` is instantiated only when the first request with the corresponding issuer is sent.This allows for an application startup that is independent from those authorization servers being up and available.==== Dynamic TenantsYou may not want to restart the application each time a new tenant is added.In this case, you can configure the `JwtIssuerReactiveAuthenticationManagerResolver` with a repository of `ReactiveAuthenticationManager` instances, which you can edit at runtime:[tabs]======Java::+[source,java,role="primary"]----private Mono<ReactiveAuthenticationManager> addManager(		Map<String, ReactiveAuthenticationManager> authenticationManagers, String issuer) {	return Mono.fromCallable(() -> ReactiveJwtDecoders.fromIssuerLocation(issuer))            .subscribeOn(Schedulers.boundedElastic())            .map(JwtReactiveAuthenticationManager::new)            .doOnNext(authenticationManager -> authenticationManagers.put(issuer, authenticationManager));}// ...JwtIssuerReactiveAuthenticationManagerResolver authenticationManagerResolver =        new JwtIssuerReactiveAuthenticationManagerResolver(authenticationManagers::get);http    .authorizeExchange(exchanges -> exchanges        .anyExchange().authenticated()    )    .oauth2ResourceServer(oauth2 -> oauth2        .authenticationManagerResolver(authenticationManagerResolver)    );----Kotlin::+[source,kotlin,role="secondary"]----private fun addManager(        authenticationManagers: MutableMap<String, ReactiveAuthenticationManager>, issuer: String): Mono<JwtReactiveAuthenticationManager> {    return Mono.fromCallable { ReactiveJwtDecoders.fromIssuerLocation(issuer) }            .subscribeOn(Schedulers.boundedElastic())            .map { jwtDecoder: ReactiveJwtDecoder -> JwtReactiveAuthenticationManager(jwtDecoder) }            .doOnNext { authenticationManager: JwtReactiveAuthenticationManager -> authenticationManagers[issuer] = authenticationManager }}// ...var customAuthenticationManagerResolver = JwtIssuerReactiveAuthenticationManagerResolver(authenticationManagers::get)return http {    authorizeExchange {        authorize(anyExchange, authenticated)    }    oauth2ResourceServer {        authenticationManagerResolver = customAuthenticationManagerResolver    }}----======In this case, you construct `JwtIssuerReactiveAuthenticationManagerResolver` with a strategy for obtaining the `ReactiveAuthenticationManager` given to the issuer.This approach lets us add and remove elements from the repository (shown as a `Map` in the preceding snippet) at runtime.[NOTE]====It would be unsafe to simply take any issuer and construct an `ReactiveAuthenticationManager` from it.The issuer should be one that the code can verify from a trusted source, such as an allowed list of issuers.====
 |