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

SpringBoot3中如何配置Keycloak的client_credentials授权模式?

SpringBoot3 OAuth2资源服务器适配Keycloak Client Credentials模式及资源角色映射

问题概述

采用client_credentials授权模式,外部服务携带对应Keycloak客户端角色的Bearer Token访问应用。SpringBoot2.x使用Keycloak适配器时,可通过use-resource-role-mappings: 'true'启用客户端资源角色映射,但SpringBoot3弃用该适配器后,自行编写的OAuth2配置始终返回403,核心问题在于未正确配置资源服务器的JWT验证逻辑,以及未提取Keycloak Token中的客户端资源角色。

原SpringBoot2.x配置参考

Java配置

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends KeycloakWebSecurityConfigurerAdapter {

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
        keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
        auth.authenticationProvider(keycloakAuthenticationProvider);
    }

    @Bean
    @Override
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
        return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
        http.csrf().disable().authorizeRequests().antMatchers("/*").hasRole("some-role").anyRequest()
                .permitAll();
    }
}

application.yml配置

keycloak:
  auth-server-url: http://test-keycloak/auth
  realm: MyRealm
  resource: MyService
  use-resource-role-mappings: 'true'

SpringBoot3正确配置方案

核心问题解析

  1. 原配置未启用OAuth2资源服务器逻辑:SpringBoot3中需显式配置oauth2ResourceServer()告知Spring Security处理Bearer Token验证。
  2. 默认JWT转换器不提取客户端资源角色:Keycloak在client_credentials模式下,角色存储在Token的resource_access.{client-id}.roles字段中,需自定义转换器提取该字段。

Java配置

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {

    @Value("${spring.security.oauth2.client.registration.keycloak.client-id}")
    private String clientId;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf().disable()
                // 配置权限规则
                .authorizeHttpRequests(request -> request
                        .requestMatchers("/*").hasRole("some-role")
                        .anyRequest().permitAll())
                // 启用OAuth2资源服务器,配置JWT验证
                .oauth2ResourceServer(oauth2 -> oauth2
                        .jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter())));
        return http.build();
    }

    // 自定义JWT转换器,适配Keycloak客户端角色
    private JwtAuthenticationConverter jwtAuthenticationConverter() {
        JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
        // 设置自定义的角色提取器
        converter.setJwtGrantedAuthoritiesConverter(new KeycloakClientRoleExtractor(clientId));
        // 使用SimpleAuthorityMapper统一角色前缀(适配hasRole方法)
        converter.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
        return converter;
    }

    // 自定义角色提取器,从resource_access中获取客户端角色
    static class KeycloakClientRoleExtractor implements Converter<Jwt, Collection<GrantedAuthority>> {
        private final String clientId;

        public KeycloakClientRoleExtractor(String clientId) {
            this.clientId = clientId;
        }

        @Override
        public Collection<GrantedAuthority> convert(Jwt jwt) {
            // 从JWT中获取resource_access字段
            Map<String, Object> resourceAccess = jwt.getClaim("resource_access");
            if (resourceAccess == null) {
                return Collections.emptyList();
            }
            // 获取当前客户端的角色集合
            Map<String, Object> clientRoleMap = (Map<String, Object>) resourceAccess.get(clientId);
            if (clientRoleMap == null) {
                return Collections.emptyList();
            }
            List<String> roles = (List<String>) clientRoleMap.get("roles");
            if (roles == null) {
                return Collections.emptyList();
            }
            // 将角色转换为Spring Security的GrantedAuthority,添加ROLE_前缀适配hasRole
            return roles.stream()
                    .map(role -> new SimpleGrantedAuthority("ROLE_" + role))
                    .collect(Collectors.toList());
        }
    }
}

application.yml配置

spring:
  security:
    oauth2:
      # 资源服务器配置:验证JWT的合法性
      resource-server:
        jwt:
          issuer-uri: http://test-keycloak/auth/realms/MyRealm
      # 客户端配置:若应用需要主动获取Token则配置,仅验证外部Token时可省略,但client-id需一致
      client:
        registration:
          keycloak:
            client-id: MyService
            client-secret: your-client-secret # client_credentials模式需配置客户端密钥
            authorization-grant-type: client_credentials
        provider:
          keycloak:
            issuer-uri: http://test-keycloak/auth/realms/MyRealm

关键配置说明

  1. 启用资源服务器:通过.oauth2ResourceServer()配置,让Spring Security识别并处理Bearer Token,自动完成JWT签名验证、过期检查等。
  2. 自定义角色提取:KeycloakClientRoleExtractor专门提取Keycloak Token中resource_access.{client-id}.roles下的客户端角色,替代原适配器use-resource-role-mappings的功能。
  3. 角色前缀处理:SimpleAuthorityMapper自动处理角色前缀,确保与hasRole()方法匹配(hasRole("some-role")对应ROLE_some-role权限)。
  4. 客户端密钥:client_credentials模式下,Keycloak要求客户端验证身份,需配置正确的客户端密钥。

验证要点

  • 检查Token内容:确保Bearer Token中包含resource_access.MyService.roles字段,且包含some-role角色。
  • 权限方法匹配:若无需ROLE_前缀,可修改转换器逻辑,同时将hasRole()改为hasAuthority()。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 00:47:56