在 Keycloak
中,自定义协议 Mapper(Custom Protocol Mapper) 是一种非常强大的机制,它允许你 动态地将用户属性、客户端属性或其他信息注入到 Token(ID Token 或 Access Token)中。
🧠 为什么需要自定义协议 Mapper?
Keycloak
默认的(访问) Token
只包含一些基础字段,如 sub
、preferred_username
、email
等。但在实际业务中,我们往往希望:
- 把用户的 租户 ID(tenant_id)
- 用户所属的 组织名称(organization_name)
- 用户的角色权限(
roles
) - 客户端相关的元数据(
client_metadata
) - 自定义业务字段(如部门、职位、区域等)
通过 自定义协议 Mapper,你可以灵活控制(访问) Token
的内容,满足后端服务 鉴权、路由、多租户管理 等需求。
✅ 场景举例
场景 | 描述 |
---|---|
多租户系统 | 将用户的 tenant_id 注入 Token,便于下游服务识别归属租户 |
微服务认证 | 在 Token 中添加 department , location , role_level 等字段用于访问控制 |
第三方集成 | 向特定 client 发放 token 时添加 integration_key 或 api_key |
动态权限 | 根据用户角色或属性生成 permissions 字段 |
🔧 配置步骤:如何创建一个自定义协议 Mapper
以下以向 Token
添加 tenant_id
字段为例,介绍如何配置一个自定义协议 Mapper
。
步骤 1:进入 Client Scopes 页面
- 登录
Keycloak
管理后台 - 进入对应
Realm
- 左侧菜单点击 Client Scopes
- 选择你要绑定
Mapper
的Scope
(通常为profile
或your_client
的default scope
)
步骤 2:添加新的 Mapper
- 点击页面上的 Add mapper > By configuration
- 选择
Mapper
类型:- 推荐使用 User Attribute
Mapper
(将用户属性注入Token
)
- 推荐使用 User Attribute
步骤 3:填写 Mapper 表单(以 User Attribute 为例)
字段 | 值 | 说明 |
---|---|---|
Name | tenant_id |
Mapper 名称(任意命名) |
Mapper Type | User Attribute |
表示从用户属性中提取数据 |
User Attribute | tenant_id |
用户实体中的属性名(需提前设置好) |
Token Claim Name | tenant_id |
最终在 Token 中的字段名 |
Claim Value Type | String / JSON / Long 等 |
数据类型 |
Add to ID token | ✅ Yes | 是否写入 ID Token |
Add to access token | ✅ Yes | 是否写入 Access Token |
Friendly Name | 可选 | 显示用名称 |
Description | 可选 | 描述 |
✅ 保存后,该字段将在后续签发的 Token
中自动出现。
📦 示例:Token 输出效果
- 假设某用户有如下属性:
{
"username": "john_doe",
"attributes": {
"tenant_id": "tenant_001",
"department": "IT"
}
}
- 配置好
Mapper
后,获取的Token
将包含:
{
"exp": 1718000000,
"iat": 1718000000,
"jti": "abc123...",
"iss": "https://keycloak.example.com/auth/realms/myrealm",
"aud": "my-client",
"sub": "user-uuid",
"tenant_id": "tenant_001", ← 新增字段
"department": "IT" ← 可选新增字段
}
🧩 其他常见 Mapper 类型(可选)
除了 User Attribute
,还有多种类型的 Mapper
可供使用:
Mapper 类型 | 用途说明 |
---|---|
User Attribute | 从用户属性映射字段 |
Role Name | 显示当前用户的所有角色 |
Group Membership | 显示用户所属组(group) |
Hardcoded claim | 固定值字段(如 environment=production) |
Script Mapper | 使用 JavaScript 脚本动态构造字段(高级) |
Audience | 添加 audience 字段 |
Attribute Statement | SAML 协议相关 |
如果你需要更复杂的逻辑(比如根据用户角色动态生成权限列表),可以使用 Script Mapper。
🧪 Script Mapper 示例(高级用法)
场景:根据用户角色生成权限字段 permissions
1. 创建 Script Mapper
- Mapper Type:
Script Mapper
- Name:
permissions-mapper
- Script:
// 获取用户所有角色
var roles = user.getRealmRoleMappings();
var permissions = [];
if (roles) {
for (var i = 0; i < roles.size(); i++) {
var role = roles.get(i);
if (role.getName().startsWith("perm_")) {
permissions.push(role.getName().replace("perm_", ""));
}
}
}
$token.setOtherClaims("permissions", permissions);
2. 效果(Token 中新增字段)
{
"permissions": ["read:data", "write:data", "delete:user"]
}
🧰 如何给 Service Account 用户添加属性(client_credentials 模式)
如果你使用的是 client_credentials
模式,你需要给 Service Account 用户 添加属性:
步骤:
- 打开你的
Client
设置页 - 点击 Service Account Settings
- 开启 Service Account Enabled
- 查看
Service Account
用户(通常是service-account-{client-id}
) - 编辑该用户,在
Attributes
中添加tenant_id
:tenant_001
- 再次请求
Token
,你应该能在Token
中看到这个字段
🧩 Mapper 的作用范围
Mapper
可以绑定到不同作用域:
作用域 | 说明 |
---|---|
Realm-Level Mappers |
对整个 Realm 下的所有 Token 生效 |
Client-Level Mappers |
仅对该 Client 签发的 Token 生效 |
Client Scope Mappers |
绑定到某个 Scope ,只有请求该 Scope 时才生效 |
例如:
- 你想让所有
Client
都带上tenant_id
→ 放到 Realm-Level Mapper - 你只想让特定
Client
带上tenant_id
→ 放到 Client-Level Mapper - 你只想在某些
Scope
下带tenant_id
→ 放到 Client Scope Mapper
🛠 常见问题排查
问题 | 解决方法 |
---|---|
Token 中没有新字段 |
检查是否已启用 Mapper 并正确绑定 Scope |
用户属性为空 | 确保用户确实设置了该属性 |
Service Account 没有属性 |
确认是 Service Account 用户本身设置了属性 |
Mapper 不生效 |
清除缓存并重新获取 Token |
Mapper 写入了错误位置 |
检查是否勾选了 “Add to ID Token” 和 “Add to Access Token” |
✅ 总结
Mapper 类型 | 适用场景 | 是否推荐 |
---|---|---|
User Attribute |
将用户属性注入 Token |
✅ 推荐 |
Role Name |
显示用户角色 | ✅ 推荐 |
Group Membership |
显示用户所属组 | ✅ 推荐 |
Hardcoded Claim |
固定字段 | ✅ 快速调试 |
Script Mapper |
动态生成字段(如权限) | ✅ 高级用法 |
Audience |
添加目标受众 | ✅ 安全增强 |
如果你能提供以下信息,我可以为你写出完整的配置指南:
Realm
名称Client ID
- 想要添加的字段名(如
tenant_id
、org_code
等) - 你是想从
user
用户属性还是客户端属性取值 - 是否使用
client_credentials
模式
欢迎继续提问,我将为你定制完整的配置方案!