Spring Security 6.0.1自定义过滤器认证成功却不跳转至首页
Spring Security自定义过滤器认证成功后不跳转问题排查与解决
问题描述
我基于Spring Boot 3.0.1和Spring Security 6.0.1构建Web服务,添加自定义AuthenticationProcessingFilter后遇到登录异常:调试确认自定义AuthenticationProvider的authenticate方法已成功返回AuthenticationToken,但认证完成后并未跳转到/index页面,始终停留在登录页;移除该自定义过滤器后,登录、登出功能均正常运行。
相关代码
SecurityConfig
@Configuration @EnableWebSecurity @EnableMethodSecurity(prePostEnabled = true) public class SecurityConfig { @Autowired private MyUsernamePasswordAuthenticationProvider myUsernamePasswordAuthenticationProvider; @Bean public AuthenticationManager authManager(HttpSecurity http) throws Exception { AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class); authenticationManagerBuilder.authenticationProvider(myUsernamePasswordAuthenticationProvider); return authenticationManagerBuilder.build(); } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.addFilterBefore(myUsernamePasswordAuthenticationFilter(http), UsernamePasswordAuthenticationFilter.class);// Works fine if comment this line. //http.addFilterBefore(myMobilephoneAuthenticationFilter(http), UsernamePasswordAuthenticationFilter.class); http.csrf().disable() .authorizeRequests((authorize) -> authorize.requestMatchers("/login/**").permitAll() .anyRequest().authenticated() ).formLogin( form -> form .loginPage("/login") .loginProcessingUrl("/login/submit") .defaultSuccessUrl("/index") .permitAll() ).logout( logout -> logout .logoutRequestMatcher(new AntPathRequestMatcher("/logout")) .logoutSuccessUrl("/login?msg=logout") .permitAll() ); return http.build(); } @Bean public MyUsernamePasswordAuthenticationFilter myUsernamePasswordAuthenticationFilter(HttpSecurity httpSecurity) throws Exception { MyUsernamePasswordAuthenticationFilter filter = new MyUsernamePasswordAuthenticationFilter(); filter.setAuthenticationManager(authManager(httpSecurity)); filter.setAuthenticationSuccessHandler(new SimpleUrlAuthenticationSuccessHandler("/index")); filter.setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler("/login?msg=invalid_simple")); filter.setFilterProcessesUrl("/login/submit"); return filter; } }
MyUsernamePasswordAuthenticationProvider
@Component public class MyUsernamePasswordAuthenticationProvider implements AuthenticationProvider { @Autowired private MyUserService myUserService; @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override public Authentication authenticate(Authentication authentication) { String name = authentication.getName(); String password = authentication.getCredentials().toString(); UserDetails dbUser = myUserService.loadUserByUsername(name); if (passwordEncoder().matches(password, dbUser.getPassword())) { var authorities = List.of(new SimpleGrantedAuthority("ROLE_USER")); // use the credentials // and authenticate against the third-party system return new MyUsernamePasswordAuthenticationToken( name, password, authorities); } else { throw new BadCredentialsException("Wrong password"); // return null; } } @Override public boolean supports(Class<?> authentication) { return authentication.equals(MyUsernamePasswordAuthenticationToken.class); } }
MyUsernamePasswordAuthenticationFilter
Same as org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
问题原因
- 过滤器重复处理请求:自定义过滤器与默认的
UsernamePasswordAuthenticationFilter都监听/login/submit接口,导致认证逻辑重复执行,后续认证上下文处理出现冲突。 - 认证上下文未正确存储:若自定义过滤器的
successfulAuthentication方法遗漏了将认证后的AuthenticationToken存入SecurityContextHolder的逻辑,会导致后续请求仍被判定为未认证。 - 成功跳转逻辑冲突:自定义过滤器的
SimpleUrlAuthenticationSuccessHandler与formLogin配置的defaultSuccessUrl存在跳转逻辑冲突,导致跳转行为异常。
解决方案
1. 替换默认过滤器而非添加前置过滤器
既然自定义了登录过滤器,直接替换默认的UsernamePasswordAuthenticationFilter,避免重复处理请求:
@Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // 替换默认过滤器,而非添加前置过滤器 http.addFilterAt(myUsernamePasswordAuthenticationFilter(http), UsernamePasswordAuthenticationFilter.class); http.csrf().disable() .authorizeRequests((authorize) -> authorize.requestMatchers("/login/**").permitAll() .anyRequest().authenticated() ).formLogin( form -> form .loginPage("/login") .permitAll() ).logout( logout -> logout .logoutRequestMatcher(new AntPathRequestMatcher("/logout")) .logoutSuccessUrl("/login?msg=logout") .permitAll() ); return http.build(); }
2. 确保认证上下文正确存储
检查自定义过滤器的successfulAuthentication方法,确保将认证结果存入SecurityContextHolder(默认过滤器已有此逻辑,若重写需保留):
@Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { // 存储认证上下文 SecurityContextHolder.getContext().setAuthentication(authResult); // 触发成功跳转 getSuccessHandler().onAuthenticationSuccess(request, response, authResult); }
3. 统一跳转配置,避免冲突
移除formLogin中的defaultSuccessUrl配置,仅保留自定义过滤器中的成功处理器配置,消除逻辑冲突:
.formLogin( form -> form .loginPage("/login") .loginProcessingUrl("/login/submit") .permitAll() )
4. 优化Bean配置
将PasswordEncoder的Bean定义从MyUsernamePasswordAuthenticationProvider移至SecurityConfig中,避免重复创建实例:
// 在SecurityConfig中添加 @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } // 移除MyUsernamePasswordAuthenticationProvider中的PasswordEncoder Bean定义
额外验证
确保自定义的MyUsernamePasswordAuthenticationToken实现Authentication接口时,isAuthenticated()方法在认证成功后返回true,否则Spring Security会认为认证未完成。
内容的提问来源于stack exchange,提问作者user6839234
相关产品推荐
相关产品推荐

