2022-02-12 14:35:14 +03:00 
										
									 
								 
							 
							
								
									
										 
								
							 
							
								 
							
							
								= OAuth 2.0 Resource Server Multi-tenancy
							 
						 
					
						
							
								
									
										
										
										
											2021-10-29 13:06:38 -06:00 
										
									 
								 
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								[[webflux-oauth2resourceserver-multitenancy]]
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								== Multi-tenancy
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								A resource server is considered multi-tenant when there are multiple strategies for verifying a bearer token, keyed by some tenant identifier.
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
									
										
										
										
											2021-12-13 16:57:36 -06:00 
										
									 
								 
							 
							
								
									
										 
								
							 
							
								 
							
							
								For example, your resource server can accept bearer tokens from two different authorization servers.
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								Alternately, your authorization server can represent a multiplicity of issuers.
							 
						 
					
						
							
								
									
										
										
										
											2021-10-29 13:06:38 -06:00 
										
									 
								 
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
									
										
										
										
											2021-12-13 16:57:36 -06:00 
										
									 
								 
							 
							
								
									
										 
								
							 
							
								 
							
							
								In each case, two things need to be done and trade-offs are associated with how you choose to do them:
							 
						 
					
						
							
								
									
										
										
										
											2021-10-29 13:06:38 -06:00 
										
									 
								 
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
									
										
										
										
											2021-12-13 16:57:36 -06:00 
										
									 
								 
							 
							
								
									
										 
								
							 
							
								 
							
							
								. Resolve the tenant.
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								. Propagate the tenant.
							 
						 
					
						
							
								
									
										
										
										
											2021-10-29 13:06:38 -06:00 
										
									 
								 
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								=== Resolving the Tenant By Claim
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
									
										
										
										
											2021-12-13 16:57:36 -06:00 
										
									 
								 
							 
							
								
									
										 
								
							 
							
								 
							
							
								One way to differentiate tenants is by the issuer claim. Since the issuer claim accompanies signed JWTs, you can do so with the `JwtIssuerReactiveAuthenticationManagerResolver`:
							 
						 
					
						
							
								
									
										
										
										
											2021-10-29 13:06:38 -06:00 
										
									 
								 
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
									
										
										
										
											2023-06-18 21:30:41 -05:00 
										
									 
								 
							 
							
								
									
										 
								
							 
							
								 
							
							
								[tabs]
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								======
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								Java::
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								+
							 
						 
					
						
							
								
									
										
										
										
											2021-10-29 13:06:38 -06:00 
										
									 
								 
							 
							
								
							 
							
								 
							
							
								[source,java,role="primary"]
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								----
							 
						 
					
						
							
								
									
										
										
										
											2023-08-08 10:02:31 -06:00 
										
									 
								 
							 
							
								
									
										 
								
							 
							
								 
							
							
								JwtIssuerReactiveAuthenticationManagerResolver authenticationManagerResolver = JwtIssuerReactiveAuthenticationManagerResolver
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    .fromTrustedIssuers("https://idp.example.org/issuerOne", "https://idp.example.org/issuerTwo");
							 
						 
					
						
							
								
									
										
										
										
											2021-10-29 13:06:38 -06:00 
										
									 
								 
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								http
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    .authorizeExchange(exchanges -> exchanges
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        .anyExchange().authenticated()
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    )
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    .oauth2ResourceServer(oauth2 -> oauth2
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        .authenticationManagerResolver(authenticationManagerResolver)
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    );
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								----
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
									
										
										
										
											2023-06-18 21:30:41 -05:00 
										
									 
								 
							 
							
								
									
										 
								
							 
							
								 
							
							
								Kotlin::
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								+
							 
						 
					
						
							
								
									
										
										
										
											2021-10-29 13:06:38 -06:00 
										
									 
								 
							 
							
								
							 
							
								 
							
							
								[source,kotlin,role="secondary"]
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								----
							 
						 
					
						
							
								
									
										
										
										
											2023-08-08 10:02:31 -06:00 
										
									 
								 
							 
							
								
									
										 
								
							 
							
								 
							
							
								val customAuthenticationManagerResolver = JwtIssuerReactiveAuthenticationManagerResolver
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    .fromTrustedIssuers("https://idp.example.org/issuerOne", "https://idp.example.org/issuerTwo")
							 
						 
					
						
							
								
									
										
										
										
											2021-10-29 13:06:38 -06:00 
										
									 
								 
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								return http {
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    authorizeExchange {
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        authorize(anyExchange, authenticated)
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    }
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    oauth2ResourceServer {
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								        authenticationManagerResolver = customAuthenticationManagerResolver
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    }
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								}
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								----
							 
						 
					
						
							
								
									
										
										
										
											2023-06-18 21:30:41 -05:00 
										
									 
								 
							 
							
								
									
										 
								
							 
							
								 
							
							
								======
							 
						 
					
						
							
								
									
										
										
										
											2021-10-29 13:06:38 -06:00 
										
									 
								 
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								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 Tenants
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
									
										
										
										
											2021-12-13 16:57:36 -06:00 
										
									 
								 
							 
							
								
									
										 
								
							 
							
								 
							
							
								You 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:
							 
						 
					
						
							
								
									
										
										
										
											2021-10-29 13:06:38 -06:00 
										
									 
								 
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
									
										
										
										
											2023-06-18 21:30:41 -05:00 
										
									 
								 
							 
							
								
									
										 
								
							 
							
								 
							
							
								[tabs]
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								======
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								Java::
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								+
							 
						 
					
						
							
								
									
										
										
										
											2021-10-29 13:06:38 -06:00 
										
									 
								 
							 
							
								
							 
							
								 
							
							
								[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)
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    );
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								----
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
									
										
										
										
											2023-06-18 21:30:41 -05:00 
										
									 
								 
							 
							
								
									
										 
								
							 
							
								 
							
							
								Kotlin::
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								+
							 
						 
					
						
							
								
									
										
										
										
											2021-10-29 13:06:38 -06:00 
										
									 
								 
							 
							
								
							 
							
								 
							
							
								[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
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								    }
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								}
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								----
							 
						 
					
						
							
								
									
										
										
										
											2023-06-18 21:30:41 -05:00 
										
									 
								 
							 
							
								
									
										 
								
							 
							
								 
							
							
								======
							 
						 
					
						
							
								
									
										
										
										
											2021-10-29 13:06:38 -06:00 
										
									 
								 
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
									
										
										
										
											2021-12-13 16:57:36 -06:00 
										
									 
								 
							 
							
								
									
										 
								
							 
							
								 
							
							
								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.
							 
						 
					
						
							
								
									
										
										
										
											2021-10-29 13:06:38 -06:00 
										
									 
								 
							 
							
								
							 
							
								 
							
							
								
							 
						 
					
						
							
								
									
										
										
										
											2021-12-13 16:57:36 -06:00 
										
									 
								 
							 
							
								
									
										 
								
							 
							
								 
							
							
								[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.
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							
							
								====