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

Spring Boot中仅使用Keycloak实现认证、自定义过滤器实现授权的配置问题

仅用Keycloak做认证、自定义过滤器做授权的Spring Boot配置方案

一、这种方案的合理性

完全合理!Keycloak作为成熟的身份认证服务,能帮你快速实现标准的OAuth2/OIDC认证流程(比如令牌校验、用户信息解析),而自定义授权过滤器可以灵活贴合你的业务权限逻辑(比如从数据库/内部权限系统拉取用户专属权限),这是Spring Boot整合Keycloak时非常常见的实践模式,既利用了Keycloak的认证能力,又保留了业务授权的灵活性。

二、解决过滤器重复执行、权限被清空的问题

你遇到的核心问题是KeycloakAuthenticationProcessingFilter重复执行,覆盖了你自定义过滤器添加的权限。问题根源主要有两个:一是自定义过滤器未确保单次请求仅执行一次,二是Keycloak过滤器的执行顺序未被正确控制。下面是具体的解决步骤:

1. 改造自定义过滤器,确保单次请求仅执行一次

继承Spring提供的OncePerRequestFilter基类,它会自动保证过滤器在单次请求生命周期内只运行一次,避免因请求调度导致的重复执行:

public class JWTAuthorizationFilter extends OncePerRequestFilter {
    private final AuthenticationManager authenticationManager;
    private final ApplicationContext context;

    public JWTAuthorizationFilter(AuthenticationManager authenticationManager, ApplicationContext context) {
        this.authenticationManager = authenticationManager;
        this.context = context;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 获取Keycloak认证后的用户上下文
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        // 仅对已认证的非匿名用户处理权限
        if (auth != null && auth.isAuthenticated() && !(auth instanceof AnonymousAuthenticationToken)) {
            // 这里替换成你的业务权限逻辑,比如从数据库查询用户权限
            List<GrantedAuthority> updatedAuthorities = new ArrayList<>(auth.getAuthorities());
            updatedAuthorities.add(new SimpleGrantedAuthority("CUSTOM_BUSINESS_PERMISSION"));
            
            // 构建更新后的认证对象并替换上下文
            Authentication updatedAuth = new UsernamePasswordAuthenticationToken(
                    auth.getPrincipal(),
                    auth.getCredentials(),
                    updatedAuthorities
            );
            SecurityContextHolder.getContext().setAuthentication(updatedAuth);
        }
        // 继续执行后续过滤器链
        filterChain.doFilter(request, response);
    }
}

2. 调整SecurityConfig配置,明确过滤器执行顺序

由于你继承了KeycloakWebSecurityConfigurerAdapter,它默认会注册KeycloakAuthenticationProcessingFilter,你需要明确将自定义过滤器添加到该过滤器之后,同时配置无状态会话策略避免不必要的过滤器重复触发:

@Configuration
@EnableWebSecurity
class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
    private final ApplicationContext context;

    // 注入Spring上下文,用于自定义过滤器的依赖获取
    public SecurityConfig(ApplicationContext context) {
        this.context = context;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 先执行父类配置,初始化Keycloak的基础认证过滤器
        super.configure(http);
        
        http
            .cors()
            .and()
            .csrf().ignoringAntMatchers("/","/auth","/auth/logout")
            .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
            .and()
            // 明确指定无状态会话,避免Keycloak因会话相关逻辑重复执行过滤器
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .authorizeRequests()
            // 放行无需认证的路径
            .antMatchers( "/", "/auth", "/password/**", "/register/**", "/v2/api-docs", "/actuator/**", "/configuration/ui", "/swagger-resources", "/configuration/security", "/swagger-ui.html", "/webjars/**", "/swagger-resources/configuration/ui", "/swagger-resources/configuration/security", "/browser/index.html#", "/browser/**").permitAll()
            .antMatchers(HttpMethod.POST, REGISTER).permitAll()
            .antMatchers(HttpMethod.POST, CONFIRM).permitAll()
            .anyRequest().authenticated()
            // 关键配置:将自定义过滤器添加到Keycloak认证过滤器之后
            .and()
            .addFilterAfter(new JWTAuthorizationFilter(authenticationManager(), context), KeycloakAuthenticationProcessingFilter.class)
            .headers()
            .contentSecurityPolicy("script-src 'self'");
    }

    // 覆盖该方法,强制Keycloak使用无状态会话策略
    @Override
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
        return new NullAuthenticatedSessionStrategy();
    }
}

3. 验证执行顺序

你可以在自定义过滤器和Keycloak过滤器中添加日志,确认执行流程:

  • 在JWTAuthorizationFilter的doFilterInternal方法中添加log.info("执行自定义授权过滤器,当前认证对象权限:{}", auth.getAuthorities());
  • 调整日志级别到DEBUG,查看KeycloakAuthenticationProcessingFilter的执行日志

最终的过滤器执行顺序会是:
KeycloakAuthenticationProcessingFilter(完成认证,将用户信息存入上下文)→ JWTAuthorizationFilter(获取认证对象,添加业务权限,更新上下文)→ 后续Spring Security权限校验过滤器

三、额外注意事项

  • 确保Keycloak的realm、client配置正确,认证流程能正常通过
  • 如果权限逻辑需要调用数据库或其他服务,建议在自定义过滤器中注入对应的Service,避免硬编码逻辑
  • 无状态模式下,SecurityContextHolder默认使用线程绑定的上下文存储,符合你的业务需求

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 22:02:47