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
相关产品推荐
相关产品推荐

