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

Java Spring Security客户端加密服务端解密后密码无法传递至后续过滤器问题

问题分析与解决方案

核心问题

你当前的代码存在两个关键问题导致解密后的密码无法传递:

  • Filter重复添加:WebSecurityConfig里同时调用了addFilterBefore(passwordDecryptionFilter, UsernamePasswordAuthenticationFilter.class)和addFilter(passwordDecryptionFilter),会导致Filter被执行两次,可能覆盖请求参数的处理逻辑。
  • Filter实现逻辑错误:你提供的代码是AuthenticationProvider的authenticate方法,而非真正的请求拦截Filter——解密逻辑应该拦截登录请求、修改请求中的密码参数,而非直接处理Authentication对象。

正确实现步骤

1. 编写正确的密码解密Filter

实现OncePerRequestFilter,拦截登录请求并通过包装请求修改密码参数:

public class PasswordDecryptionFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 仅拦截登录POST请求
        if ("/login".equals(request.getServletPath()) && "POST".equalsIgnoreCase(request.getMethod())) {
            // 包装请求以支持修改参数
            HttpServletRequest wrappedRequest = new HttpServletRequestWrapper(request) {
                @Override
                public String getParameter(String name) {
                    String value = super.getParameter(name);
                    // 对密码参数执行解密
                    if ("password".equals(name) && value != null) {
                        try {
                            return decryptPassword(value); // 替换为你的实际解密逻辑
                        } catch (Exception e) {
                            throw new RuntimeException("密码解密失败", e);
                        }
                    }
                    return value;
                }

                @Override
                public Map<String, String[]> getParameterMap() {
                    Map<String, String[]> map = new HashMap<>(super.getParameterMap());
                    String[] passwords = map.get("password");
                    if (passwords != null && passwords.length > 0) {
                        try {
                            passwords[0] = decryptPassword(passwords[0]);
                            map.put("password", passwords);
                        } catch (Exception e) {
                            throw new RuntimeException("密码解密失败", e);
                        }
                    }
                    return map;
                }
            };
            filterChain.doFilter(wrappedRequest, response);
        } else {
            filterChain.doFilter(request, response);
        }
    }

    // 自定义解密方法,替换为实际加密算法对应的逻辑
    private String decryptPassword(String encryptedPassword) throws Exception {
        // 示例:此处编写AES/RSA等解密代码
        return encryptedPassword;
    }
}

2. 修正WebSecurityConfig配置

移除重复的Filter添加代码,确保解密Filter在UsernamePasswordAuthenticationFilter之前执行:

@Override
protected void configure(HttpSecurity http) throws Exception {
    // 仅添加一次解密Filter,放在UsernamePasswordAuthenticationFilter之前
    http.addFilterBefore(passwordDecryptionFilter, UsernamePasswordAuthenticationFilter.class);

    http.csrf().csrfTokenRepository(csrfTokenRepository());

    http.authorizeRequests()
            .antMatchers("/login*", "/css/**", "/js/**", "/pages/**", "/img/**", "/fonts/**", "/plugins/**", "/jrxml/**").permitAll()
            .antMatchers("/tokenValidation").hasAuthority(Authorities.TOTP_AUTH_AUTHORITY)
            .anyRequest().authenticated();

    Cookie sameSiteCookie = new Cookie("SameSite", "None");
    http.addFilterBefore(new SamSiteAndCSPFilter(sameSiteCookie), UsernamePasswordAuthenticationFilter.class);

    http.addFilterBefore(totpAuthFilter, UsernamePasswordAuthenticationFilter.class);

    http.exceptionHandling().accessDeniedPage("/403");

    http.formLogin()
            .loginPage("/login")
            .usernameParameter("username")
            .passwordParameter("password")
            .failureUrl("/login?error=true")
            .failureHandler(customAuthenticationFailureHandler)
            .successHandler(customAuthenticationSuccessHandler);

    http.sessionManagement()
            .sessionFixation().migrateSession()
            .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
            .invalidSessionUrl("/login")
            .maximumSessions(1)
            .sessionRegistry(sessionRegistry())
            .and()
            .sessionFixation().none();

    http.logout()
            .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
            .invalidateHttpSession(true)
            .clearAuthentication(true)
            .logoutSuccessUrl("/raya/login?logSucc=true")
            .logoutSuccessHandler(logoutSuccessHandler())
            .deleteCookies("JSESSIONID")
            .permitAll();
}

3. 验证AuthenticationProvider逻辑

确保从Authentication对象中获取的是解密后的密码:

@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    String username = authentication.getName();
    String decryptedPassword = authentication.getCredentials().toString();
    System.out.println("解密后的密码: " + decryptedPassword);

    User user = userRepository.findUserByUserName(username);
    if (user == null) {
        throw new BadCredentialsException("用户名不存在");
    }

    // 用密码编码器验证解密后的密码与数据库存储的加密密码是否匹配
    if (!encoder().matches(decryptedPassword, user.getPassword())) {
        throw new BadCredentialsException("密码错误");
    }

    return new UsernamePasswordAuthenticationToken(user, null, getAuthorities(Arrays.asList(user.getRole())));
}

关键注意事项

  • 禁止重复添加同一个Filter,避免请求被多次处理导致参数异常。
  • 必须通过HttpServletRequestWrapper包装请求,原生请求参数不可直接修改。
  • 确保客户端加密逻辑与服务端解密逻辑完全匹配,防止解密失败。

内容的提问来源于stack exchange,提问作者Greko2015 GuFn

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 04:40:24