此版本仍在开发中,尚未被视为稳定版本。如需最新稳定版本,请使用 Spring Security 7.0.4spring-doc.cadn.net.cn

OAuth 2.0 资源服务器多租户

Multi-tenancy

当存在多种用于验证持有者Tokens(bearer token)的策略,并且这些策略通过某个租户标识符进行区分时,该资源服务器被视为多租户的。spring-doc.cadn.net.cn

例如,您的资源服务器可以接受来自两个不同的授权服务器的bearerTokens。 或者,您的授权服务器可以代表多个发行者。spring-doc.cadn.net.cn

在每种情况下,都需要做两件事,并且根据你选择的方式会涉及到权衡:spring-doc.cadn.net.cn

  1. 解决租户问题。spring-doc.cadn.net.cn

  2. 传播租户。spring-doc.cadn.net.cn

通过声明解析租户

一种区分租户的方式是通过issuer声明。由于issuer声明伴随签名的JWT,你可以使用JwtIssuerReactiveAuthenticationManagerResolver来实现这一点:spring-doc.cadn.net.cn

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 只在首次发送对应发行人的请求时才被实例化。 这使得应用程序启动与那些授权服务器是否运行和可用无关。spring-doc.cadn.net.cn

动态租户

您可能不想每次添加新租户时都重启应用程序。 在这种情况下,您可以配置JwtIssuerReactiveAuthenticationManagerResolver,使用一个ReactiveAuthenticationManager实例的仓库,在运行时编辑这些实例:spring-doc.cadn.net.cn

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所示)添加和移除元素。spring-doc.cadn.net.cn

简单地取任何一个发行者并从其构建一个ReactiveAuthenticationManager是不安全的。 代码应该能够验证来自可信源(例如允许列表中的发行者)的一个发行者。spring-doc.cadn.net.cn