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

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

问题原因

  1. 过滤器重复处理请求:自定义过滤器与默认的UsernamePasswordAuthenticationFilter都监听/login/submit接口,导致认证逻辑重复执行,后续认证上下文处理出现冲突。
  2. 认证上下文未正确存储:若自定义过滤器的successfulAuthentication方法遗漏了将认证后的AuthenticationToken存入SecurityContextHolder的逻辑,会导致后续请求仍被判定为未认证。
  3. 成功跳转逻辑冲突:自定义过滤器的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 07:55:41