You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Keycloak的React SPA单登录获取差异化JWT令牌对接双API方案咨询

技术方案建议

一、同一Realm下为两个API创建独立Client的思路完全正确

两个API对JWT声明的要求差异极大,通过分别创建Client可以在Keycloak层面独立配置令牌规则,完美匹配各自需求:

  • sub声明配置:API1的Client保留默认逻辑,用user_id作为sub;API2的Client添加自定义Mapper,将user_name映射为sub字段。
  • exp过期时间配置:在每个Client的「Advanced Settings」里设置不同的Access Token Lifespan——API1设为极长周期(如10年,模拟无限制),API2设为10分钟。
  • aud、scp声明配置:aud默认就是Client ID,自然区分两个API;scp则通过配置Client的专属Roles定义不同权限范围,满足差异要求。

二、一次登录获取双令牌的实现方案

基于keycloak-js和Token Exchange机制,推荐以下两种可行方案:

方案1:初始登录拿主令牌,再通过Token Exchange获取双API令牌

这是最稳妥、符合安全规范的方案:

  1. 配置SPA专属Client:在Keycloak中为React SPA创建public类型的Client(开启PKCE),并确保该SPA Client拥有向两个API Client发起Token Exchange的权限(在API Client的「Permissions」中添加SPA Client的访问权限)。
  2. 完成用户初始登录:用keycloak-js初始化SPA Client,调用login()完成用户认证,获取SPA自身的访问令牌作为主令牌。
  3. 交换获取API1令牌:调用keycloak-js的tokenExchange()方法,传入API1的Client ID、专属scope等参数,示例代码:
const api1Token = await keycloak.tokenExchange({
  client_id: 'api1-client-id',
  scope: 'api1-required-scope',
  requested_subject: keycloak.subject
});
  1. 交换获取API2令牌:同理传入API2的Client ID,注意API2已通过Mapper将user_name映射为sub,调用方式类似:
const api2Token = await keycloak.tokenExchange({
  client_id: 'api2-client-id',
  scope: 'api2-required-scope',
  requested_subject: keycloak.profile.username
});
  1. 令牌管理:将两个API的令牌分别存储(如sessionStorage),请求对应API时携带各自令牌;API2令牌过期后,可再次通过Token Exchange刷新(无需用户重新登录)。

方案2:初始登录时请求多Client Scope(兼容性稍差)

部分新版本Keycloak允许在初始登录时指定多个Client的scope(格式为client-id/scope),登录后可结合Token Exchange优化流程,但这种方式对Keycloak版本有要求,优先推荐方案1。

三、关键配置注意事项

  • API Client类型:两个API Client需设为confidential或bearer-only,同时开启「Service Accounts Enabled」以支持Token Exchange。
  • Mapper配置细节:在API2的Client中添加「User Property」类型的Mapper,将username映射为sub字段,确保令牌sub声明符合要求。
  • Token Exchange权限:在Realm的「Token Exchange」设置中,允许SPA Client与两个API Client之间的令牌交换,避免权限报错。

内容的提问来源于stack exchange,提问作者Anton Fil

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 05:25:52