OAuth 2.0 资源服务器多租户

本站(springdoc.cn)中的内容来源于 spring.io ,原始版权归属于 spring.io。由 springdoc.cn 进行翻译,整理。可供个人学习、研究,未经许可,不得进行任何转载、商用或与之相关的行为。 商标声明:Spring 是 Pivotal Software, Inc. 在美国以及其他国家的商标。

多租户

当有多种策略来验证由一些租户标识符为关键的 bearer token 时,一个资源服务器被认为是多租户。

例如,你的资源服务器可以接受来自两个不同的授权服务器的 bearer token。另外,你的授权服务器也可以代表多个发行者(issuer)。

在每一种情况下,都需要做两件事,而权衡则与你选择如何做这两件事有关。

  1. 解析租户。

  2. 传播租户。

通过 Claim 解析租户

区分租户的一个方法是通 issuer claim。由于 issuer claim 伴随着签名的 JWT,你可以通过 JwtIssuerReactiveAuthenticationManagerResolver 来实现。

  • Java

  • Kotlin

JwtIssuerReactiveAuthenticationManagerResolver authenticationManagerResolver = new JwtIssuerReactiveAuthenticationManagerResolver
    ("https://idp.example.org/issuerOne", "https://idp.example.org/issuerTwo");

http
    .authorizeExchange(exchanges -> exchanges
        .anyExchange().authenticated()
    )
    .oauth2ResourceServer(oauth2 -> oauth2
        .authenticationManagerResolver(authenticationManagerResolver)
    );
val customAuthenticationManagerResolver = JwtIssuerReactiveAuthenticationManagerResolver("https://idp.example.org/issuerOne", "https://idp.example.org/issuerTwo")

return http {
    authorizeExchange {
        authorize(anyExchange, authenticated)
    }
    oauth2ResourceServer {
        authenticationManagerResolver = customAuthenticationManagerResolver
    }
}

这很好,因为 issuer 端点的加载是懒加载的。事实上,相应的 JwtReactiveAuthenticationManager 只有在发送第一个请求的时候才会被实例化。这使得应用程序的启动与这些授权服务器的启动和可用性无关。

动态租户

你可能不想在每次添加新租户时都重启应用程序。在这种情况下,你可以用一个 ReactiveAuthenticationManager 实例 repository 来配置 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。这种方法可以让我们在运行时从repository(在前面的片段中显示为 Map)中添加和删除元素。

简单地使用任何 issuer 并从中构建一个 ReactiveAuthenticationManager 是不安全的。issuer 应该是代码可以从可信的来源验证的,比如允许的 issuer 列表。