Spring Security 6+Keycloak基于Realm角色的权限访问失效排查
核心错误点
1. JWT角色提取逻辑完全错误
从你的错误日志能看到,用户角色存储在JWT的顶级Roles字段(Roles=[cafeteria, obw, ROLE_obw, courseplanning]),但你的authoritiesConverter代码一直在读取realm_access.roles,以及spring-addons-confidential/spring-addons-public这两个和你业务无关的客户端角色,完全没处理实际存在的Roles字段,导致Spring Security根本获取不到用户的角色权限,直接返回403。
另外,你的实际客户端ID是po-ms(日志里aud=[po-ms]、azp=po-ms),转换器里硬编码的客户端名称完全不对,这部分代码无效。
2. hasRole()的前缀匹配问题
Spring Security的hasRole("obw")方法会自动匹配带ROLE_前缀的权限(等价于hasAuthority("ROLE_obw")),但如果转换器直接把obw转为SimpleGrantedAuthority,权限名称是obw,和hasRole()要求的ROLE_obw不匹配,也会触发403。
修正步骤
1. 修复JWT角色转换器
修改authoritiesConverter,正确读取JWT中的角色字段,并处理前缀适配:
@SuppressWarnings("unchecked") @Bean public Jwt2AuthoritiesConverter authoritiesConverter() { return jwt -> { // 读取JWT中实际存储角色的顶级Roles字段 final var roles = (Collection<String>) jwt.getClaims().getOrDefault("Roles", List.of()); // 为角色统一添加ROLE_前缀,适配hasRole()方法;若使用hasAuthority可跳过此步骤 return roles.stream() // 避免重复添加前缀(比如你的角色中已有ROLE_obw) .map(role -> role.startsWith("ROLE_") ? role : "ROLE_" + role) .map(SimpleGrantedAuthority::new) .toList(); }; }
2. 调整权限规则(二选一)
- 若保留
hasAnyRole("obw"):上面的转换器已添加前缀,无需修改filterChain中的权限配置; - 若不想添加前缀:将
auth.requestMatchers("/thirdpath/**").hasAnyRole("obw")改为auth.requestMatchers("/thirdpath/**").hasAnyAuthority("obw")。
3. 验证Keycloak配置
从你提供的角色配置和令牌截图来看,用户确实拥有obw角色,但要确保Keycloak的客户端(po-ms)角色映射器配置正确,保证角色被写入JWT的Roles字段(如果想改用realm_access.roles,需要在Keycloak中添加对应的映射器,将Realm角色写入该字段)。
验证方法
修改后重启服务,再次请求接口,查看日志中的Granted Authorities,应该能看到ROLE_obw、ROLE_cafeteria这类权限,此时权限校验即可正常通过。
内容的提问来源于stack exchange,提问作者Octavia

