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

Spring Security集成Keycloak:Bearer Token请求返回403错误排查

嘿,我之前在Spring Security + Keycloak的Kotlin项目里也碰到过一模一样的403问题,大概率是某个配置环节没衔接上,给你列几个最容易踩坑的点,你逐一排查下:

1. 没正确映射Keycloak令牌里的角色到Spring Security权限

Keycloak的角色默认存在realm_access.roles或者客户端专属的resource_access[your-client-id].roles里,但Spring Security不会自动识别这些字段。你得配置一个JWT转换器来做映射:

@Bean
fun jwtAuthenticationConverter(): JwtAuthenticationConverter {
    val authoritiesConverter = JwtGrantedAuthoritiesConverter()
    // 从realm的角色列表里提取权限,前缀加ROLE_适配Spring的默认规则
    authoritiesConverter.setAuthoritiesClaimName("realm_access.roles")
    authoritiesConverter.setAuthorityPrefix("ROLE_")

    val converter = JwtAuthenticationConverter()
    converter.setJwtGrantedAuthoritiesConverter(authoritiesConverter)
    return converter
}

然后一定要在你的SecurityFilterChain里把这个转换器用上:

@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
    http
        .authorizeHttpRequests { auth ->
            auth.anyRequest().authenticated()
        }
        .oauth2ResourceServer { oauth2 ->
            oauth2.jwt { jwt ->
                jwt.jwtAuthenticationConverter(jwtAuthenticationConverter())
            }
        }
    return http.build()
}

2. 配置文件里的Issuer URI和Keycloak不匹配

检查你的application.yml(或properties)里的 issuer 地址,必须和Keycloak Realm的Issuer URI完全一致(包括端口、realm名称):

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: http://localhost:8080/realms/your-realm-name

另外要确认Keycloak客户端的Access Type设置正确(前后端分离用public,服务间调用用confidential),并且Web Origins/Redirect URIs配置符合你的请求来源。

3. 没开启方法级安全(如果用了@PreAuthorize之类的注解)

如果你的控制器用了@PreAuthorize("hasRole('ADMIN')")这类注解,必须在配置类上加上@EnableMethodSecurity来启用:

@Configuration
@EnableMethodSecurity(prePostEnabled = true)
class SecurityConfig {
    // 你的配置代码
}

还要注意Keycloak的角色是大小写敏感的,比如ADMIN和admin会被Spring当成两个不同的权限。

4. 令牌本身有问题

用jwt.io解析你的Bearer令牌,检查这几个点:

  • exp字段:令牌有没有过期
  • iss字段:是不是和配置里的Issuer URI完全一致
  • 角色字段:有没有包含你需要的权限(比如realm_access.roles里有没有对应的角色)

也可以开启Spring Security的调试日志,看看认证过程到底哪里失败了:

logging:
  level:
    org.springframework.security: DEBUG

5. 跨域请求的CORS配置没放行Authorization头

如果你的请求是跨域的,Spring Security默认会拦截带自定义头的请求,得配置CORS允许携带Authorization:

@Bean
fun corsConfigurationSource(): CorsConfigurationSource {
    val config = CorsConfiguration()
    config.allowedOrigins = listOf("http://your-frontend-domain") // 替换成你的前端地址
    config.allowedMethods = listOf("GET", "POST", "PUT", "DELETE")
    config.allowedHeaders = listOf("*")
    config.exposedHeaders = listOf("Authorization")
    config.allowCredentials = true

    val source = UrlBasedCorsConfigurationSource()
    source.registerCorsConfiguration("/**", config)
    return source
}

然后在filterChain里加上.cors { it.configurationSource(corsConfigurationSource()) }。


内容的提问来源于stack exchange,提问作者Maurice Müller

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 07:51:08