一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第3天,点击查看活动详情
哈喽,大家好 我是
xy👨🏻💻。今天给大家来分享一个bug🐞。在同一浏览器打开两个同源页面发生的数据串改的问题,很多大型网站都中招了 💡
背景
一天下午,某某上帝(客户就是上帝)正在使用的我们公司的产品,在浏览器打开A标签页登录了a账号,然后又到B标签页登录了b账号,也就是同时登录了两个账号,然后到A下去操作数据(不刷新页面),结果造成了在A下修改到了B的数据,而且两个账号的权限还存在差异 💥
为什么会被串改
至于为什么会修改到 B 的数据:
前端把用户信息和 token 之类的是存储在浏览器本地的,这样一来,打开新的标签页登录其它账户,由于是同源页面,新的标签页的用户信息和token肯定会覆盖掉前一个标签页的用户信息和token,在页面不刷新的情况下,操作a账户下的数据,其实是操作的b下的数据
很多同学遇到这个问题的第一反应:
这个不是很正常的操作吗或者让用户刷新页面不就好了,包括我也是这样想的 🤣
同时也查看了市面上的一些产品,同样有类似的问题,就拿我们比较熟悉的 码云 来说吧,我特地的注册了两个账号
- 注册的新账号在第一个标签页登录
因为是全新的账号,没有任何信息之类的
- 打开新的标签页,先退出登录,然后登录我之前一直使用的账号
自己一直使用的账号通知和私信还是有不少内容的
- 回到第一个标签页
账户明明是新的账户,但是却把自己使用的旧账户的数据给展示过来了
当时心里暗喜,码云这么大的平台都没做处理,我们应该也无所谓了 ✌️
但是我就是个打工仔,搞不搞还不是领导说了算吗?既然领导已经提了这个需求,并且强烈要求优化用户体验,只能想办法解决喽 😩
需求方案
方案 1:
- 同一浏览器支持
同时登录两个账号,互不影响
方案 2:
- 同一浏览器打开两个同源页面,把
前一个标签页面提示用户强制刷新或者直接重定向到公司官网
回归问题本质
这个问题的本质其实就是:由于是
同源页面,新的标签页的用户信息和token会覆盖掉前一个标签页的用户信息和token
既然本质问题是本地存储替换的问题,那就想办法着手解决喽 🙍♂️
如何做到同时登录两个账号
做到两个账号同时登录互不干涉,其实只需要保证本地存储互不影响,让不同的用户存储的 token 的键名不一样
实现方法:在登录的时候存储用userName+token作为键来存储token
import Cookies from 'js-cookie'
export function setToken( token, userName ) {
return Cookies.set( userName + 'token', token, { expires: xxxx } )
}
但是需要考虑到这个时候用户名:userName也可能会被覆盖,所以在每次刷新页面之前,把用户名存储到sessionStorage中去
window.addEventListener("beforeunload", () => {
sessionStorage.setItem("userName", this.userName || "");
});
为什么要放到sessionStorage中呢 ?
💡 因为
sessionStorage生命周期为当前窗口或标签页,也就是每个标签页中的sessionStorage互不影响,即使是同源页面
获取 token 直接根据userName+token获取即可
export function getToken() {
return Cookies.get( sessionStorage.getItem( 'userName' ) + 'token' )
}
好了,到这里基本上完美解决想要同一个浏览器登录多个用户账号的问题
前一个标签强制刷新或者重定向
再次回到问题本身,还是本地存储的问题。打开第二个同源页面标签页,有没有办法能够告诉第一个标签页呢???
当然有办法了,我们可以利用 HTML5 storage事件监听:
💡
HTML5 storage事件监听:当同源页面的某个页面修改了localStorage,其余的同源页面只要注册了storage事件,就会触发
Web Storage API内建了一套事件通知机制,当存储区域的内容发生改变(包括增加、修改、删除数据)时,就会自动触发 storage 事件,并把它发送给所有感兴趣的监听者。因此,如果需要跟踪存储区域的改变,就需要在关心存储区域内容的页面监听storage事件。
window.addEventListener("storage", (e)=>{
// 获取 e 后做一系列判断操作
}, false);
实际上,这个事件e上还带有很多信息,可以对事件做精确的控制 👇。
| 字段 | 含义 |
|---|---|
| key | 发生变化的 storageKey |
| newValue | 变换后新值 |
| oldValue | 变换前原值 |
| storageArea | 相关的变化对象 |
| url | 触发变化的 URL,如果是 frameset 内,则是触发帧的 URL |
有了这些内容,就可以根据自己的业务需求来做需要的操作了。
但是这里注意一个问题,只有当同源页面的某个页面修改了localStorage,也就是不修改,还是监听不到的。
如果直接把第一个标签页的链接复制到第二个标签页,本地存储是不会更改的,这个时候第一个标签页的监听事件将不会触发
这个时候可以在每次页面初始化的时候在 localStorage 中写入一个唯一的标识,建议是时间戳之类的,这样即使把地址复制到第二个标签页中,也会执行初始化操作,时间戳改变就会触发监听的storage事件。
ok 了,问题统统解决
💡 最后想
@码云以及很多存在此类问题的网站都提出这个优化点,2022,让我们变得更好吧 🤞。
写在最后
大家好,我是一名前端🤫爱好:瞎折腾
如果你也是一名瞎折腾的前端欢迎加我微信交流哦...