侧边栏壁纸
博主头像
一笑痕

人生若只如初见,
是可喜亦或者是可悲?

  • 累计撰写 104 篇文章
  • 累计收到 7 条评论

跟着跳转走一趟,POST 和 Authorization 各在哪一步没了

2026-9-22 / 0 评论 / 15 阅读

跟着跳转走一趟,POST 和 Authorization 各在哪一步没了

前端表单提交后跳到一个新地址,接口那边收到的是 GET,参数全空。这类问题的定位成本高,因为浏览器不会把中间那几跳的过程告诉调用方:fetch 跟着跳转跑完,最后返回的是终点的 200,中间的 301 或 302 只在网络面板里留一行。

脚本 tools/verify-redirect-method.mjs,Node v26.8.1 的 fetch(undici),本机起两个服务端:一个跳转端,一个终点端。每个到达的请求都记下方法、请求体字节数、Authorization 头有没有带。

现象:同样的请求,五种状态码给出两种结果

=== 每个状态码:POST + 4 字节请求体 + Authorization 头,跟一次跳转 ===
301: 跳转端收到 POST(4B) → 终点端收到 GET(0B) 终点端 Authorization: 带 最终状态 200,follow 后 url=/t
302: 跳转端收到 POST(4B) → 终点端收到 GET(0B) 终点端 Authorization: 带 最终状态 200,follow 后 url=/t
303: 跳转端收到 POST(4B) → 终点端收到 GET(0B) 终点端 Authorization: 带 最终状态 200,follow 后 url=/t
307: 跳转端收到 POST(4B) → 终点端收到 POST(4B) 终点端 Authorization: 带 最终状态 200,follow 后 url=/t
308: 跳转端收到 POST(4B) → 终点端收到 POST(4B) 终点端 Authorization: 带 最终状态 200,follow 后 url=/t

第一跳五种状态码收到的都是一样的报文:POST,4 字节请求体,头部带着。终点端收到的就不一样了,301、302、303 三条把方法换成了 GET,请求体变成 0 字节;307 和 308 原样带过去。同源跳转里 Authorization 都保留。

影响面:登录态不会丢,参数会丢

方法被换掉之后,请求体不再发送,那些挂在 body 里的字段全部消失。表单参数、application/json 的 payload、上传字段都属于这一档。带在 URL 上的 query 不受影响,仍能到达终点。

反过来说,Authorization 这类头部在跨源那一跳才出问题:

=== 跨源跳转(端口不同算跨源):Authorization 还带不带 ===
302: 终点端收到 GET(0B),Authorization 没带,host=/end
307: 终点端收到 POST(4B),Authorization 没带,host=/end

本机实验里「跨源」只是端口不同。302 那一行方法与请求体都变了,307 那一行方法与请求体保住了,但两行的 Authorization 都没到终点。到了别的域名上,浏览器不会把凭证带过去,这是同源策略的一部分。常见的登录跳转失效就是这样来的:网关 307 到另一个域名,方法明明保住了,令牌却没了,终点返回 401,前端只看到「请求失败」。

定位:先看跳转是在哪一端发生的

链路上有两个可能出问题的地方,排查顺序可以固定下来。第一跳的方法对不对,看跳转端收到的日志;到了终点还没到,就继续看终点的日志。两个服务端各自记账之后,「方法在哪一步变的」这个问题会有明确答案,不用靠猜。

fetch 的两个选项能帮着把过程摊开。redirect: 'manual' 会把 302 原样返回,跳到哪由调用方自己决定。

=== redirect 选项:manual 与 error ===
manual: 状态 302,location=http://127.0.0.1:55068/t,终点端收到 0 次
error: TypeError: fetch failed
error 之后终点端收到 0 次

manual 这一行的价值在于终点端收到 0 次,说明请求停在了跳转端,可以自己决定要不要发第二次。redirect: 'error' 则直接抛错,同样没有第二次请求。想看清跳转,manual 比连着日志翻快。

跳转次数上限也顺手测了:一个自己指自己的循环,服务端一共收到 21 次请求,然后 fetchTypeError: fetch failed。这个上限在不同实现里不一样,写爬虫或代理时不要假设它足够大,也不要假设它一定会抛。

改法:把跳转留给调用方,或者选对状态码

网关和反向代理这一层,配置文件里写 301 还是 307 是个明确的选择。需要在跳转后保留方法与请求体的接口,用 307;只是把浏览器导向一个新地址的,301 和 302 都可以,但要想清楚 POST 会变成 GET 这件事。历史上 303 就是为「POST 完去看结果页」这个场景定义的,方法换成 GET 属于它的本意。

客户端这一侧,涉及写操作的请求建议用 redirect: 'manual',自己读 Location 再决定下一条请求怎么发,凭证要不要带也由自己判断。这样跳转链路的每一步都在代码里,出问题时日志里能看到是哪一跳丢的。

这一条没实测:跨源跳转丢 Authorization 的具体边界,各浏览器在 fetch 与 XHR 上是否完全一致,只测了本机的这一种实现。这一点我不敢当成结论用,要在目标浏览器上再核一遍才敢下判断。

    🤞 分享