OAuth 2.0 资源服务器多租户
多租户
当存在多种根据某些租户标识符进行验证的承载令牌策略时,资源服务器被视为多租户。
例如,你的资源服务器可以接受来自两个不同授权服务器的持有者令牌。或者,你的授权服务器可以代表多个发行人。
在每种情况下,需要做两件事,并且如何选择去做这些事会涉及到权衡:
-
解析租户。
-
传播租户。
通过声明解析租户
可以通过 issuer 声明来区分租户。由于 issuer 声明会伴随已签名的 JWT,因此可以使用 JwtIssuerReactiveAuthenticationManagerResolver
来实现这一点:
- Java
- Kotlin
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)
);
val customAuthenticationManagerResolver = JwtIssuerReactiveAuthenticationManagerResolver
.fromTrustedIssuers("https://idp.example.org/issuerOne", "https://idp.example.org/issuerTwo")
return http {
authorizeExchange {
authorize(anyExchange, authenticated)
}
oauth2ResourceServer {
authenticationManagerResolver = customAuthenticationManagerResolver
}
}
这样很好,因为发行者端点是延迟加载的。实际上,只有在发送了带有相应发行者的第一个请求时,才会实例化相应的 JwtReactiveAuthenticationManager
。这使得应用程序的启动独立于那些授权服务器是否处于启动和可用状态。
动态租户
您可能不希望每次添加新租户时都重新启动应用程序。在这种情况下,您可以使用 ReactiveAuthenticationManager
实例的仓库来配置 JwtIssuerReactiveAuthenticationManagerResolver
,您可以在运行时编辑该仓库:
- Java
- Kotlin
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)
);
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
}
}
在这种情况下,你使用一种策略来构造 JwtIssuerReactiveAuthenticationManagerResolver
,该策略用于为给定的 issuer 获取 ReactiveAuthenticationManager
。这种方法允许我们在运行时添加和移除存储库中的元素(如前面代码片段中所示的 Map
)。
备注
简单地接受任何发行者并从中构建一个 ReactiveAuthenticationManager
是不安全的。发行者应该是代码可以从可信来源验证的,例如允许的发行者列表。