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

如何用自定义对象替代KeycloakPrincipal适配Spring非Boot REST API

解决方案:替换KeycloakPrincipal为自定义CustomObject

完全可以实现,既不用修改原有业务代码,也能避免直接耦合Keycloak API。最佳实现方式不是单纯使用Filter,而是扩展Spring Security的AuthenticationProvider——这更贴合Spring Security的认证生命周期,逻辑更严谨。

方式一:扩展KeycloakAuthenticationProvider(推荐)

Keycloak的Spring Security适配器默认使用KeycloakAuthenticationProvider生成包含KeycloakPrincipal的认证对象。我们可以扩展这个Provider,在认证成功后将KeycloakPrincipal转换为你的CustomObject:

public class CustomKeycloakAuthenticationProvider extends KeycloakAuthenticationProvider {

    @Override
    protected Authentication createSuccessfulAuthentication(HttpServletRequest request, KeycloakAuthenticationToken token) {
        // 先让父类完成基础的令牌验证
        Authentication originalAuth = super.createSuccessfulAuthentication(request, token);
        
        // 从KeycloakPrincipal中提取用户信息
        KeycloakPrincipal<KeycloakSecurityContext> keycloakPrincipal = 
            (KeycloakPrincipal<KeycloakSecurityContext>) originalAuth.getPrincipal();
        AccessToken accessToken = keycloakPrincipal.getKeycloakSecurityContext().getToken();
        
        // 转换为你的CustomObject,根据业务需求填充字段
        CustomObject customObject = new CustomObject();
        customObject.setId(accessToken.getSubject()); // Keycloak用户唯一ID
        customObject.setUsername(accessToken.getPreferredUsername());
        customObject.setRoles(accessToken.getRealmAccess().getRoles());
        // 补充其他自定义字段,比如从accessToken.getOtherClaims()获取自定义Claims
        
        // 生成新的认证对象,替换Principal为CustomObject
        return new UsernamePasswordAuthenticationToken(
            customObject,
            originalAuth.getCredentials(),
            originalAuth.getAuthorities()
        );
    }
}

然后在Spring Security配置类中替换默认的Provider:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        CustomKeycloakAuthenticationProvider customProvider = new CustomKeycloakAuthenticationProvider();
        // 可选:配置角色映射,比如将Keycloak的ROLE_前缀转为Spring Security的格式
        customProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
        auth.authenticationProvider(customProvider);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
        http.authorizeRequests()
            .antMatchers("/api/**").authenticated();
    }

    @Bean
    public KeycloakConfigResolver keycloakConfigResolver() {
        // 加载classpath下的keycloak.json配置
        return new KeycloakSpringConfigResolver();
    }
}

方式二:自定义后置Filter(快速改造方案)

如果不想扩展Provider,也可以通过自定义Filter在认证完成后修改SecurityContext中的Principal。注意要把这个Filter放在Keycloak认证Filter之后执行:

public class CustomPrincipalConversionFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        
        // 仅处理Keycloak认证后的令牌
        if (auth instanceof KeycloakAuthenticationToken) {
            KeycloakPrincipal<KeycloakSecurityContext> keycloakPrincipal = 
                (KeycloakPrincipal<KeycloakSecurityContext>) auth.getPrincipal();
            AccessToken accessToken = keycloakPrincipal.getKeycloakSecurityContext().getToken();
            
            // 构建CustomObject
            CustomObject customObject = new CustomObject();
            customObject.setId(accessToken.getSubject());
            customObject.setUsername(accessToken.getPreferredUsername());
            // 填充其他字段...
            
            // 替换SecurityContext中的认证对象
            Authentication newAuth = new UsernamePasswordAuthenticationToken(
                customObject,
                auth.getCredentials(),
                auth.getAuthorities()
            );
            SecurityContextHolder.getContext().setAuthentication(newAuth);
        }
        
        filterChain.doFilter(request, response);
    }
}

在Security配置中注册Filter:

@Override
protected void configure(HttpSecurity http) throws Exception {
    super.configure(http);
    // 放在KeycloakAuthenticationProcessingFilter之后执行
    http.addFilterAfter(new CustomPrincipalConversionFilter(), KeycloakAuthenticationProcessingFilter.class)
        .authorizeRequests()
        .antMatchers("/api/**").authenticated();
}

方案对比

  • 扩展Provider:完全融入Spring Security的认证流程,在认证成功的第一时间完成转换,逻辑更可靠,是推荐的方案。后续更换令牌提供商时,只需替换这个Provider的实现即可,业务代码完全不受影响。
  • 自定义Filter:实现简单,适合快速验证,但需要注意Filter的执行顺序,避免在认证未完成时执行转换。

注意事项

  • 所有Keycloak相关的转换逻辑都要封装在扩展类/Filter中,业务代码不要直接引用Keycloak的API,确保解耦。
  • 根据你的CustomObject结构,正确从Keycloak的AccessToken中提取信息,包括标准Claims和自定义Claims(通过accessToken.getOtherClaims().get("custom-claim")获取)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 12:55:18