请停止在 401 错误时重定向到登录 🛑

发布: (2026年1月16日 GMT+8 05:57)
4 min read
原文: Dev.to

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。

一个健壮的应用会这样处理:

  1. 拦截 – 捕获 401 错误。
  2. 排队 – 暂停失败的请求;不要重新加载页面。
  3. 刷新 – 在后台获取新令牌(使用刷新令牌) 显示模态框让用户重新输入密码。
  4. 重试 – 认证成功后,使用新令牌重新发送原始请求。

用户甚至不会注意到;表单成功保存。

如何测试?(难点)

实现“静默刷新”很棘手,测试起来也很烦人。访问令牌通常有效一小时,所以你不能让 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 在网络层剥离头部。

Back to Blog

相关文章

阅读更多 »

Axios

Axios 的封面图片 https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com...