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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 03:45:03