请停止在 401 错误时重定向到登录 🛑
Source: Dev.to
“懒惰”模式
为什么会出现这种情况?通常是因为 JWT(访问令牌)已过期,后端返回 401 Unauthorized,前端代码正好按照教程的做法:
// Don't do this
axios.interceptors.response.use(null, error => {
if (error.response.status === 401) {
window.location.href = '/login'; // RIP data 💀
}
return Promise.reject(error);
});
开发者常常争辩:“但这是安全要求!会话已经失效!”
是的,会话已经失效,但这并不意味着必须丢失当前页面的状态。
更好的方式(弹性)
如果用户只是浏览仪表盘,重定向是可以接受的。但如果他们有未保存的输入(表单、评论、设置),重定向就是一个 bug。
一个健壮的应用会这样处理:
- 拦截 – 捕获 401 错误。
- 排队 – 暂停失败的请求;不要重新加载页面。
- 刷新 – 在后台获取新令牌(使用刷新令牌) 或 显示模态框让用户重新输入密码。
- 重试 – 认证成功后,使用新令牌重新发送原始请求。
用户甚至不会注意到;表单成功保存。
如何测试?(难点)
实现“静默刷新”很棘手,测试起来也很烦人。访问令牌通常有效一小时,所以你不能让 QA “等 60 分钟再点击保存”。
你需要一种方式,在点击按钮的同一时刻触发 401 错误,即使令牌仍然有效。
“混沌”方法
与其等待令牌自然过期,不如在请求进行中删除它。
使用 Playwright,可以拦截即将发送的请求并剥离 Authorization 头部,使其在到达服务器前被删除。这样后端会拒绝请求,立即触发你的恢复逻辑。
def test_chaos_silent_logout(page):
# 1. Login and go to a form
page.goto("/login")
# ... perform login logic ...
page.goto("/settings/profile")
# 2. Fill out data
page.fill("#bio", "Important text I don't want to lose.")
# 3. CHAOS: Intercept the 'save' request
def kill_token(route):
headers = route.request.headers
# Manually delete the token to simulate expiration
if "authorization" in headers:
del headers["authorization"]
# Send the "naked" request; backend will return 401
route.continue_(headers=headers)
# Attach the interceptor
page.route("**/api/profile/save", kill_token)
# 4. Click Save
page.click("#save-btn")
# 5. Verify the app recovered
expect(page.locator("#bio")).to_have_value("Important text I don't want to lose.")
expect(page.locator(".success-message")).to_be_visible()
总结
网络故障和令牌过期是生活的常态。你的应用应当在不惩罚用户的前提下处理它们。把 401 Unauthorized 当作可恢复的错误,而不是致命崩溃。
PS:如果需要在无法运行 Playwright 脚本的真实移动设备上进行测试,可以使用 Chaos Proxy 在网络层剥离头部。