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

Spring Boot集成Keycloak无法获取角色 @RolesAllowed报403

问题根因

角色不生效的核心原因是配置混用了两套完全独立的Keycloak集成方案,且Keycloak客户端角色映射配置缺失:

  • 代码中引入了Keycloak官方旧版Spring Boot Adapter,继承KeycloakWebSecurityConfigurerAdapter并配置了KeycloakAuthenticationProvider,这套逻辑会主动解析Token中的realm_access、resource_access字段提取Keycloak角色。
  • 但HttpSecurity配置中添加了.oauth2Login()配置,实际认证流程走的是Spring Security原生OIDC客户端逻辑,从日志打印的Principal类型为OAuth2AuthenticationToken、用户类为DefaultOidcUser可以确认,之前配置的KeycloakAuthenticationProvider根本没有生效,自然不会解析Keycloak自定义角色。
  • 日志打印的ID Token属性中没有realm_access、resource_access字段,说明Keycloak客户端没有配置角色映射器,角色信息根本没有被写入返回的Token。
  • 额外配置问题:/work_queue/**路径被配置为permitAll(),该规则会绕过路径层面的认证校验,仅方法层面的注解校验会生效,不符合权限控制的常规设计。
修复步骤
  • 第一步:统一集成方案,禁止两套逻辑混用
    • 如果选择Keycloak官方旧Adapter方案(仅适配Spring Boot 2.x,官方已停止维护):直接删除http.oauth2Login()相关配置,按照Keycloak Adapter规范配置登录跳转逻辑即可。
    • 如果选择Spring Security原生OAuth2 Client方案(推荐,适配所有Spring Boot版本):删除KeycloakWebSecurityConfigurerAdapter相关继承配置、移除Keycloak Adapter依赖,自行实现OIDC用户的权限映射逻辑。
  • 第二步:补全Keycloak客户端角色映射配置
    进入Keycloak管理控制台,找到当前使用的myapp-app客户端,打开Mappers配置页,添加/确认以下两个映射器存在:
    • Realm角色映射器:类型选择User Realm Role,配置将Realm角色写入ID Token和Access Token,默认Claim路径为realm_access.roles
    • 客户端角色映射器:类型选择User Client Role,配置将当前客户端的角色写入ID Token和Access Token,默认Claim路径为resource_access.myapp-app.roles
      配置完成后重新登录,打印用户属性时即可看到roles相关字段。
  • 第三步:实现角色到Spring Security权限的映射
    如果使用Spring Security原生OIDC方案,自定义GrantedAuthoritiesMapper,从OidcUser属性中提取Realm角色和客户端角色列表,转换为GrantedAuthority集合。注意SimpleAuthorityMapper默认会给角色名添加ROLE_前缀并转为大写,要保证@RolesAllowed注解中配置的角色名和最终生成的权限字符串完全匹配,避免因大小写、前缀不匹配导致403。
    参考映射逻辑代码:
    public class KeycloakAuthoritiesMapper implements GrantedAuthoritiesMapper {
        private static final String REALM_ROLES_CLAIM = "realm_access";
        private static final String RESOURCE_ROLES_CLAIM = "resource_access";
        private static final String ROLES_FIELD = "roles";
        private static final String ROLE_PREFIX = "ROLE_";
    
        @Override
        public Collection<? extends GrantedAuthority> mapAuthorities(Collection<? extends GrantedAuthority> authorities) {
            Set<GrantedAuthority> mappedAuths = new HashSet<>(authorities);
            // 从OidcUser上下文提取对应Claim中的角色,拼接ROLE_前缀后加入mappedAuths即可
            return mappedAuths;
        }
    }
    
  • 第四步:调整路径权限规则,将需要权限控制的/work_queue/**路径从permitAll()修改为authenticated(),避免未认证用户被直接放行。
Keycloak Realm角色与客户端角色差异
  • Realm角色:属于Realm层级的全局角色,在整个Realm范围内生效,用户被分配Realm角色后,该Realm下所有接入的客户端都可以读取到该角色,适合定义跨系统通用的权限身份,比如全局管理员、普通用户这类通用角色。
  • 客户端角色:属于单个客户端私有的角色,仅对所属客户端生效,默认不会被其他客户端读取,适合定义单个应用内部的细粒度权限,不会占用全局角色命名空间,适合应用内部专属的权限控制。

内容的提问来源于stack exchange,提问作者Woodsman

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 13:48:18