Java中禁用equals()比对密码,应使用恒定时间算法(如逐字符异或)比对char[];密码需加盐哈希存储;会话须绑定并防CSRF;MockMvc测试需手动传递JSESSIONID;表单密码乱码需统一UTF-8编码。
equals()比较密码字符串是错的直接用==或equals()比对用户输入的密码和存储的密码,看似能跑通,但存在严重安全与逻辑问题。Java字符串常量池机制会让相同字面值的String对象共享引用,一旦密码来自字面量或被intern过,==可能偶然返回true;而equals()虽能正确比语义,但它不防定

真实登录流程中,密码字段应始终用char[]接收并立即擦除,比对必须使用恒定时间算法:
HttpServletRequest.getParameter("password")拿到String后,立刻转为char[],再清空原始String引用(无法彻底清除,但减少驻留)Arrays.equals(char[], char[])——它仍可能被JIT优化出短路行为;应手写循环,遍历全部字符并累积异或结果,最后判断是否为0BCryptPasswordEncoder生成的$2a$10$...格式),绝不能存明文或简单Base64单纯校验账号密码通过就session.setAttribute("user", user),只是完成了最基础的认证,离“模拟用户登录流程”还差关键两步:会话绑定与请求合法性校验。
常见疏漏包括:
HttpSession的setMaxInactiveInterval(1800),导致长期空闲会话滞留服务器内存session.setAttribute("sessionId", session.getId())配合前端存储,后续请求无法携带有效JSESSIONID CookieSet-Cookie: JSESSIONID=xxx; HttpOnly; Secure; Path=/; SameSite=Strict,使前端JS无法读取、仅HTTPS传输、防跨站冒用(Spring Security场景),或未验证X-CSRF-TOKEN Header,导致攻击者可诱导用户点击恶意链接完成非预期操作MockMvc怎么传Cookie和Header用MockMvc写集成测试模拟真实HTTP请求,光构造post("/login").param("username", "a").param("password", "b")远远不够——它不自动管理Session生命周期,也不会发回Cookie给下个请求。
正确做法是链式调用保留上下文:
ResultActions loginResult = mockMvc.perform(post("/login")
.param("username", "test")
.param("password", "pass123"))
.andExpect(status().is3xxRedirection())
.andExpect(header().string("Location", "/home"));
// 提取重定向后的JSESSIONID
String sessionId = loginResult.andReturn().getResponse().getCookie("JSESSIONID").getValue();
// 后续请求带上Cookie
mockMvc.perform(get("/profile")
.cookie(new Cookie("JSESSIONID", sessionId))
.header("X-Requested-With", "XMLHttpRequest"))
.andExpect(status().isOk());
注意:MockMvc默认不启用Cookie管理,必须显式提取并传递;若用@WebMvcTest且依赖SecurityMockMvcConfigurers.springSecurity(),还需额外配置csrf().disable()或提供合法token,否则403拦截。
不是编码问题,是HTTP协议层默认用ISO-8859-1解码表单数据,而UTF-8中文密码(比如含特殊符号或emoji)会被错误截断,导致new String(password.getBytes("ISO-8859-1"), "UTF-8")也救不回来。
根治方法只有两个:
web.xml或Spring Boot的application.properties里强制统一请求编码:server.tomcat.uri-encoding=UTF-8(Tomcat 8.5+)或spring.http.encoding.force=true
encodeURIComponent()对密码字段单独编码,后端用URLDecoder.decode(password, "UTF-8")解码——但此法绕过容器默认解析,需手动从request.getQueryString()或request.getInputStream()读原始字节
真正上线环境几乎都选第一种;第二种只在调试加密传输或JSON体提交时出现,此时Content-Type已是application/json,不受表单编码影响。