在 Keycloak 中,如果你通过 client_id 和 client_secret 使用 client_credentials 模式获取 Token,返回的 Token 中默认不包含租户信息(tenant info) 是正常现象。下面我来详细解释原因、影响和解决办法。
在 Keycloak 里,客户端签发的 Token 没有租户信息可能由以下原因导致:
- Scope 配置问题 :
Scope用于声明请求访问的资源和权限,会影响Keycloak返回的访问Token信息。若未在授权请求里配置包含租户信息的Scope,访问Token可能就不会包含租户信息。例如在授权请求中添加openid才会生成包含用户身份信息的ID Token。 - Client-Scope 配置缺失 :
Keycloak中的Client-Scope能为每个客户端分配授权范围,直接影响访问Token中的内容。若没有配置包含租户信息的Client-Scope或自定义Mapper,Token可能不会包含租户信息。可通过自定义Scope/Mapper来控制Token中的字段 。
📌 一、问题背景
- 你使用的是:
POST /realms/{realm}/protocol/openid-connect/token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
&client_id={your_client_id}
&client_secret={your_client_secret}
这种方式是典型的 服务间通信(Machine-to-Machine, M2M)模式,Keycloak 返回的 Token 是一个代表客户端身份的 Client Token,而不是用户 Token。
❓ 为啥 Token 中没有租户信息
✅ 原因如下:
| 原因 | 说明 |
|---|---|
1. Client Token 不代表用户 | client_credentials 获取的是客户端身份凭证,与用户无关,自然不会有“租户”上下文 |
| 2. 租户信息通常来自用户属性 | 租户字段通常是用户的一个属性(如 tenant_id),而 client 没有绑定具体用户 |
3. 默认 Mapper 未映射租户字段 | 即使 client 有 service account,也需手动配置 mapper 才能将租户信息写入 token |
🧩 二、Token 类型区分
| Token 来源 | 用户上下文 | 是否带租户信息 | 应用场景 |
|---|---|---|---|
authorization_code | ✅ 有用户 | ✅ 可带租户信息 | 用户登录 |
password (user) | ✅ 有用户 | ✅ 可带租户信息 | 用户名密码登录 |
client_credentials | ❌ 无用户 | ❌ 默认不带租户信息 | 后端服务调用 |
✅ 三、如何让 client_credentials 签发的 Token 包含租户信息?
你可以通过以下方式实现:
🔧 方法一:为 Client 开启 Service Account 并设置 Mapper
- 说明:
Mapper(映射器)是客户端范围映射器(Client scopes -> Mappers)

步骤:
-
在
Keycloak管理控制台中:- 进入对应
Realm - 找到你的
Client(即使用client_id的那个) - 切换到 Service Accounts Settings
- 启用 Service Account Enabled
- 进入对应
-
查看该
Client的Service Account用户:- 页面会显示用户名,例如:
service-account-your-client-id - 你可以给这个用户设置角色、分配属性,包括
tenant_id
- 页面会显示用户名,例如:
-
给这个用户添加自定义属性(如
tenant_id=xxx) -
回到
Client设置:- 菜单选择 Client Scopes > Default Client Scope
- 添加
Mapper,把tenant_id属性映射进token(可以放在ID Token或Access Token中)
-
再次请求
Token,你应该能在token payload中看到tenant_id字段
🔧 方法二:使用 Token Exchange(Token Exchange Grant)
Enable Standard Token Exchange V2 for this client.
如果你需要模拟某个用户的租户上下文,可以先获取用户
Token,再通过Token Exchange换成client的Token,并保留租户信息。

注意: 当前客户端
client需要启用服务账号角色(ServiceAccountsEnabled = true)。
常见的 ClientRepresentation 对象设置如下:
// <auto-generated/>
using Microsoft.Kiota.Abstractions.Serialization;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System;
namespace Keycloak.AuthServices.Sdk.Kiota.Admin.Models;
#pragma warning disable CS1591
public class ClientRepresentation : IAdditionalDataHolder, IParsable
#pragma warning restore CS1591
---
// 创建新客户端
var client = new ClientRepresentation
{
Name = authConfig.ClientName,
Description = authConfig.Description,
ClientId = authConfig.ClientId,
Secret = authConfig.ClientSecret,
//RedirectUris = ["https://localhost:5001/*"],
//WebOrigins = ["https://localhost:5001"],
Enabled = true,
PublicClient = false,
Protocol = "openid-connect",
// 允许您向 Keycloak 验证此客户端并检索专用于此客户端的访问令牌。根据 OAuth2 规范,这可以支持此客户端的'客户端凭据授权'。
ServiceAccountsEnabled = true,
// 这启用了基于标准 OpenID Connect 重定向的身份验证和授权代码。根据 OpenID Connect 或 OAuth2 规范,这启用了对此客户端的'授权代码流'的支持。
StandardFlowEnabled = true,
};
关于 ClientRepresentation 更多信息,请查看:
请求示例:
POST /realms/{realm}/protocol/openid-connect/token
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&subject_token={user-access-token}
&subject_token_type=urn:ietf:params:oauth:token-type:access_token
&client_id={your_client_id}
&client_secret={your_client_secret}
这样获得的 Token 可以继承用户中的租户信息(需要配置好 Scope 和 Mapper)。
🔧 方法三:自定义协议 Mapper(推荐)
你可以编写或配置一个 Protocol Mapper,让 client 的 Token 主动带上租户信息:
步骤:
- 进入
Client的 Mappers 页面 - 创建一个新 Mapper:
- Name:
tenant_id - Mapper Type:
User Attribute - User Attribute:
tenant_id - Token Claim Name:
tenant_id - Claim Value Type:
String
- Name:
- 保存后,再次请求
Token,应该能看到tenant_id字段
📦 四、Token 示例对比
- 默认
Token(client_credentials)
{
"jti": "abc123",
"exp": 1718000000,
"nbf": 0,
"iss": "https://keycloak.example.com/auth/realms/myrealm",
"aud": "your-client-id",
"sub": "a1b2c3d4-e5f6-7890-g1h2-i3j4k5l6m7n8",
"typ": "Bearer",
"azp": "your-client-id"
}
- 带租户信息的
Token(配置好Mapper后)
{
"jti": "abc123",
"exp": 1718000000,
"nbf": 0,
"iss": "https://keycloak.example.com/auth/realms/myrealm",
"aud": "your-client-id",
"sub": "a1b2c3d4-e5f6-7890-g1h2-i3j4k5l6m7n8",
"typ": "Bearer",
"azp": "your-client-id",
"tenant_id": "tenant_12345"
}
此时 client 的 token 就会多一个参数 tenant_id,同理其他参数也可以自定义映射(Mapper)。
✅ 五、总结
| 问题 | 原因 | 解决方案 |
|---|---|---|
client_credentials 获取的 Token 没有 tenant 信息 | client token 默认无用户上下文,不自动带租户字段 | 配置 Service Account + Mapper |
如何让 Token 包含租户信息? | 租户字段通常属于用户属性 | 给 Service Account 用户加属性并映射 |
是否支持 client 级别的租户字段? | 支持,但需自定义 Mapper | 创建 User Attribute Mapper |
5901

被折叠的 条评论
为什么被折叠?



