Keycloak v0.17旧版令牌交换:目标端交换是否为良好实践?
背景说明
假设有两个机密客户端:client_source、client_target,分别对应各自的后端服务,client_source会向client_target转发请求。
用户拥有以下角色:client_source -> [client_source_role_1,client_source_role_2], client_target -> [client_target_role_1]
用户通过client_source认证后,获得的访问令牌结构如下:
{ ... "sub": "d9385e2f-e64c-420a-a231-80aaddfad734", "typ": "Bearer", "azp": "client_source", "resource_access": { "client_source ": { "roles": [ "client_source_role_1", "client_source_role_2" ] } }, "scope": "profile email", ... }
现在需要将client_source的令牌交换为client_target的令牌,以获取用户在client_target中的角色权限。
方案一:源客户端侧执行跨客户端令牌交换
操作方式
配置允许client_source与client_target之间进行令牌交换,并为client_source授予交换权限,在client_source侧发起令牌交换请求。
交换请求
curl --request POST \ 'http://localhost:8085/auth/realms/<my realm>/protocol/openid-connect/token' \ --header 'Content-Type: application/x-www-form-urlencoded' \ --data-urlencode 'client_id=<client_source client id>' \ --data-urlencode 'client_secret=<client_source client secret>' \ --data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \ --data-urlencode 'subject_token=<client_source access token>' \ --data-urlencode 'subject_token_type=urn:ietf:params:oauth:token-type:access_token' \ --data-urlencode 'requested_token_type=urn:ietf:params:oauth:token-type:refresh_token' \ --data-urlencode 'audience=<client_target client id>'
返回令牌结构
{ ... "sub": "d9385e2f-e64c-420a-a231-80aaddfad734", "typ": "Bearer", "azp": "client_source", "aud": "client_target", "resource_access": { "client_target ": { "roles": [ "client_target_role_1" ] } }, "scope": "profile email", ... }
存在问题
交换后的令牌无法使用client_target的client_id/client_secret刷新,因为令牌的azp(授权方)是client_source,与刷新时使用的客户端ID不匹配,导致刷新令牌绑定到了错误的客户端。
方案二:目标客户端侧执行令牌交换
操作方式
在client_target侧发起令牌交换请求,无需额外配置客户端间的权限策略(默认允许自令牌交换)。
交换请求
curl --request POST \ 'http://localhost:8085/auth/realms/<my realm>/protocol/openid-connect/token' \ --header 'Content-Type: application/x-www-form-urlencoded' \ --data-urlencode 'client_id=<client_source client id>' \ --data-urlencode 'client_secret=<client_source client secret>' \ --data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \ --data-urlencode 'subject_token=<client_source access token>' \ --data-urlencode 'subject_token_type=urn:ietf:params:oauth:token-type:access_token' \ --data-urlencode 'requested_token_type=urn:ietf:params:oauth:token-type:refresh_token'
返回令牌结构
{ ... "sub": "d9385e2f-e64c-420a-a231-80aaddfad734", "typ": "Bearer", "azp": "client_target", "resource_access": { "client_target ": { "roles": [ "client_target_role_1" ] } }, "scope": "profile email", "sid": "da75a8c5-b3f4-4e58-b7ed-e5fb2c190235", ... }
优势
client_target应用无需知晓client_source的客户端ID,可通过自身Keycloak凭证完成交换,流程更便捷。
问题与分析
问:若源令牌仅包含源客户端所需信息且体积较小,在目标端进行令牌交换是否为良好实践?
结论:是可行的实践,但需结合场景权衡
安全性层面:
- 方案二中
client_target直接处理源令牌,减少了client_source与client_target之间的权限配置依赖,降低了跨客户端权限泄露的风险。只要源令牌本身没有敏感信息(已满足“仅包含源客户端所需信息”的前提),安全性不会打折扣。 - 交换后的令牌
azp为client_target,可正常使用自身凭证刷新,避免了方案一的刷新令牌绑定问题,减少了后续运维隐患。
- 方案二中
可维护性层面:
- 无需配置跨客户端令牌交换权限,减少Keycloak后台的配置工作,降低了客户端间的耦合度。后续新增或调整客户端时,无需同步修改权限策略。
client_target自主完成令牌交换,逻辑闭环,便于排查问题(比如令牌刷新失败、权限异常等问题可集中在client_target侧定位)。
性能层面:
- 源令牌体积小,交换过程的网络开销和Keycloak的处理成本都很低,不会带来明显性能损耗。
综上,在源令牌信息精简、无敏感数据的前提下,目标端执行令牌交换是高效且合理的实践,既解决了方案一的刷新令牌问题,又简化了配置和依赖关系。
内容的提问来源于stack exchange,提问作者HydraMazay

