Spring Boot 实现强制修改密码的自定义过滤器(避免重定向循环)

浅枫酱_2290

浅枫酱_2290

2026-05-20

131人浏览

原创

Spring Boot 实现强制修改密码的自定义过滤器(避免重定向循环)

本文详解如何在 spring security 中正确实现强制密码修改的自定义 filter,重点解决因未排除目标页面导致的“too many redirects”问题,并提供可直接落地的完整配置与最佳实践。

本文详解如何在 spring security 中正确实现强制密码修改的自定义 filter,重点解决因未排除目标页面导致的“too many redirects”问题,并提供可直接落地的完整配置与最佳实践。

在基于 Spring Boot 的 Web 应用中,当业务要求用户首次登录或密码过期后必须立即跳转至密码重置页(如 /change-password),且禁止访问其他任何受保护资源时,常通过自定义 Filter 实现该拦截逻辑。但若实现不当(例如未对重置页本身放行),极易触发浏览器重定向循环(HTTP 302 → 302 → …),最终报错 “Too many redirects”。

核心问题在于:原始 ForcePasswordChangeFilter 在用户需改密时无条件重定向至 /change-password,但该请求本身仍会再次经过该 Filter —— 此时用户仍处于“已认证但需改密”状态,Filter 再次执行重定向,形成死循环。

✅ 正确做法是:在 Filter 内部显式放行 /change-password 路径,确保该页面可被正常访问,同时阻断其他所有受保护路径。

以下是优化后的完整实现:

✅ 1. 安全配置类(SecurityConfiguration.java)

@Configuration
@EnableWebSecurity
public class SecurityConfiguration {

    @Autowired
    private UserService userService;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public UserDetailsService userDetailsService() {
        return new AppUserDetailsService();
    }

    @Bean
    public DaoAuthenticationProvider daoAuthenticationProvider() {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setPasswordEncoder(passwordEncoder());
        provider.setUserDetailsService(userDetailsService());
        return provider;
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authz -> authz
                .requestMatchers("/login", "/", "/css/**", "/js/**", "/assets/**", "/img/**", "/api/**").permitAll()
                .requestMatchers("/change-password", "/change-password/**").permitAll() // ← 关键:显式放行改密页
                .anyRequest().authenticated()
            )
            .formLogin(form -> form
                .loginPage("/login")
                .loginProcessingUrl("/login.do")
                .failureUrl("/login?error")
                .usernameParameter("username")
                .passwordParameter("password")
                .defaultSuccessUrl("/home", true) // true: 强制跳转,避免 referer 回退
            )
            .logout(logout -> logout
                .logoutUrl("/logout")
                .invalidateHttpSession(true)
                .deleteCookies("JSESSIONID")
                .logoutSuccessUrl("/login?logout")
            );

        // 将自定义 Filter 插入到 BasicAuthenticationFilter 之后(即认证完成之后)
        http.addFilterAfter(forcePasswordChangeFilter(), BasicAuthenticationFilter.class);

        return http.build();
    }

    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return (web) -> web.ignoring()
            .requestMatchers("/resources/**", "/static/**", "/images/**"); // 静态资源忽略安全检查
    }

    @Bean
    public ForcePasswordChangeFilter forcePasswordChangeFilter() {
        return new ForcePasswordChangeFilter(userService);
    }
}

✅ 2. 自定义过滤器(ForcePasswordChangeFilter.java)

public class ForcePasswordChangeFilter extends OncePerRequestFilter {

    private final UserService userService;

    public ForcePasswordChangeFilter(UserService userService) {
        this.userService = userService;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain)
            throws ServletException, IOException {

        // 仅对已认证用户生效
        if (SecurityContextHolder.getContext().getAuthentication() != null
                && SecurityContextHolder.getContext().getAuthentication().isAuthenticated()) {

            User user = userService.authenticatedUser();
            String path = request.getRequestURI();

            // ✅ 关键逻辑:若用户需改密,且当前请求不是改密页,则重定向;否则放行
            if (user != null && user.getShouldChangePassword()) {
                if ("/change-password".equals(path) || path.startsWith("/change-password/")) {
                    filterChain.doFilter(request, response); // 放行改密页
                    return;
                }
                response.sendRedirect(request.getContextPath() + "/change-password");
                return;
            }
        }

        filterChain.doFilter(request, response);
    }
}

⚠️ 注意事项:

  • 使用 OncePerRequestFilter 替代 GenericFilter(更符合 Spring 生态习惯,自动避免重复执行);
  • 优先通过 SecurityContextHolder 判断认证状态,比 userService.isAuthenticated() 更可靠;
  • 重定向使用 response.sendRedirect(...) 时建议添加 request.getContextPath(),兼容部署上下文路径;
  • /change-password/** 路径需在 authorizeHttpRequests 和 Filter 内双重放行(前者绕过权限校验,后者绕过强制跳转)。

✅ 3. 控制器补充(确保改密页无权限限制)

@Controller
public class AuthController {

    @GetMapping("/change-password")
    public String showChangePasswordForm(Model model) {
        model.addAttribute("user", userService.authenticatedUser());
        return "auth/change-password";
    }

    @PostMapping("/change-password")
    public String changePassword(@RequestParam String newPassword,
                                 RedirectAttributes redirectAttrs) {
        try {
            userService.updatePassword(newPassword);
            redirectAttrs.addFlashAttribute("success", "密码修改成功!");
            return "redirect:/home";
        } catch (Exception e) {
            redirectAttrs.addFlashAttribute("error", "密码更新失败:" + e.getMessage());
            return "redirect:/change-password";
        }
    }
}

✅ 总结

强制密码修改功能看似简单,但极易因 Filter 执行时机与路径匹配疏漏引发重定向风暴。关键设计原则有三:

  1. 精准拦截时机:Filter 必须在认证完成后(如 BasicAuthenticationFilter 后)执行;
  2. 白名单兜底:所有与改密流程相关的路径(如 /change-password, /change-password/save)必须在 Filter 内和 Security 配置中同步放行;
  3. 幂等与健壮性:使用 OncePerRequestFilter、校验 Authentication 状态、处理空用户等边界情况。

遵循以上方案,即可稳定、安全地实现企业级强制密码策略。

PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
java
java

Java是一个通用术语,用于表示Java软件及其组件,包括“Java运行时环境 (JRE)”、“Java虚拟机 (JVM)”以及“插件”。php中文网还为大家带了Java相关下载资源、相关课程以及相关文章等内容,供大家免费下载使用。

2023.06.15

10917

6

java正则表达式语法
java正则表达式语法

java正则表达式语法是一种模式匹配工具,它非常有用,可以在处理文本和字符串时快速地查找、替换、验证和提取特定的模式和数据。本专题提供java正则表达式语法的相关文章、下载和专题,供大家免费下载体验。

2023.07.05

8042

9

java自学难吗
java自学难吗

Java自学并不难。Java语言相对于其他一些编程语言而言,有着较为简洁和易读的语法,本专题为大家提供java自学难吗相关的文章,大家可以免费体验。

2023.07.31

6972

8

java配置jdk环境变量
java配置jdk环境变量

Java是一种广泛使用的高级编程语言,用于开发各种类型的应用程序。为了能够在计算机上正确运行和编译Java代码,需要正确配置Java Development Kit(JDK)环境变量。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

2023.08.01

1184

3

java保留两位小数
java保留两位小数

Java是一种广泛应用于编程领域的高级编程语言。在Java中,保留两位小数是指在进行数值计算或输出时,限制小数部分只有两位有效数字,并将多余的位数进行四舍五入或截取。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

2023.08.02

948

3

java基本数据类型
java基本数据类型

java基本数据类型有:1、byte;2、short;3、int;4、long;5、float;6、double;7、char;8、boolean。本专题为大家提供java基本数据类型的相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.02

1416

5

java有什么用
java有什么用

java可以开发应用程序、移动应用、Web应用、企业级应用、嵌入式系统等方面。本专题为大家提供java有什么用的相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.02

2869

5

java在线网站
java在线网站

Java在线网站是指提供Java编程学习、实践和交流平台的网络服务。近年来,随着Java语言在软件开发领域的广泛应用,越来越多的人对Java编程感兴趣,并希望能够通过在线网站来学习和提高自己的Java编程技能。php中文网给大家带来了相关的视频、教程以及文章,欢迎大家前来学习阅读和下载。

2023.08.03

20151

3

配置java环境变量
配置java环境变量

配置Java环境变量是为了让操作系统能够识别和使用Java的相关命令和功能。本专题为大家提供配置java环境变量相关文章,帮助大家解决问题。

2023.08.03

1255

8

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
热门推荐
/
最新课程
phpStudy极速入门视频教程
phpStudy极速入门视频教程

共6课时 | 54.7万人学习

独孤九贱(4)_PHP视频教程
独孤九贱(4)_PHP视频教程

共89课时 | 133.9万人学习