登录验证码功能报:验证码已失效
Published on Nov 25, 2025, with 6 view(s) and 0 comment(s)
Ai 摘要:摘要: 登录验证码功能因跨域问题导致验证码失效。前端与API域名不同引发跨域,浏览器默认阻止跨域Cookie。解决方案包括Nginx设置`SameSite=None; Secure`,配置CORS允许凭证,并确保前端请求携带凭证。

在打开认证验证码功能时遇到了一个这样的问题:
验证码已失效,请刷新后重试

相关后端代码:

// 验证码发送
public void getCaptcha(HttpServletRequest request, HttpServletResponse response) throws IOException {
        // 定义图形验证码的宽、高、验证码字符数、干扰线的条数
        LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(100, 30, 4, 20);

        // 获取验证码中的文本
        String code = lineCaptcha.getCode();

        // 将验证码存储在会话中,用于后续验证
        request.getSession().setAttribute("captcha", code);

        // 设置响应的内容类型为图片
        response.setContentType("image/png");

        // 获取输出流
        ServletOutputStream outputStream = response.getOutputStream();

        // 将验证码写出到输出流
        lineCaptcha.write(outputStream);

        // 关闭输出流
        outputStream.close();
    }
    
// 登录
public R<LoginUserVo> login(String username, String password, String captcha, HttpServletRequest request) {
        try{
            // 0. 校验验证码
            String sessionCaptcha = (String) request.getSession().getAttribute("captcha");
            if (sessionCaptcha == null) {
                throw new BusinessException(400, "验证码已失效,请刷新后重试");
            }
            if (!sessionCaptcha.equalsIgnoreCase(captcha)) {
                throw new BusinessException(400, "验证码错误");
            }
            // 验证码一次性使用
            request.getSession().removeAttribute("captcha");
            ...
         }
  }

原因分析:获取验证码后,调用login接口时没有携带上cookie。
为啥没有携带嘞?
因为部署时前端地址为http://lvmaoya.cn:8520,api地址为https://api.lvmaoya.cn。发生跨域,现在所有现代浏览器(Chrome、Safari、Edge):跨域 cookie 必须 SameSite=None,否则浏览器会自动阻止。

解决方案

  1. Nginx 设置 Set-Cookie:
proxy_cookie_path / "/; SameSite=None; Secure"; 
// 检验可行

  2. springBoot + springScurity应用配置:应该在这里配置sameSiteCookies相关属性,没有试过。

public CorsFilter corsFilter() {
        // 创建CorsConfiguration对象
        CorsConfiguration config = new CorsConfiguration();
        // 允许所有来源(根据需求可以设置为具体的域名)
        config.setAllowedOrigins(Arrays.asList(
                "https://lvmaoya.cn",
                "http://localhost:3001",
                "http://lvmaoya.cn:8520"
        ));
        // 允许所有请求头
        config.addAllowedHeader("*");
        // 允许所有HTTP方法(GET、POST、PUT、DELETE等)
        config.addAllowedMethod("*");
        // 允许携带凭证(如Cookie)
        config.setAllowCredentials(true);

        // 创建UrlBasedCorsConfigurationSource对象
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        // 对所有路径应用CORS配置
        source.registerCorsConfiguration("/**", config);

        // 返回CorsFilter
        return new CorsFilter(source);
    }

⚠️ 注意

  1. 前端在请求时 axios.defaults.withCredentials = true; 否则 cookie 依然带不上。
  2. SameSite=None 时浏览器要求同时:Secure(HTTPS 才允许)

回顾一下这整个验证流程

① 前端请求 /captcha
        ↓
② 后端创建 Session → 保存验证码到 Session
        ↓
③ 响应返回时发送 Set-Cookie(JSESSIONID)
        ↓
④ 浏览器保存 Cookie
        ↓
⑤ 前端请求 /login 带上同样的 Cookie
        ↓
⑥ 后端通过 JSESSIONID 找到同一个 Session
        ↓
⑦ 后端取出 session["captcha"] 进行验证