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
相关产品推荐
相关产品推荐

