飘易博客(作者:Flymorn)
订阅《飘易博客》RSS,第一时间查看最新文章!
飘易首页 | 留言本 | 关于我 | 订阅Feed

浏览器多标签场景:多账号同时登录网站系统如何防止串号?

Author:飘易 Source:飘易
Category:网站设计 PostTime:2026/3/22 9:55:30
正 文:

网站采用API-JWT + VUE前端架构设计,用户登录后将 Bearer Token 存储到localStorage或Cookie里,同时设置有效期。在单用户登录时没有问题,但是多个用户在同一浏览器里同时登录时会遇到这样一种场景:

先在浏览器里打开一个标签P1登录了A用户,然后新开一个标签P2登录了B用户,此时P1标签里实际读取的用户token已经变成了B用户,如果此时操作提交数据,你以为你还是操作的A用户,实际上已经是B用户的资料了,这就发生了俗称“串号”的现象。 专业上会说是 Session Bleed(会话渗漏):这是专业、形象的说法。就像液体渗漏一样,一个人的数据流到了另一个人的页面上。


如何在现有网站架构上最低成本解决这个串号问题呢?


飘易提供一种思路,前端 + 后端低成本解决串号:


一、前端

1、用户登录成功后,利用 sessionStorage 特性:sessionStorage 的生命周期仅限于标签页或窗口存活期间,一旦浏览器进程彻底关闭,数据就会被清空。

登录逻辑里:我们把当前登录成功的用户ID写入 sessionStorage

sessionStorage.setItem('locked_user_id', user.id);// 防串号-锁死用户ID
setStore({ name: 'userinfo', content: user });// 同时存入localStorage

还要考虑一种情况,我们的token是会被持久化存储一段时间的,在这段时间内,如果浏览器被关闭后,重新打开系统页面,此时是不会有 登录逻辑的,这种情况下,我们要在 App.vue 或 路由守卫里设置初始的用户ID:

// 防串号 - App.vue
if (!sessionStorage.getItem('locked_user_id')) {
    // 如果是重新打开浏览器,尝试从持久化存储localStorage恢复
    let user = getStore({ name: 'userinfo' });
    if (user && user.id) {
        sessionStorage.setItem('locked_user_id', user.id);
    }
}


2、axios 全局拦截调整:

请求拦截

//HTTPrequest拦截
axios.interceptors.request.use(config => {

  if (getToken()) {
    config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带token
  }
  const locked_user_id = sessionStorage.getItem('locked_user_id');// 防串号
  if(locked_user_id){
    config.headers['X-Verify-User-ID'] = locked_user_id;
  }

  return config
}, error => {
  return Promise.reject(error)
});

响应拦截:

//HTTPresponse拦截
axios.interceptors.response.use(res => {
  const status = Number(res.status) || 200;
  const resData = res.data || {};

  // 处理身份隔离冲突 (403) - 串号
  if (status === 403 || resData.errcode === 403) {
    handleIdentityConflict(); // 调用下方定义的弹窗函数
    return Promise.reject(new Error('Identity Conflict'));
  }
  // 其他逻辑...
  
  // 只有完全正确才返回数据
  return res;
}, error => {
  return Promise.reject(new Error(error));
});

handleIdentityConflict 函数的定义:

/**
 * 封装身份冲突后的弹窗逻辑
 */
function handleIdentityConflict() {
  // 防止重复弹出多个确认框
  if (window.isShowingIdentityConfirm) return;
  window.isShowingIdentityConfirm = true;
  // 确认交互
  MessageBox.confirm(
    `检测到您已在其他标签页登录了新账号,当前页面已失效。是否切换到最新账号继续操作?`,
    '身份环境变更',
    { confirmButtonText: '立即切换', cancelButtonText: '留在原地', type: 'warning' }
  ).then(() => {
    let user = getStore({ name: 'userinfo' });
    if (user && user.id) {
      sessionStorage.setItem('locked_user_id', user.id);// 更新本标签页的锁,对齐身份
      location.reload();// 刷新页面,让页面加载新客户的数据
    }
  }).finally(() => {
    window.isShowingIdentityConfirm = false;
  });  
}

handleIdentityConflict 这个函数需要注意避免一个页面多次触发,因为一个页面里可能有多个ajax请求,如果不做处理,就会触发多次提醒,对用户并不友好,所以我们这里增加一个全局变量,来判断当前页面是否已经触发提醒了。


二、后端

后端一般在中间件里对请求进行判断,这里飘易的后端是LUMAN,中间件这样修改:

public function handle($request, Closure $next)
{
    // 1. 原有的身份认证(获取当前登录的用户对象)
    $user = $request->user('user');

    if (empty($user)) {
        return response()->json([
            'errcode' => 401,
            'errmsg' => 'Please provide the correct credentials'
        ]);
    }

    // 2. 获取前端通过 Header 传过来的“标签页锁定 ID”
    $headerUserId = $request->header('X-Verify-User-ID');

    // 3. 核心校验:如果前端传了 ID,则必须与当前 Token 所属的 ID 一致
    if ($headerUserId && $user->id != $headerUserId) {
        return response()->json([
            'errcode' => 403, 
            'errmsg' => '身份环境已变更,请关闭当前页面',
            'debug_info' => [
                'expected' => $user->id,
                'received' => $headerUserId
            ]
        ], 403);
    }

    return $next($request);
}

核心是判断请求头里的 X-Verify-User-ID 和token解析出来的用户ID 是否一致,不一致就报 403 错误,让前端收到403状态码再去做对应的提示。

其他框架也是类似处理。

作者:飘易
来源:飘易
版权所有。转载时必须以链接形式注明作者和原始出处及本声明。
上一篇:PHP加密混淆方案对比选型 & YAK Pro Php Obfuscator介绍
下一篇:阿里云SMTP邮件服务如何集成到系统?
0条评论 “浏览器多标签场景:多账号同时登录网站系统如何防止串号?”
No Comment .
发表评论
名称(*必填)
邮件(选填)
网站(选填)

记住我,下次回复时不用重新输入个人信息
目 录
飘易搜索
最新文章
相关文章
随机文章
© 2007-2030 飘易博客 Www.Piaoyi.Org 版权所有 Sitemap