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

Keycloak 21与Spring Security 6集成:403权限问题解决求助

解决Spring Security与Keycloak 21集成后的403权限拒绝问题

出现403的核心原因是Spring Security默认不会自动将Keycloak返回的角色转换为自身可识别的GrantedAuthority,需按以下步骤修复:

1. 确认用户已分配目标角色

在Keycloak控制台中,找到对应用户的「角色映射」页面,确保已将TECH_SUPPORT角色(领域或客户端角色)分配给该用户——仅在客户端中添加角色但未分配给用户无效。

2. 补充Scope配置

Keycloak默认不会在Token中返回角色信息,需在YAML配置的scope中添加roles:

spring:
  security:
    oauth2:
      client:
        registration:
          keycloak-login:
            scope: openid,profile,email,offline_access,roles

3. 配置角色权限转换器

编写自定义转换器,将Keycloak返回的Token角色转换为Spring Security的权限格式(需添加ROLE_前缀,匹配你配置的hasAuthority("ROLE_TECH_SUPPORT")):

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.stream.Collectors;

@Component
public class KeycloakAuthorityMapper implements org.springframework.security.core.authority.mapping.GrantedAuthorityMapper {

    @Override
    public Collection<? extends GrantedAuthority> mapAuthorities(Collection<? extends GrantedAuthority> authorities) {
        return authorities.stream()
                .map(this::convertAuthority)
                .flatMap(Collection::stream)
                .collect(Collectors.toList());
    }

    private Collection<? extends GrantedAuthority> convertAuthority(GrantedAuthority authority) {
        // 处理OIDC用户权限
        if (authority instanceof OidcUserAuthority oidcAuth) {
            Map<String, Object> attributes = oidcAuth.getAttributes();
            
            // 提取领域角色
            Map<String, Object> realmAccess = (Map<String, Object>) attributes.get("realm_access");
            if (realmAccess != null) {
                Collection<String> realmRoles = (Collection<String>) realmAccess.get("roles");
                if (realmRoles != null) {
                    return realmRoles.stream()
                            .map(role -> new SimpleGrantedAuthority("ROLE_" + role))
                            .collect(Collectors.toList());
                }
            }
            
            // 提取客户端角色(如果使用的是客户端角色)
            Map<String, Object> resourceAccess = (Map<String, Object>) attributes.get("resource_access");
            if (resourceAccess != null) {
                Map<String, Object> clientAccess = (Map<String, Object>) resourceAccess.get("admin_console_client");
                if (clientAccess != null) {
                    Collection<String> clientRoles = (Collection<String>) clientAccess.get("roles");
                    if (clientRoles != null) {
                        return clientRoles.stream()
                                .map(role -> new SimpleGrantedAuthority("ROLE_" + role))
                                .collect(Collectors.toList());
                    }
                }
            }
        } 
        // 处理普通OAuth2用户权限(按需选择)
        else if (authority instanceof OAuth2UserAuthority oauth2Auth) {
            Map<String, Object> attributes = oauth2Auth.getAttributes();
            // 同上逻辑提取角色
        }
        
        return Collections.singletonList(authority);
    }
}

4. 更新Spring Security配置

在SecurityFilterChain中配置oauth2Login的用户信息端点,使用自定义的权限转换器:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

    private final KeycloakAuthorityMapper authorityMapper;

    public WebSecurityConfig(KeycloakAuthorityMapper authorityMapper) {
        this.authorityMapper = authorityMapper;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.authorizeHttpRequests()
                .requestMatchers("/*").hasAuthority("ROLE_TECH_SUPPORT")
                .anyRequest().authenticated()
                .and()
                .oauth2Login(oauth2 -> oauth2
                        .userInfoEndpoint(userInfo -> userInfo
                                .userAuthoritiesMapper(authorityMapper)
                        )
                );

        return httpSecurity.build();
    }
}

5. 验证Token内容

登录成功后,解析获取的ID Token,确认realm_access.roles或resource_access.admin_console_client.roles字段中包含TECH_SUPPORT,确保角色已正确返回。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 08:22:01