刷新一次就掉登录:一次性票据只肯被消费 1 次
先说现象:我把一个刚登录完的页面刷新了一下,回来就变成未登录状态。地址栏里还挂着 `?ticket=ST-xxxxxxxx` 这样的参数。 结论先说:这类 URL 不是"页面地址",是一次性票据的回跳地址。它只能被消费一次。 ## 票据是怎么被消费的 SSO 的典型流程是这样: 1. 你在 A 站完成认证 2. A 站发给你一个一次性票据,把你 302 到 B 站:`https://b.example.com/home?ticket=ST-xxx` 3. B 站拿 ticket 去服务端校验,换回自己的 session cookie,同时把这张票标记为已消费 4. 浏览器这时候已经拿到 B 站的 cookie,票据的使命结束了 问题出在第 4 步:**票据没有任何人清理**,它还原样留在地址栏里。 于是按 F5 会发生什么: ``` GET /home?ticket=ST-xxx → 302 /login?error=ticket_used ``` 服务端收到一张已消费的票,标准做法是拒绝。有些实现会顺手把当前 session 一起清掉——从用户的角度看,就是"刷新一下,登录没了"。 ## 怎么确认是这个原因 不用猜,看两样东西就够。 第一,看响应头: ```bash curl -sI 'https://b.example.com/home?ticket=ST-xxx' | head -5 ``` 如果返回 `302`,`Location` 指向登录页或错误页,基本就定了。 第二,看 cookie 还在不在: ```js document.cookie.split(';').map(s => s.trim().split('=')[0]) ``` session 那个名字在,说明服务端没清 session,只是页面判定要求重新认证;不在了,说明服务端主动清掉了。这两种情况的处理方式完全不同,别混。 ## 正确做法 落地页拿到 session 之后,第一件事是把票据从地址栏抹掉: ```js if (location.search.includes('ticket=')) { const u = new URL(location.href); u.searchParams.delete('ticket'); history.replaceState({}, '', u.toString()); } ``` 用 `replaceState` 而不是 `pushState`:它不往历史栈里加记录,用户按后退不会又回到带票据的那个地址。 服务端也该做一层:**校验通过后 302 到不带参数的干净地址**,别让票据留在浏览器里。这一步比前端兜底更可靠,因为用户完全可能在页面脚本跑起来之前就按了刷新。 ## 一个容易忽略的细节 票据校验失败之后往往不是直接报错,而是给一条重定向链:错误页 → 登录页 → 登录页的静态资源。整条链走完,体感就变成了"网卡了"。 所以排查这类问题别看页面提示,看状态码和 cookie 在不在。页面说"请重新登录",可能是因为 session 真没了,也可能只是票据脏了、服务端选择保守处理。 ## 小结 - 带 `ticket` / `code` / `token` 参数的落地 URL,默认按"只能消费一次"对待 - 前端用 `replaceState` 抹掉参数,服务端用 302 跳到干净地址,两边都做最稳 - 排查顺序:先看状态码,再看 cookie,最后才看页面文案
- 点赞
- 收藏
- 关注作者
评论(0)