当你在JS中集成返回上一页并刷新的功能时,部分接口突然返回401错误,问题根源往往在于页面后退或刷新时认证令牌(token)丢失或未正确携带,解决这一问题的核心思路是:确保token在页面生命周期内持久化,并在每次请求时自动附加到请求头中。
为什么js返回上一页刷新会导致401错误?
页面后退和刷新是两个不同的操作,但它们都可能破坏已建立的认证状态,理解底层的触发机制,才能精准定位问题。
页面后退时状态丢失的常见场景
当用户点击浏览器后退按钮或调用window.history.back()时,浏览器默认会从缓存中加载页面,如果页面之前是通过JavaScript单页应用(SPA)方式加载的,后退可能触发页面完全重新加载,导致内存中的token变量被清空,很多开发者习惯将token存在JavaScript变量或Vuex/Pinia状态管理中,一旦页面重新加载,这些状态就会丢失,后续接口请求自然因缺少Authorization头而返回401。
请求拦截器未覆盖所有API调用
在集成JS时,你可能会使用axios或fetch的拦截器统一添加token,但如果有些接口是通过其他方式调用的,比如直接使用XMLHttpRequest或第三方库,拦截器可能没有覆盖到,更常见的是,在页面后退后,某些初始化请求发生在拦截器设置之前,导致token没有附加,据行业共识,这类问题在集成JS时部分接口401错误中占比相当大。
缓存策略与身份验证冲突
浏览器对后退页面的缓存策略(bfcache)可能会保留页面状态,但不会保留JavaScript的执行上下文,如果后端接口返回了Cache-Control: no-store头部,浏览器会强制重新请求页面,但token可能已经在之前的页面关闭时被清除,这种冲突在需要频繁后退的多页面应用中尤为突出。
排查集成JS时部分接口返回401的三个步骤
面对401错误,不要急于改代码,先按顺序排查,避免在无关方向上浪费精力。
第一步:检查网络请求的Authorization头
打开浏览器开发者工具的网络面板,找到返回401的那个接口,查看请求头中是否包含
Authorization: Bearer <token>,如果这个头不存在,问题出在请求发送侧,多数情况下,你会发现页面后退后第一次请求缺少该头,而后续请求正常,这直接指向了token在页面加载时的初始化问题。
第二步:验证token的存储与读取逻辑
确认token是否在页面刷新后仍然存在,在控制台输入localStorage.getItem('token')或sessionStorage.getItem('token'),看是否有值,如果值为空,说明存储方式不对,很多开发者误用了sessionStorage,但页面后退时如果页面是从缓存中恢复的,sessionStorage会被保留,但如果在页面关闭后重新打开,则会丢失,你需要根据业务需求选择正确的存储容器。
第三步:监听浏览器后退事件并主动刷新token
使用window.addEventListener('pageshow', function(event) { if (event.persisted) { / 从bfcache恢复 / } })可以捕获页面从缓存恢复的事件,在这个事件中重新读取token并更新请求拦截器,如果此时token已经过期,还需要调用刷新令牌的接口。js返回上一页刷新401错误往往就是这一步被忽略了。
使用持久化存储解决页面后退token丢失问题
一旦明确了问题出在token丢失,解决方案就很清晰了,下面提供三种经过验证的方法,你可以根据项目架构选择。
localStorage vs sessionStorage的选择
- localStorage:数据持久化,即使关闭浏览器再打开,token依然存在,适合需要长期保持登录状态的场景,但要注意,用户手动清除浏览器数据时会丢失。
- sessionStorage:数据在页面会话期间有效,但新标签页或窗口打开时会创建新的会话,页面后退时如果页面未关闭,sessionStorage仍然有效,但如果你在页面关闭后重新打开,token会丢失。
对于大多数后台管理系统,建议使用localStorage存储token,并在页面加载时读取,这是防止页面后退token丢失最直接的方法。
在axios拦截器中统一附加token
// 请求拦截器 axios.interceptors.request.use(config => { const token = localStorage.getItem('access_token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }, error => Promise.reject(error))
这个拦截器必须在应用初始化时注册,并且确保在页面任何加载路径下都能执行,如果页面后退触发了全页面刷新,拦截器会随新的应用实例重新注册,此时localStorage中的token会被读取并附加到请求中,这是解决集成JS时部分接口401错误的标配方案。
利用history API避免页面完全刷新
如果你希望用户在后退时页面不重新加载,可以使用history.pushState和history.replaceState控制路由,并配合popstate事件监听前进后退,在这种模式下,页面不会重新加载,token状态会保留在内存中,但要注意,如果用户直接点击浏览器刷新按钮,页面还是会重新加载,所以持久化存储仍然是必要的。
实际案例:后台管理系统后退刷新修复记
一个典型的例子是某后台管理系统的左侧菜单和右侧内容区,点击菜单项通过JS加载内容,当用户点击浏览器后退按钮时,页面完全刷新,所有接口返回401。
场景描述
系统使用Vue2 + axios,token存储在Vuex中,用户从页面A进入页面B,然后点击后退,浏览器执行了window.history.back(),页面重新加载,Vuex状态丢失,axios拦截器虽然注册了,但初始化时读取Vuex中的token,此时已经为空,导致后续所有接口请求都缺少Authorization头。
错误重现
在页面后退后,控制台报错401,网络请求中Authorization头确实不存在,第一个请求是获取用户信息接口,它返回401,导致整个应用进入未登录状态。
修复步骤
- 将token存储从Vuex改为localStorage,并在应用初始化时从localStorage读取并注入Vuex。
- 在axios拦截器中,如果localStorage中有token,直接使用,不再依赖Vuex。
- 在
pageshow事件中检查event.persisted,如果为true,重新从localStorage读取token并更新axios默认头。 - 对于需要刷新token的接口,在401响应拦截器中调用刷新接口,并使用队列防止重复刷新。
经过这些修改,js返回上一页刷新401错误不再出现,用户后退后页面依然保持登录状态。
js返回上一页刷新401错误常见问题与解答
为什么后退时只有部分接口401,其他接口正常?
通常是因为那些正常接口的请求是在页面加载前发出的,且token此时还未被清除,或者这些接口使用了不同的认证方式,比如cookie认证,而401接口则依赖Authorization头,且请求时机稍晚,token已经失效,检查一下接口的请求顺序和认证方式就能区分。
使用sessionStorage存储token,后退后依然丢失是怎么回事?
如果你在页面A中通过JS跳转到页面B,页面B使用了sessionStorage,在页面B中后退到页面A时,页面A会被重新加载,而页面A的sessionStorage在页面B中设置的值并不会传递给页面A,除非页面A和页面B是同一个来源的不同页面,但sessionStorage的作用域是当前页面,不同页面之间无法共享,除非它们是通过window.open打开的同一标签页,所以后退时页面A的sessionStorage可能没有该token,导致丢失,解决方案是统一使用localStorage或通过服务端session管理。
前端路由后退(比如vue-router的go(-1))也会引起401吗?
不会,因为vue-router或react-router的导航是前端路由,页面不会重新加载,token状态保留在内存中,只有当你在前端路由后退时触发了window.location.reload()或使用了window.location.href跳转,才可能导致401,所以建议使用前端路由的router.go(-1)代替history.back(),并配合beforeRouteEnter守卫检查token有效性。
解决JS返回上一页刷新时的401错误,关键在于让token的存储和读取不依赖页面内存状态,使用localStorage持久化,并在请求拦截器中统一处理,同时监听页面缓存恢复事件,确保token在页面后退后仍然可用,这样,无论用户如何后退或刷新,接口都能正确携带认证信息。
首发原创文章,作者:王坚,如若转载,请注明出处:https://idctop.com/article/546571.html




