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

Spring Security过滤器重复调用致外部API二次请求的解决方案问询

解决方案:解决Spring Security过滤器重复执行问题并优化认证架构

一、解决过滤器执行两次的核心思路

过滤器重复执行通常是因为未限定过滤范围,导致无需认证的请求也触发了过滤逻辑,或是过滤器在链中的位置不明确。可以通过以下方式解决:

1. 限定过滤器的请求匹配范围

给自定义过滤器添加RequestMatcher,仅对需要认证的路径生效,跳过permitAll的请求:

public MyCustomAuthFilter filter() {
    MyCustomAuthFilter filter = new MyCustomAuthFilter();
    // 定义无需过滤的路径集合
    RequestMatcher skipMatcher = new OrRequestMatcher(
            new AntPathRequestMatcher("/api/v1/auth/login"),
            new AntPathRequestMatcher("/api/v1/user/password/reset-forgotten/**"),
            new AntPathRequestMatcher("/api/v1/status"),
            new AntPathRequestMatcher("/swagger-ui/**"),
            new AntPathRequestMatcher("/swagger-resources/**"),
            new AntPathRequestMatcher("/v3/api-docs")
    );
    // 设置仅过滤非跳过路径的请求
    filter.setRequestMatcher(new NegatedRequestMatcher(skipMatcher));
    
    // 原AuthenticationManager逻辑保留
    filter.setAuthenticationManager(authManager);
    return filter;
}

2. 重写过滤器的shouldNotFilter方法

在自定义过滤器中直接判断请求路径,无需认证的请求直接放行:

public class MyCustomAuthFilter extends AbstractPreAuthenticatedProcessingFilter {

    @Override
    protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
        return request.getHeader("Username");
    }

    @Override
    protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
        return request.getHeader("Authorization");
    }

    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
        String path = request.getRequestURI();
        return path.startsWith("/api/v1/auth/login") ||
               path.startsWith("/api/v1/user/password/reset-forgotten/") ||
               path.equals("/api/v1/status") ||
               path.startsWith("/swagger-ui/") ||
               path.startsWith("/swagger-resources/") ||
               path.startsWith("/v3/api-docs");
    }
}

3. 明确过滤器在链中的位置

使用addFilterBefore或addFilterAfter指定过滤器的位置,避免重复注册:

return http
        .addFilterBefore(filter(), UsernamePasswordAuthenticationFilter.class)
        .authorizeRequests()
            // 原授权配置保留
            .antMatchers("/api/v1/auth/login","/api/v1/user/password/reset-forgotten/**").permitAll()
            .anyRequest().authenticated()
        .and()
        .build();

二、更优的认证架构实现(符合Spring Security规范)

推荐将认证逻辑与过滤器分离,使用自定义PreAuthenticatedAuthenticationProvider,既保留Spring Security体系支持,又让代码结构更清晰:

1. 自定义PreAuthenticationProvider

@Component
public class CustomPreAuthenticatedProvider extends AbstractPreAuthenticatedAuthenticationProvider {

    private final MyExternalApi myExternalApi;

    public CustomPreAuthenticatedProvider(MyExternalApi myExternalApi) {
        this.myExternalApi = myExternalApi;
    }

    @Override
    protected void additionalAuthenticationChecks(Authentication authentication, PreAuthenticatedAuthenticationToken token) throws AuthenticationException {
        // 调用外部API执行认证逻辑
        String username = (String) token.getPrincipal();
        String authHeader = (String) token.getCredentials();
        try {
            myExternalApi.checkUserToken(username, authHeader);
        } catch (Exception e) {
            throw new BadCredentialsException("无效的令牌或用户名");
        }
    }

    @Override
    protected Object getUserDetails(Object principal) {
        // 返回UserDetails对象,支持后续授权逻辑(可按需扩展权限)
        return new User((String) principal, "", Collections.emptyList());
    }
}

2. 配置SecurityFilterChain

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http, CustomPreAuthenticatedProvider authProvider) throws Exception {
    AuthenticationManager authManager = http.getSharedObject(AuthenticationConfiguration.class).getAuthenticationManager();

    MyCustomAuthFilter filter = new MyCustomAuthFilter();
    filter.setAuthenticationManager(authManager);
    // 设置请求匹配范围
    RequestMatcher skipMatcher = new OrRequestMatcher(
            new AntPathRequestMatcher("/api/v1/auth/login"),
            new AntPathRequestMatcher("/api/v1/user/password/reset-forgotten/**"),
            new AntPathRequestMatcher("/api/v1/status"),
            new AntPathRequestMatcher("/swagger-ui/**"),
            new AntPathRequestMatcher("/swagger-resources/**"),
            new AntPathRequestMatcher("/v3/api-docs")
    );
    filter.setRequestMatcher(new NegatedRequestMatcher(skipMatcher));

    return http
            .authenticationProvider(authProvider)
            .addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class)
            .authorizeRequests()
                .antMatchers("/api/v1/auth/login","/api/v1/user/password/reset-forgotten/**").permitAll()
                .antMatchers("/api/v1/status").permitAll()
                .antMatchers("/swagger-ui/**", "/swagger-resources/**", "/v3/api-docs").permitAll()
                .anyRequest().authenticated()
            .and()
            .build();
}

该方案既解决了过滤器重复执行的问题,又完全保留了Spring Security的体系特性(如antMatchers().permitAll()、认证上下文管理等),同时代码职责分离,更易维护和扩展。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 00:15:44