基于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令牌
这是最稳妥、符合安全规范的方案:
- 配置SPA专属Client:在Keycloak中为React SPA创建public类型的Client(开启PKCE),并确保该SPA Client拥有向两个API Client发起Token Exchange的权限(在API Client的「Permissions」中添加SPA Client的访问权限)。
- 完成用户初始登录:用keycloak-js初始化SPA Client,调用
login()完成用户认证,获取SPA自身的访问令牌作为主令牌。 - 交换获取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 });
- 交换获取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 });
- 令牌管理:将两个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
相关产品推荐
相关产品推荐

