如何区分Keycloak手动分配角色与映射角色并清理映射角色?
解决Keycloak外部IdP映射角色残留问题
要实现清除仅由外部IdP映射的角色、保留手动分配角色(含同时被手动+映射的角色),核心是利用Keycloak中角色关联的origin属性区分来源:手动分配的角色关联origin为空或Realm ID,而外部IdP映射的角色关联origin会被设置为对应IdP的唯一标识。以下提供两种可行方案:
方案一:使用脚本映射器(无需编码,快速实现)
Keycloak支持通过Groovy脚本自定义映射逻辑,适合快速验证和轻量场景:
- 启用脚本支持:进入Realm设置 → 选择「Scripting」标签 → 开启「Enable Scripting」。
- 添加自定义脚本映射器:
- 进入目标外部IdP的「Mappers」页面 → 点击「Create」。
- 类型选择「Script Mapper」,填写名称(如
Sync External Roles with Cleanup)。 - 在「Script」框中填入以下逻辑:
// 获取当前身份提供商ID def idpId = identityProvider.getId() def userRoleManager = session.userRoleManager def realm = session.realm // 清除当前IdP之前映射的角色关联(仅删除origin为该IdP的记录) def existingMappings = userRoleManager.getRoleMappings(user) existingMappings.each { mapping -> if (mapping.getOrigin() == idpId) { userRoleManager.deleteRoleMapping(user, mapping.getRole(), idpId) } } // 从外部IdP获取角色并映射到Keycloak角色(需根据实际外部IdP返回的属性调整) // 示例:假设外部IdP将角色放在用户属性"external_roles"中 def externalRoles = federatedIdentity.getAttribute("external_roles") ?: [] externalRoles.each { extRoleName -> def targetRole = realm.getRole(extRoleName) if (targetRole) { // 映射时指定origin为当前IdP,方便后续清理 userRoleManager.grantRole(user, targetRole, idpId) } } - 保存映射器,设置「Sync Mode Override」为「Force」。
方案二:自定义Java映射器(适合复杂场景/生产环境)
如果需要更灵活的逻辑或避免脚本依赖,可开发自定义Java映射器:
编写映射器类:
继承AbstractIdentityProviderMapper,重写角色同步逻辑,核心是通过origin过滤并删除旧映射:import org.keycloak.models.*; import org.keycloak.broker.provider.AbstractIdentityProviderMapper; import org.keycloak.broker.provider.BrokeredIdentityContext; public class CleanupExternalRoleMapper extends AbstractIdentityProviderMapper { public static final String PROVIDER_ID = "cleanup-external-role-mapper"; @Override public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, UserModel user, BrokeredIdentityContext context) { String idpId = context.getIdpConfig().getId(); UserRoleManager userRoleManager = session.userRoleManager(); // 删除当前IdP之前映射的角色关联 userRoleManager.getRoleMappings(user).forEach(mapping -> { if (idpId.equals(mapping.getOrigin())) { userRoleManager.deleteRoleMapping(user, mapping.getRole(), idpId); } }); // 执行新角色映射逻辑(示例:从context获取外部角色并映射) context.getRoles().forEach(extRole -> { RoleModel targetRole = realm.getRole(extRole); if (targetRole != null) { userRoleManager.grantRole(user, targetRole, idpId); } }); } @Override public String getId() { return PROVIDER_ID; } @Override public String getDisplayCategory() { return ROLE_MAPPER_CATEGORY; } @Override public String getDisplayType() { return "Cleanup External Role to Role"; } }打包部署:
- 将代码打包为JAR,放入Keycloak的
providers目录。 - 执行
kc.sh build(Quarkus版本)或对应WildFly版本的部署命令安装扩展。 - 重启Keycloak后,在外部IdP的Mappers中即可选择该自定义映射器。
- 将代码打包为JAR,放入Keycloak的
关键逻辑说明
- 仅删除
origin为目标IdP的角色关联:手动分配的角色关联origin为空,即使角色同时被手动+映射,删除映射关联后,手动关联仍会保留,角色不会被移除。 - 新映射的角色需指定
origin为当前IdP:确保后续清理时能准确识别哪些是映射来的角色。
内容的提问来源于stack exchange,提问作者R. Aubel
相关产品推荐
相关产品推荐

