从一个HTTP 302 重定向到数据类型不匹配的 bug 纠错过程

大家好,我是三桂,一个热爱前端和后端开发的程序员。今天,我想分享一个最近在开发一个用户管理系统时遇到的坑。这个系统用 Vue.js 做前端,Spring Boot + Spring Security做后端,涉及用户添加和编辑功能。原本以为只是个简单的 CRUD 操作,结果一个 PUT 请求的 302 重定向让我调试了好几个小时。这篇文章当作我的学习教训,记录从问题发现到最终解决的全过程,希望能帮到其他遇到类似问题的朋友。记住:调试时,多看日志,多用工具,别急于改代码!

问题起源:一个简单的用户编辑功能

项目背景:我用Vue.js构建前端表单,用户可以添加或编辑用户信息,包括登录账号、密码、姓名、手机、邮箱,以及四个状态字段:accountNoExpired(账户未过期)、credentialsNoExpired(凭证未过期)、accountNoLocked(账户未锁定)、accountEnabled(账户启用)。这些状态字段在数据库中是Integer类型(1表示正常,0表示异常)。

前端提交代码用axios发送POST(添加)和PUT(编辑)请求到后端API /api/user。后端用Spring Security管理认证,带JWT token验证,还用Redis存储token。

一切看起来正常,但当我测试编辑用户(PUT请求)时,浏览器控制台显示请求返回302 Found,重定向到/login。奇怪的是,POST和GET请求都正常,只有PUT出问题。而且,F12网络面板显示重定向后,浏览器疯狂发送1000多个/login的OPTIONS请求,形成循环,导致页面卡死。

初始症状

  • PUT /api/user 返回302,Location头指向/login。

  • 随后/login的OPTIONS请求返回200,然后又一个OPTIONS返回302,循环不止。

  • 后端日志没报异常,前端日志显示请求失败,但没具体错误。

我当时想:这是URL被拦截?还是token验证失败?或者Spring Security配置问题?

排查过程:从表面到深层,一步步试错

第一阶段:怀疑网络和配置问题

我先检查了基本的东西:

  • 请求头:F12确认PUT请求带了Authorization头,token正确(从sessionStorage或localStorage获取)。

  • CORS配置:后端已允许所有方法和头,OPTIONS请求返回200,排除跨域问题。

  • 代理/WAF:我没用Nginx或Cloudflare,直接本地运行,排除代理拦截。

试错1:我用Postman手动发送PUT请求,带token和数据,结果还是302。说明不是浏览器问题,是后端逻辑。

教训:调试时,先用工具如Postman隔离前端问题。

第二阶段:深入Spring Security和过滤器

我怀疑是认证问题,因为302重定向常见于未认证请求。

  • 检查TokenVerifyFilter:这是自定义过滤器,验证JWT、Redis token匹配。如果失败,应该返回JSON错误(CodeEnum.TOKEN_IS_EXPIRED等),但我没看到JSON响应,而是302。

  • 检查SecurityConfig:配置了formLogin,loginProcessingUrl是/api/login,permitAll()只允许/api/login,但默认登录页面是/login,未明确permitAll(),导致/login被拦截。

试错2:我修改SecurityConfig,添加/login到permitAll(),并在TokenVerifyFilter放行/login和OPTIONS请求。结果:循环减少,但PUT还是302。

试错3:禁用CSRF(已禁用),检查是否不支持PUT方法。但后端有@PutMapping,文档支持。

试错4:添加日志到TokenVerifyFilter,确认token收到且Redis匹配。日志显示验证通过,但还是302。说明过滤器放行后,Spring Security后续逻辑出问题。

错误修改:我一度怀疑是formLogin配置问题,试着设置loginPage("/api/login"),但没解决,因为问题不是登录端点。

教训:重定向循环往往是配置问题,但要区分loginProcessingUrl(处理登录)和loginPage(显示页面)。

第三阶段:分析重定向细节

F12网络面板成了救星:

  • 第一:OPTIONS /api/user 200(预检)。

  • 第二:PUT /api/user 302,带token和JSON负载。

  • 第三:OPTIONS /login 200。

  • 第四:OPTIONS /login 302,循环开始。

为什么/login收到OPTIONS?OPTIONS是CORS预检,通常为复杂请求如PUT。但/login应该是GET或POST,为什么预检?

试错5:怀疑前端FormData导致Content-Type是multipart/form-data,而后端期望application/json。切换为JSON发送,但还是302。

错误修改:我改了前端用普通对象发送JSON,但没注意字段值是"正常"(字符串),后端是Integer,导致解析失败。

教训:看F12时,不仅看状态码,要看负载和响应头(Location)。也检查数据类型匹配。

第四阶段:最终发现——数据类型不匹配

我盯着前端负载:

json

{
  "id": 2,
  "loginAct": "yuyan",
  "loginPwd": "",
  "name": "于嫣啊",
  "phone": "17438374938",
  "email": "yuyan@163.com",
  "accountNoExpired": "正常",
  "credentialsNoExpired": "正常",
  "accountNoLocked": "正常",
  "accountEnabled": "正常"
}

后端UserQuery类:

java

public class UserQuery {
  // ...
  private Integer accountNoExpired;
  // ...
}

啊哈!"正常"是字符串,Spring解析@RequestBody时无法转为Integer,抛出HttpMessageNotReadableException。后端没捕获这个异常,默认转为302重定向到/login。

确认:我查了Spring文档,JSON解析失败会触发错误处理,如果没自定义,会重定向。

解决:修改前端:

javascript

formData.append('accountNoExpired', this.addUser.accountNoExpired == "正常" ? "1" : "0");
// 类似其他字段

测试:PUT成功,返回200!循环消失。

为什么用FormData有效?因为multipart/form-data用字符串提交,Spring自动转为Integer("1" -> 1),而JSON严格匹配类型。

教训:前后端数据类型必须一致!用JSON时,发送数字而非字符串。

  • 微信
  • 赶快加我聊天吧
  • QQ
  • 赶快加我聊天吧
  • weinxin
三桂

发表评论 取消回复 您未登录,登录后才能评论,前往登录