Spring Boot中仅使用Keycloak实现认证、自定义过滤器实现授权的配置问题
一、这种方案的合理性
完全合理!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

