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官方旧Adapter方案(仅适配Spring Boot 2.x,官方已停止维护):直接删除
- 第二步:补全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相关字段。
- Realm角色映射器:类型选择
- 第三步:实现角色到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
相关产品推荐
相关产品推荐

