Spring Security集成Keycloak新增自定义API-Key认证后全部请求403
核心问题分析
1. 多HttpSecurity配置逻辑错误
你定义的两个WebSecurityConfigurerAdapter子类中,优先级更高的ApiKeySecurityConfig没有指定专属的请求匹配规则,默认会接管所有请求。但该配置中既没有注册任何认证过滤器,也没有配置认证入口,所有请求直接判定为未认证返回403,根本不会走到优先级更低的Keycloak配置逻辑中。
2. 自定义API Key过滤器未注册到过滤器链
你只实现了ApiKeyFilter类,但没有将其加入Spring Security的过滤器链,自然不会被执行。
3. 过滤器选型和逻辑缺陷
AbstractAuthenticationProcessingFilter默认用于处理特定登录路径的认证请求,不适合全局拦截所有请求:无api-key请求头时返回null的逻辑不会自动触发后续过滤器执行,且你没有将认证成功的Token写入安全上下文,就算校验通过也会被判定为未认证。
修复方案
推荐直接合并两个安全配置,避免多HttpSecurity的匹配冲突,修改后逻辑更清晰,完全满足「有api-key优先校验,校验通过跳过Keycloak,无api-key走原Keycloak逻辑」的需求。
第一步:修改自定义过滤器父类
将ApiKeyFilter改为继承OncePerRequestFilter,适配全局拦截逻辑:
public class ApiKeyFilter extends OncePerRequestFilter { private final ApiKeyAuthenticationProvider authProvider; public ApiKeyFilter(ApiKeyAuthenticationProvider authProvider) { this.authProvider = authProvider; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String apiKeyHeader = request.getHeader("api-key"); // 无api-key直接走后续过滤器(包括Keycloak认证) if (apiKeyHeader == null) { filterChain.doFilter(request, response); return; } // 有api-key走自定义校验 try { ApiKeyAuthenticationToken authToken = new ApiKeyAuthenticationToken(apiKeyHeader); Authentication authenticated = authProvider.authenticate(authToken); // 校验通过写入安全上下文,后续过滤器不会再触发认证 SecurityContextHolder.getContext().setAuthentication(authenticated); filterChain.doFilter(request, response); } catch (BadCredentialsException e) { // api-key校验失败直接返回401 response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.getWriter().write("Invalid Api-Key"); } } }
第二步:调整Token类实现
补充Principal返回值,避免后续逻辑空指针:
public class ApiKeyAuthenticationToken extends AbstractAuthenticationToken { private final String token; public ApiKeyAuthenticationToken(String token) { super(null); this.token = token; } @Override public Object getCredentials() { return token; } @Override public Object getPrincipal() { // 可根据实际业务返回自定义用户信息 return "api-key-user"; } }
第三步:合并安全配置,删除多余的ApiKeySecurityConfig
只保留Keycloak的配置类,注册自定义过滤器和认证Provider:
@Slf4j @Configuration @EnableWebSecurity @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") public class SecurityConf extends KeycloakWebSecurityConfigurerAdapter { @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { // 注册两个认证Provider,Keycloak的保留 KeycloakAuthenticationProvider provider = keycloakAuthenticationProvider(); provider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper()); auth.authenticationProvider(provider); // 注册自定义API Key认证Provider auth.authenticationProvider(apiKeyAuthenticationProvider()); } @Bean public ApiKeyAuthenticationProvider apiKeyAuthenticationProvider() { return new ApiKeyAuthenticationProvider(); } @Bean public ApiKeyFilter apiKeyFilter() { return new ApiKeyFilter(apiKeyAuthenticationProvider()); } @Bean @Override protected SessionAuthenticationStrategy sessionAuthenticationStrategy() { return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl()); } @Override protected void configure(HttpSecurity http) throws Exception { // 将API Key过滤器加到Keycloak认证过滤器之前,优先执行 http.addFilterBefore(apiKeyFilter(), KeycloakAuthenticationProcessingFilter.class); super.configure(http); http.csrf().disable().authorizeRequests().anyRequest().authenticated(); http.headers().frameOptions().disable(); } // 保留你原有的Keycloak过滤器注册、配置解析等其他代码不变 @Bean public FilterRegistrationBean keycloakAuthenticationProcessingFilterRegistrationBean(KeycloakAuthenticationProcessingFilter filter) { FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter); registrationBean.setEnabled(false); return registrationBean; } // 其余原有的keycloak相关Bean、keycloakConfigResolver等代码全部保留即可 // ... 省略原有代码 }
验证逻辑
修改完成后启动服务:
- 请求带合法
api-key头:优先走自定义校验,校验通过后直接访问接口,不会触发Keycloak认证 - 请求不带
api-key头:自动走原有Keycloak认证逻辑,不影响原有业务 - 请求带非法
api-key头:直接返回401错误
内容的提问来源于stack exchange,提问作者J-Deq87
相关产品推荐
相关产品推荐

