Keycloak与Angular+REST集成疑问及角色为空问题排查
一、双客户端方案是否为最优?能否用单个机密客户端适配前后端?
首先明确:双客户端方案是Keycloak针对SPA+后端API场景的推荐最佳实践,绝对不建议用单个机密客户端同时适配前后端,核心原因如下:
- Angular作为单页应用(SPA)属于公开客户端,它运行在浏览器环境中,根本无法安全存储机密客户端的密钥——任何嵌入在前端代码里的密钥,都能通过浏览器开发者工具、反编译等方式被轻易获取,这会直接导致你的客户端身份被冒充,带来严重的安全风险。
- 双客户端的分工更清晰合理:
- 公开客户端(frontend-dev):专门负责前端认证流程,处理OIDC授权码流程(配合PKCE防止授权码劫持),不需要密钥,完全适配浏览器的安全环境。
- Bearer-only客户端(backend-dev):专门用于后端API的令牌校验,只接受JWT令牌并验证其合法性,不需要处理前端登录跳转,用密钥确保只有合法的令牌来源能通过校验。
强行用单个机密客户端适配前后端,完全违背了OAuth2/OIDC的安全设计原则,属于典型的不安全实践。
二、后端Spring Boot无法获取角色导致403的问题排查与解决
结合你提供的配置和环境(Keycloak 3.4.12、Spring Boot 1.5.18、Keycloak Spring Adapter 3.4.3),出现@RolesAllowed失效、角色为空的情况,大概率是以下几个原因:
1. 角色映射与权限前缀不匹配
你配置了SimpleAuthorityMapper,这个类默认会把Keycloak返回的角色名转换为大写并添加ROLE_前缀。比如:
- 如果Keycloak中给用户分配的客户端角色是
admin,Spring Security中实际生成的权限会是ROLE_ADMIN。 - 但如果你代码中用的是
@RolesAllowed("admin"),就会因为权限不匹配返回403。
解决方法:
要么把注解改成@RolesAllowed("ROLE_ADMIN"),要么调整SimpleAuthorityMapper的配置,取消前缀或关闭大写转换:
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper() {{ setPrefix(""); // 去掉ROLE_前缀 setConvertToUpperCase(false); // 不强制转换为大写 }});
2. 未正确使用客户端角色映射
你设置了keycloak.use-resource-role-mappings=true,这意味着Spring Security只会读取当前客户端(backend-dev)的角色,而不是Realm级别的角色。请确认:
- 你给用户分配的是
backend-dev客户端下的角色,而非Realm角色。 - 在Keycloak控制台的
backend-dev客户端设置中,检查Scope标签页,确保需要的角色被包含在客户端可用范围内(或者直接开启Full Scope Allowed)。
3. JWT令牌中未包含客户端角色
可以用jwt.io解析前端传递给后端的Bearer令牌,查看resource_access字段是否包含backend-dev的角色信息,正确的结构应该是:
"resource_access": { "backend-dev": { "roles": ["admin", "user"] } }
如果没有这个结构,说明Keycloak没有将客户端角色注入到令牌中,需要检查:
- 用户是否确实被分配了
backend-dev的角色。 backend-dev客户端的Settings标签页中,Access Type是否设置为bearer-only(你的配置是对的,但需要确认控制台设置一致)。
4. Keycloak适配器与Server版本不兼容
你使用的Keycloak Server是3.4.12,但Spring Adapter是3.4.3,版本差异较大,很可能存在兼容性问题。Keycloak的适配器版本建议与Server版本完全一致,否则容易出现令牌解析、角色映射等异常。
解决方法:将Spring Boot的Keycloak依赖版本升级到3.4.12,比如在pom.xml中:
<dependency> <groupId>org.keycloak</groupId> <artifactId>keycloak-spring-boot-starter</artifactId> <version>3.4.12.Final</version> </dependency>
5. 手动获取角色的方式错误
你当前手动获取角色的代码是从AccessToken中读取,但开启use-resource-role-mappings=true后,应该读取resourceAccess中的客户端角色,而非realmAccess。正确的获取方式示例:
AccessToken token = ((KeycloakSecurityContext)((KeycloakAuthenticationToken) request.getUserPrincipal()).getCredentials()).getToken(); // 获取backend-dev客户端的角色列表 List<String> clientRoles = token.getResourceAccess("backend-dev").getRoles();
如果你之前读取的是token.getRealmAccess().getRoles(),那自然会得到空数组,因为你设置了只使用客户端角色映射。
内容的提问来源于stack exchange,提问作者aquero

