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

Spring Boot+Keycloak OAuth2授权码流自定义JWT转换器未调用问题

问题分析

从日志可见,当前认证流程生成的是OAuth2AuthenticationToken而非资源服务器的JwtAuthenticationToken——这是因为浏览器访问走的是OAuth2客户端登录流程(基于Session),而非资源服务器的JWT Bearer Token认证流程,因此你配置在oauth2ResourceServer下的自定义JwtAuthConverter不会被触发。这直接导致接口权限校验时,用户的Granted Authorities中没有ROLE_client_admin,返回403错误。

解决方案

需要同时处理两种认证场景的权限转换:浏览器端的OAuth2登录流程,以及API调用的JWT Bearer流程。以下是具体实现:

1. 自定义OAuth2用户服务(处理浏览器登录场景)

创建CustomOAuth2UserService,从Keycloak返回的ID Token中提取角色并添加ROLE_前缀:

@Component
public class CustomOAuth2UserService extends DefaultOAuth2UserService {
    
    @Value("${jwt.auth.converter.resource-id}")
    private String resourceId;

    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        OAuth2User user = super.loadUser(userRequest);
        Map<String, Object> attributes = user.getAttributes();
        
        List<String> roles = new ArrayList<>();
        // 提取Realm级角色
        if (attributes.containsKey("realm_access")) {
            Map<String, Object> realmAccess = (Map<String, Object>) attributes.get("realm_access");
            roles.addAll((List<String>) realmAccess.get("roles"));
        }
        // 提取客户端级角色
        if (attributes.containsKey("resource_access")) {
            Map<String, Object> resourceAccess = (Map<String, Object>) attributes.get("resource_access");
            if (resourceAccess.containsKey(resourceId)) {
                Map<String, Object> clientRoles = (Map<String, Object>) resourceAccess.get(resourceId);
                roles.addAll((List<String>) clientRoles.get("roles"));
            }
        }

        // 转换为Spring Security权限格式
        Set<GrantedAuthority> authorities = roles.stream()
                .map(role -> new SimpleGrantedAuthority("ROLE_" + role))
                .collect(Collectors.toSet());
        // 保留原有Scope权限
        authorities.addAll(user.getAuthorities());

        return new DefaultOAuth2User(authorities, attributes, "preferred_username");
    }
}

2. 更新SecurityConfig配置

将自定义用户服务绑定到OAuth2登录流程,同时保留资源服务器的JWT转换器配置:

@RequiredArgsConstructor
@EnableWebSecurity
@EnableMethodSecurity
@Configuration
public class SecurityConfig {
    
    private final JwtAuthConverter jwtAuthConverter;
    private final CustomOAuth2UserService customOAuth2UserService;
    
    @Value(value = "${jwt.auth.converter.issuer-uri}")
    private String issuerUri;
    
    @Bean   
    public SecurityFilterChain clientFilterChain(HttpSecurity http) throws Exception {  
        return http.authorizeHttpRequests(req-> req
                 .anyRequest().authenticated())
        // 配置OAuth2登录的用户信息处理
        .oauth2Login(oauth2 -> oauth2
                .userInfoEndpoint(userInfo -> userInfo
                        .userService(customOAuth2UserService))
        )
        // 保留资源服务器的JWT配置(供API调用场景使用)
        .oauth2ResourceServer(oauth2ResourceServer -> oauth2ResourceServer
                .jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthConverter))
         )
        .build();
     }
        
    @Bean
    public JwtDecoder jwtDecoder() {
        return NimbusJwtDecoder.withIssuerLocation(issuerUri).build();
    } 
}

3. 完善Keycloak配置

  • 在Keycloak客户端设置中,确保roles Scope已启用;
  • 进入客户端的"Client Scopes" -> "roles" -> "Mappers",确认角色映射器已配置为将角色添加到ID Token和Access Token中。

排查思路

  • 确认认证流程类型:通过日志中的Authentication类型判断当前走的是客户端登录还是资源服务器JWT流程;
  • 解析ID Token内容:用jwt.io解析Keycloak返回的ID Token,检查是否包含realm_access/resource_access字段及目标角色;
  • 校验组件注册:确认自定义JwtAuthConverter和CustomOAuth2UserService被Spring容器正确扫描(@Component注解生效);
  • 检查Keycloak权限配置:验证角色是否被正确分配给用户,且客户端设置中允许角色出现在Token中。

教程推荐

  • Spring Security官方文档:重点学习OAuth2 Client的用户信息处理、权限转换,以及Resource Server的JWT配置,掌握双认证模式的整合;
  • Keycloak官方文档:Spring Boot整合章节,包含授权码流配置、角色映射、权限控制的详细步骤;
  • 手动实现权限转换:通过扩展DefaultOAuth2UserService或实现OAuth2AuthenticationConverter,自定义从OAuth2用户信息中提取角色并转换为Spring Security权限格式。

内容的提问来源于stack exchange,提问作者Man13

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 00:44:58