在 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模式
欢迎继续提问,我将为你定制完整的配置方案!
672

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



