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

基于Keycloak的OAuth2资源服务器角色映射失败问题求助

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 04:25:16