背景
「权限控制」几乎是 Web 应用必备的功能,尤其是 toB 的应用,还会产生单点登录(SSO)的需求。目前实现 SSO 的方案都避免不了各种 302 跳转,当用户访问的 URL 触发了 302 还好说,可以成功的实现登录。但是,当由 JS 发起的 AJAX/fetch 请求碰到 302 的场景就不那么顺利了,具体场景如下:
- 用户长时间未操作页面,导致登录过期,但是页面还保留在浏览器上;
- 此时用户操作页面,触发了 AJAX/fetch 请求给服务端;
- 服务端发现用户登录失效,返回
302和Location;
此时,问题就出现了,AJAX/fetch 不会按照预期处理 302,具体会发生什么,请见下文。
场景模拟测试
用 express 起一个服务,前端分别用 axios 和 fetch 请求,测试各种场景的返回情况。
express 服务端代码
router.get("/302", function (req, res, next) {
res.redirect(302, "https://www.baidu.com");
});
router.get("/401", function (req, res, next) {
res.redirect(401, "https://www.baidu.com");
});
router.get("/json", function (req, res, next) {
res.header("key1", "key1-value");
res.json({ success: true });
});
浏览器正常访问 302
axios 测试
axios.get('/json').then(res => {
console.log("Axios Basic Res:",res)
}).catch(err => {
console.log("Axios Error:", err)
})
普通 json 返回
302 返回
- 浏览器抛跨域异常;
- Axios 走
catch逻辑; - JS 抛运行异常;
小结
- 302 返回直接被浏览器处理了,根本不会传到 JS(Axios)这层;
- 浏览器 redirect 后,被视为 Axios 发起了跨域请求,所以抛异常;
- Axios 捕获了网络异常,走了 catch 逻辑后抛出;
所以 Axios 等基于 xhr 的库对于 302 是无能为力的,会被浏览器在上层就「截胡」。
fetch 测试
听说 fetch 有个 redirect 属性可以控制 302 的返回,让我们来试一下。
fetch('/302', { redirect: 'follow' }).then(async res => {
console.log("Fetch Basic Res:", res)
console.log("Fetch JSON Res:", await res.json())
}).catch(err => {
console.log("Fetch Error:", err)
})
普通 json 返回
默认 redirect: 'follow' 的 302 返回
- 浏览器抛跨域异常;
- JS 抛运行异常;
- fetch 走
catch逻辑;
设置了 redirect: 'manual' 的 302 返回
- 无任何异常;
- fetch 走
then逻辑;
注:下图走了 catch 逻辑是因为 res.json 的使用,与请求无关。
设置了 redirect: 'error' 的 302 返回
- JS 抛运行异常;
- fetch 走
catch逻辑;
小结
fetch 可以通过设置 redirect 属性成功拦截到 302 并进行处理,但是无论设置为 manual 还是 error 都无法获取到 headers 中的 Location 信息。
所以,即使 fetch 能够不被浏览器「截胡」,也无法获取到想要的 Location 信息,只能在代码当中「写死」要跳转的 URL。
如果 302 的地址支持 CORS
上面的例子中,是因为 baidu 没有支持 CORS 所以抛了异常。严谨的同学可能会问,如果我 302 到一个支持跨域访问的服务会怎样呢?经过笔者测试,结论如下,就不贴截图了:
- 通常 JS 发的请求都要求
content-type是 JSON,所以如果 302 的地址也是返回 JSON 格式的内容,那么一切都会很顺利; - 如果 302 的地址返回的是
text/html等非 JSON 格式的数据,就会抛 JS 解析 JSON 的异常,这种情况才是最常见的; - 也有比较 hack 的办法:当发现 response 的
content-type不是 JSON 时,停止 JSON 解析逻辑,进行location.href跳转。但是强烈不推荐!!!太 hack 了,这就是个大坑!!!
总结
- 像 Axios 这种基于 xhr 的库会被浏览器「截胡」,所以无法处理 302;
- fetch 可以不被「截胡」,但是无法从 response 获取到其他信息,所以也无法实现预期;
所以对于 JS 发起的非浏览器跳转请求,还是推荐:返回 401 并在 headers 中携带 Location 信息。