mirror of
				https://github.com/spring-projects/spring-security.git
				synced 2025-11-04 00:28:54 +00:00 
			
		
		
		
	This commit removes unnecessary main-branch merges starting from 8750608b5bca45525c99d0a41a20ed02de93d8c7 and adds the following needed commit(s) that were made afterward: - 5dce82c48bc0b174838501c5a111b2de70822914
		
			
				
	
	
		
			128 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			128 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
= OAuth 2.0 Resource Server Multi-tenancy
 | 
						|
 | 
						|
[[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.
 | 
						|
 | 
						|
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 Claim
 | 
						|
 | 
						|
One 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 = JwtIssuerReactiveAuthenticationManagerResolver
 | 
						|
    .fromTrustedIssuers("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
 | 
						|
    .fromTrustedIssuers("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 Tenants
 | 
						|
 | 
						|
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:
 | 
						|
 | 
						|
[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.
 | 
						|
====
 |