OAuth 2.0 Resource Server Multi-tenancy

Multi-tenancy

当验证 bearer token 有多种策略(以某种租户标识符为键)时,资源服务器被认为是多租户。

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

在每种情况下,都需要做两件事,并且如何选择做这两件事存在权衡利弊:

  1. Resolve the tenant.

  2. Propagate the tenant.

Resolving the Tenant By 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 仅在发送了具有相应发行者的第一个请求时才实例化。这允许应用程序启动独立于这些授权服务器是否启动和可用。

Dynamic Tenants

您可能不想在每次添加新租户时都重新启动应用程序。在这种情况下,您可以使用 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
    }
}

在这种情况下,您使用获取 ReactiveAuthenticationManager 并传递给发行者给定的策略来构建 JwtIssuerReactiveAuthenticationManagerResolver。此方法允许我们在运行时添加和移除存储库元素(在先前代码段中显示为 Map)。

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