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 是不安全的。頒發者應該是程式碼可以從可信來源驗證的,例如允許的頒發者列表。