Spring Security认证成功后异常重定向问题排查与方案咨询
问题现象
为API端点添加访问控制时,无论请求/api/apple还是/api/orange,服务都会重定向到/。当前通过继承AbstractAuthenticationProcessingFilter实现自定义头认证,但该过滤器设计为认证成功后重定向,不符合API无状态、返回原请求响应的需求。
原因分析
AbstractAuthenticationProcessingFilter是为**交互式认证场景(如表单登录)**设计的,其默认逻辑中,认证成功后会调用successfulAuthentication方法,该方法会自动重定向到配置的successUrl(未配置时默认跳转到/)。这种重定向行为完全不适用于API接口,因为API需要保持请求上下文,直接返回业务响应而非跳转页面。
此外,你在过滤器中手动设置SecurityContextHolder.getContext().setAuthentication(authentication),这会干扰Spring Security的认证流程,导致后续逻辑混乱。
正确实现方案
对于基于自定义请求头的预认证场景,应该使用Spring Security提供的AbstractPreAuthenticatedProcessingFilter——它专门为预认证场景(如基于请求头、客户端证书的认证)设计,默认不会触发重定向,完全适配API的无状态认证需求。
步骤1:修改过滤器实现
将SessionAuthFilter改为继承AbstractPreAuthenticatedProcessingFilter,重写获取预认证主体和凭证的方法:
public class SessionAuthFilter extends AbstractPreAuthenticatedProcessingFilter { private final CustomUserDetails userDetails; protected SessionAuthFilter(RequestMatcher requestMatcher, AuthenticationManager authenticationManager, CustomUserDetails userDetails) { super(requestMatcher, authenticationManager); this.userDetails = userDetails; // 设置认证失败时抛出异常,避免后续过滤器误处理 setThrowExceptionWhenTokenRejected(true); } @Override protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) { String sessionToken = request.getHeader("SessionToken"); return sessionToken != null ? sessionToken.trim() : null; } @Override protected Object getPreAuthenticatedCredentials(HttpServletRequest request) { // 预认证场景下,凭证可设为固定值,后续由Provider验证有效性 return "N/A"; } }
步骤2:调整认证提供者逻辑
修改PreAuthProvider,确保认证失败时抛出明确异常,同时正确验证token对应的用户有效性:
@Component @Slf4j public class PreAuthProvider implements AuthenticationProvider { private final CustomUserDetails userDetails; // 注入CustomUserDetails用于验证token有效性 public PreAuthProvider(CustomUserDetails userDetails) { this.userDetails = userDetails; } @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { if (!supports(authentication.getClass())) { return null; } String sessionToken = (String) authentication.getPrincipal(); log.debug("PreAuthenticated authentication request: {}", authentication); // 验证token对应的用户是否存在且有效 UserDetails user; try { user = userDetails.loadUserByUsername(sessionToken); } catch (UsernameNotFoundException e) { throw new BadCredentialsException("Invalid SessionToken"); } if (!user.isCredentialsNonExpired()) { throw new InsufficientAuthenticationException("SessionToken has expired"); } // 返回已认证的Authentication对象 return new PreAuthenticatedAuthenticationToken(user.getUsername(), user.getPassword(), user.getAuthorities()); } @Override public boolean supports(Class<?> authentication) { return PreAuthenticatedAuthenticationToken.class.isAssignableFrom(authentication); } }
步骤3:修正SecurityFilterChain配置
确保过滤器添加到正确位置,同时保持无状态会话配置:
@Configuration @EnableWebSecurity public class SecurityConfig { @Bean AuthenticationManager customAuthenticationManager(PreAuthProvider preAuthProvider) { return new ProviderManager(List.of(preAuthProvider)); } @Bean SessionAuthFilter customAuthFilter(AuthenticationManager authManager, CustomUserDetails userDetails) { SessionAuthFilter filter = new SessionAuthFilter( new OrRequestMatcher( new AntPathRequestMatcher("/apple/**"), new AntPathRequestMatcher("/orange/**") ), authManager, userDetails); // 自定义认证成功逻辑:不重定向,直接继续请求链 filter.setAuthenticationSuccessHandler((request, response, auth) -> {}); return filter; } @Bean public SecurityFilterChain filterChain(HttpSecurity http, SessionAuthFilter authFilter) throws Exception { http.exceptionHandling() .authenticationEntryPoint(new Http403ForbiddenEntryPoint()) .accessDeniedHandler(new AccessDeniedHandlerImpl()) .and() .formLogin().disable() .httpBasic().disable() .authorizeRequests() .antMatchers( "/", "/error", "/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html", "/actuator/**" ).permitAll() .antMatchers(HttpMethod.GET, "/apple").hasAuthority("getApples") .antMatchers(HttpMethod.GET, "/orange").hasAuthority("getOranges") .anyRequest().authenticated() .and() .addFilterBefore(authFilter, AbstractPreAuthenticatedProcessingFilter.class) .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); return http.build(); } }
关键优化点
- 替换过滤器类型:用
AbstractPreAuthenticatedProcessingFilter替代AbstractAuthenticationProcessingFilter,彻底避免默认重定向行为。 - 单一职责拆分:过滤器只负责提取认证信息,用户验证逻辑交给
AuthenticationProvider,符合Spring Security的设计规范。 - 明确异常处理:设置
throwExceptionWhenTokenRejected(true),确保认证失败时直接返回403/401,而非继续执行请求链。 - 自定义成功处理器:覆盖认证成功后的逻辑,不做任何跳转,让请求直接流向业务控制器。
内容的提问来源于stack exchange,提问作者superflux

