You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何区分Keycloak手动分配角色与映射角色并清理映射角色?

解决Keycloak外部IdP映射角色残留问题

要实现清除仅由外部IdP映射的角色、保留手动分配角色(含同时被手动+映射的角色),核心是利用Keycloak中角色关联的origin属性区分来源:手动分配的角色关联origin为空或Realm ID,而外部IdP映射的角色关联origin会被设置为对应IdP的唯一标识。以下提供两种可行方案:

方案一:使用脚本映射器(无需编码,快速实现)

Keycloak支持通过Groovy脚本自定义映射逻辑,适合快速验证和轻量场景:

  1. 启用脚本支持:进入Realm设置 → 选择「Scripting」标签 → 开启「Enable Scripting」。
  2. 添加自定义脚本映射器:
    • 进入目标外部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映射器:

  1. 编写映射器类:
    继承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";
        }
    }
    
  2. 打包部署:

    • 将代码打包为JAR,放入Keycloak的providers目录。
    • 执行kc.sh build(Quarkus版本)或对应WildFly版本的部署命令安装扩展。
    • 重启Keycloak后,在外部IdP的Mappers中即可选择该自定义映射器。

关键逻辑说明

  • 仅删除origin为目标IdP的角色关联:手动分配的角色关联origin为空,即使角色同时被手动+映射,删除映射关联后,手动关联仍会保留,角色不会被移除。
  • 新映射的角色需指定origin为当前IdP:确保后续清理时能准确识别哪些是映射来的角色。

内容的提问来源于stack exchange,提问作者R. Aubel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 11:27:32