You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何限制网站仅允许单个会话、标签页或浏览器窗口访问?

这个需求挺有挑战性的——要同时限制同浏览器多标签/窗口和跨浏览器的并发访问,纯前端JS只能搞定前者,跨浏览器的限制必须结合后端会话管理。下面给你拆解两种可行方案:

一、纯前端JS实现同浏览器多标签/窗口限制

利用localStorage的同源共享特性(同域名下所有标签页共用),我们可以给每个页面分配唯一会话ID,定时检查当前页面的ID是否和存储的一致,不一致就说明有其他标签页活跃,直接关闭当前页面。

核心代码示例

// 生成唯一会话ID(时间戳+随机字符串)
function generateSessionId() {
  return Date.now() + '-' + Math.random().toString(36).substr(2, 9);
}

window.addEventListener('load', function() {
  const storageKey = 'active_site_session';
  const currentSessionId = generateSessionId();
  
  // 检查是否已有未过期的活跃会话
  const storedSession = localStorage.getItem(storageKey);
  if (storedSession) {
    const [id, expires] = storedSession.split('|');
    // 会话未过期则关闭当前页面
    if (Date.now() < parseInt(expires)) {
      // 部分浏览器不允许关闭用户手动打开的页面,这里可以替换成隐藏内容+提示
      alert("已有其他窗口正在访问本站,当前窗口将关闭");
      window.close();
      return;
    }
  }
  
  // 存储当前会话ID+5分钟过期时间(避免页面崩溃后无法重新访问)
  const expiresTime = Date.now() + 5 * 60 * 1000;
  localStorage.setItem(storageKey, `${currentSessionId}|${expiresTime}`);
  
  // 每秒检查会话有效性
  setInterval(function() {
    const storedSession = localStorage.getItem(storageKey);
    if (!storedSession) return;
    const [storedId, expires] = storedSession.split('|');
    if (storedId !== currentSessionId || Date.now() >= parseInt(expires)) {
      alert("您的会话已失效,请重新打开页面");
      window.close();
    }
  }, 1000);
  
  // 页面关闭/刷新时清理会话记录
  window.addEventListener('beforeunload', function() {
    localStorage.removeItem(storageKey);
  });
});

方案局限

  • 只能限制同浏览器的多标签/窗口,不同浏览器的localStorage是隔离的,管不了跨浏览器访问;
  • 如果用户手动清除localStorage或者页面崩溃未触发beforeunload,可能出现下次访问被拦截的情况,所以加了5分钟过期的容错逻辑。
二、Node.js + 前端结合实现跨浏览器多窗口限制

要跨浏览器限制,必须依赖后端记录用户的活跃会话——通过Cookie+Session识别用户,每个用户(或设备)只能有一个活跃会话。配合WebSocket实时同步会话状态,一旦新会话建立,旧会话会被强制失效。

步骤1:后端代码(Node.js + Express + WebSocket)

const express = require('express');
const session = require('express-session');
const WebSocket = require('ws');
const http = require('http');

const app = express();
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });

// 配置Session(生产环境建议用Redis替代内存存储)
app.use(session({
  secret: 'your-custom-secret-key',
  resave: false,
  saveUninitialized: true,
  cookie: { 
    secure: false, // 生产环境请开启HTTPS并设为true
    maxAge: 30 * 60 * 1000 // Cookie有效期30分钟
  }
}));

// 存储活跃的WebSocket连接(key为sessionID)
const activeSessions = new Map();

// 托管静态页面(把index.html放在public文件夹下)
app.use(express.static('public'));

// 拦截非WebSocket请求,检查是否已有活跃会话
app.use((req, res, next) => {
  if (req.headers.upgrade === 'websocket') return next();
  if (activeSessions.has(req.session.id)) {
    res.send(`<script>alert('已有其他窗口/浏览器正在访问本站'); window.close();</script>`);
    return;
  }
  next();
});

// 处理WebSocket连接
wss.on('connection', (ws, req) => {
  const sessionId = req.session.id;
  
  // 已有活跃会话则拒绝新连接
  if (activeSessions.has(sessionId)) {
    ws.send(JSON.stringify({ type: 'invalid' }));
    ws.close();
    return;
  }
  
  activeSessions.set(sessionId, ws);
  
  // 监听前端心跳,保持连接
  ws.on('message', (data) => {
    const msg = JSON.parse(data);
    if (msg.type === 'heartbeat') {
      ws.send(JSON.stringify({ type: 'ok' }));
    }
  });
  
  // 连接关闭时移除活跃会话记录
  ws.on('close', () => {
    activeSessions.delete(sessionId);
  });
});

server.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});

步骤2:前端index.html代码

<!DOCTYPE html>
<html>
<head>
  <title>Single Session Site</title>
</head>
<body>
  <h1>欢迎访问本站</h1>

  <script>
    // 建立WebSocket连接
    const ws = new WebSocket(`ws://${window.location.host}`);

    ws.onopen = () => {
      // 每3秒发送心跳,保持会话活跃
      setInterval(() => {
        ws.send(JSON.stringify({ type: 'heartbeat' }));
      }, 3000);
    };

    ws.onmessage = (event) => {
      const msg = JSON.parse(event.data);
      if (msg.type === 'invalid') {
        alert('已有其他窗口/浏览器正在访问本站,当前页面将关闭');
        window.close();
      }
    };

    ws.onclose = () => {
      alert('您的会话已失效,请重新打开页面');
      window.close();
    };

    // 页面关闭时主动断开WebSocket连接
    window.addEventListener('beforeunload', () => {
      ws.close();
    });
  </script>
</body>
</html>

方案说明

  • 跨浏览器限制依赖Cookie识别用户,只要用户在同一设备的不同浏览器中使用同一个Cookie(或未清除Cookie),就只能打开一个会话;
  • 生产环境要把Session存储换成Redis等持久化方案,避免服务器重启或多实例部署时会话丢失;
  • 如果用户禁用Cookie,这个方案会失效,可以结合IP+User-Agent做补充标识,但可能存在同局域网用户误判的问题。

内容的提问来源于stack exchange,提问作者Кирилл Морозов

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 06:41:47