OAuth 2.0 资源服务器多租户

多租户

当存在多种策略来验证 Bearer 令牌,且这些策略由某个租户标识符进行键控时,资源服务器被认为是多租户的。

例如,你的资源服务器可以接受来自两个不同授权服务器的 Bearer 令牌。或者,你的授权服务器可以代表多个颁发者。

在每种情况下,都需要完成两件事,并且你选择如何完成它们会带来权衡:

  1. 解析租户。

  2. 传播租户。

按声明解析租户

区分租户的一种方式是根据颁发者声明(issuer claim)。由于颁发者声明伴随着签名的 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 只有在发送包含对应颁发者的第一个请求时才会实例化。这使得应用程序启动可以独立于那些授权服务器是否已启动并可用。

动态租户

你可能不想在每次添加新租户时重启应用程序。在这种情况下,你可以配置 JwtIssuerReactiveAuthenticationManagerResolver,使其使用一个 ReactiveAuthenticationManager 实例的存储库,你可以在运行时编辑该存储库:

  • 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,该策略根据颁发者获取 ReactiveAuthenticationManager。这种方法允许我们在运行时从存储库(在前述代码片段中显示为 Map)中添加和删除元素。

简单地接受任何颁发者并从中构建 ReactiveAuthenticationManager 是不安全的。颁发者应该是代码可以从可信来源验证的,例如允许的颁发者列表。