基于Keycloak的OAuth2资源服务器角色映射失败问题求助
问题概述
开发基于Keycloak和Spring OAuth2资源服务器的认证应用时,角色映射无法正确识别,具体表现为:
- 配置的
/user接口要求user角色,但携带user角色的请求无法通过校验 - 拥有
admin角色的用户可正常访问/user接口 /auth接口返回的权限列表包含重复的ROLE_USER及多个来自account客户端的角色
依赖配置
plugins { id("org.springframework.boot") version "2.7.8-SNAPSHOT" } implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server") implementation("com.c4-soft.springaddons:spring-addons-webmvc-jwt-resource-server:5.3.2")
JWT核心内容
"allowed-origins": [], "realm_access": { "roles": [ "user" ] }, "resource_access": { "myapp": { "roles": [ "user" ] }, "account": { "roles": [ "manage-account", "manage-account-links", "view-profile" ] } },
安全配置类
@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true) class JWTSecurityConfig { @Bean fun filterChain(http: HttpSecurity): SecurityFilterChain = http .cors() .and() .authorizeRequests { auth -> auth.antMatchers(HttpMethod.GET, "/user") .hasRole("user") .antMatchers(HttpMethod.GET, "/admin") .hasRole("admin") .anyRequest() .authenticated() } .oauth2ResourceServer(OAuth2ResourceServerConfigurer<HttpSecurity>::jwt) .build() @Bean fun jwtAuthenticationConverterForKeycloak(): JwtAuthenticationConverter? { val jwtGrantedAuthoritiesConverter = Converter<Jwt, Collection<GrantedAuthority>> { jwt: Jwt -> val realmAccess = jwt.getClaim<Map<String, Collection<String>>>("realm_access") val roles = realmAccess["roles"]!! roles.stream() .map { role -> SimpleGrantedAuthority("ROLE_$role") } .collect(Collectors.toList()) } val jwtAuthenticationConverter = JwtAuthenticationConverter() jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter) return jwtAuthenticationConverter } }
注:代码中'realm_access'应改为双引号"realm_access",否则Kotlin会识别为字符而非字符串,导致解析失败
application.yml配置
spring: security: oauth2: resource-server: jwt: issuer-uri: http://localhost:8181/auth/realms/myapp jwk-set-uri: http://localhost:8181/auth/realms/myapp/protocol/openid-connect/certs com: c4-soft: springaddons: security: issuers[0]: location: http://localhost:8181/auth/realms/myapp authorities: claims: realm_access.roles,resource_access.my-app.roles,resource_access.account.roles prefix: ROLE_ cors[0]: path: /user cors[1]: path: /admin
控制器代码
@RestController class TestController { @GetMapping("/user") @PreAuthorize("hasRole('user')") fun helloUser(): ResponseEntity<Foo> = ResponseEntity.ok().body(Foo("Hello User")) @GetMapping("/admin") @PreAuthorize("hasRole('admin')") fun helloAdmin(): ResponseEntity<Foo> = ResponseEntity.ok().body(Foo("Hello Admin")) @GetMapping("/auth") fun auth(jwt : JwtAuthenticationToken) = jwt.authorities.map(GrantedAuthority::getAuthority); } data class Foo(val value: String)
当前现象
调用/auth接口返回权限列表:
[ "ROLE_USER", "ROLE_USER", "ROLE_MANAGE-ACCOUNT", "ROLE_MANAGE-ACCOUNT-LINKS", "ROLE_VIEW-PROFILE" ]
排查方向
1. 自定义转换器未生效,存在配置冲突
你同时定义了自定义JwtAuthenticationConverter和spring-addons的权限解析配置,但spring-addons的自动配置优先级更高,会覆盖自定义转换器的逻辑。且安全配置中未将自定义转换器绑定到oauth2ResourceServer,导致实际生效的是spring-addons的解析规则,这也是返回结果中包含resource_access和account角色的原因。
解决:
- 若要使用自定义转换器,需在
oauth2ResourceServer配置中指定:.oauth2ResourceServer { it.jwt { jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverterForKeycloak()) } } - 或删除自定义转换器,完全依赖spring-addons的配置,同时修正配置中的错误。
2. spring-addons配置中的资源名称错误
配置里resource_access.my-app.roles中的my-app与JWT里的myapp(无横杠)不匹配,导致该配置项无法解析myapp客户端的角色,需修正为resource_access.myapp.roles。
3. 角色大小写不匹配导致权限校验失败
Spring Security的hasRole('user')会自动拼接前缀ROLE_,实际校验的是ROLE_user(小写user),但你的权限返回结果是ROLE_USER(大写USER),大小写不匹配导致校验不通过。而admin用户能访问/user接口,推测该用户同时拥有小写的user角色,或spring-addons的配置存在自动转大写的逻辑。
解决:
- 统一角色大小写:Keycloak中创建角色时使用小写,或修改权限转换逻辑为小写前缀拼接
- 若使用
hasAuthority替代hasRole,需明确指定完整权限名(如hasAuthority('ROLE_USER'))
4. 权限重复解析问题
返回结果中的两个ROLE_USER分别来自realm_access.roles和resource_access.myapp.roles,若不需要同时解析这两个来源的角色,可在spring-addons配置中移除其中一个,或在自定义转换器中只保留需要的角色来源。
内容的提问来源于stack exchange,提问作者mat373

