阻止人们在多个选项卡上加载我的网站

IT技术 javascript cookies browser tabs
2021-03-07 13:42:21

我希望用户仅从浏览器中的一个选项卡浏览我的网站如何才能做到这一点?我会使用 javascript 和 cookie 吗?

例如,我有一个网站:www.example.com - 我希望我的客户只能从一个浏览器中的一个选项卡访问该网站。如果他们打开另一个选项卡并加载站点(或站点的​​子页面) - 我想要一个警报“无法打开多个实例”,然后将它们重定向到错误页面。

有一点需要注意 - 如果用户将地址从www.example.com/action/door/mine.aspx更改www.example.com - 那应该可以正常工作,因为用户在同一个(原始)选项卡中。

任何帮助将不胜感激。提前致谢。

6个回答

我为此创建了一个简单的解决方案。母版页面布局创建一个选项卡 GUID 并将其存储在选项卡的 sessionStorage 区域中。在存储区域上使用事件侦听器我将选项卡 GUID 写入站点 localStorage 区域。然后侦听器将选项卡 GUID 与写入站点存储的选项卡进行比较,如果它们不同,则它知道打开的选项卡不止一个。

因此,如果我有三个选项卡 A、B、C,则单击选项卡 C、选项卡 A 和 B 中的某些内容会检测到另一个选项卡已打开并警告用户这一点。我还没有修复它,所以最后一个选项卡使用了获取通知,正在进行中。

这是我在母版页中的 JS,另外在登录页面中,我可以localStorage.Clear清除上一个会话中的最后一个选项卡。

    // multi tab detection
function register_tab_GUID() {
    // detect local storage available
    if (typeof (Storage) !== "undefined") {
        // get (set if not) tab GUID and store in tab session
        if (sessionStorage["tabGUID"] == null) sessionStorage["tabGUID"] = tab_GUID();
        var guid = sessionStorage["tabGUID"];

        // add eventlistener to local storage
        window.addEventListener("storage", storage_Handler, false);

        // set tab GUID in local storage
        localStorage["tabGUID"] = guid;
    }
}

function storage_Handler(e) {
    // if tabGUID does not match then more than one tab and GUID
    if (e.key == 'tabGUID') {
        if (e.oldValue != e.newValue) tab_Warning();
    }
}

function tab_GUID() {
    function s4() {
        return Math.floor((1 + Math.random()) * 0x10000)
          .toString(16)
          .substring(1);
    }
    return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
      s4() + '-' + s4() + s4() + s4();
}

function tab_Warning() {
    alert("Another tab is open!");
}

注意:是IE9+

希望这可以帮助。

@DanielASathishKumar 所有 JS 都应该在页面中,您应该在页面加载时运行 register_tab_GUID()。
2021-04-18 13:42:21
它不适合我。我需要在加载时调用任何 js 函数吗?
2021-04-19 13:42:21
这是最好的解决方案。谢谢
2021-04-23 13:42:21
如果您复制一个选项卡,会话存储也将在新选项卡中。关联
2021-05-15 13:42:21

更新 - 2020

客户端实现:

我们可以利用广播频道 API,它允许跨浏览上下文(窗口、选项卡、框架或 iframe)进行通信,前提是这两个上下文来自同一来源。

检测从第一个选项卡加载网站的第二个选项卡的简单实现:

    //in entry point of your app (index.js)    

    const channel = new BroadcastChannel('tab');

    channel.postMessage('another-tab');
    // note that listener is added after posting the message

    channel.addEventListener('message', (msg) => {
      if (msg === 'another-tab') {
        // message received from 2nd tab
        alert('Cannot open multiple instances');
      }
    });

如果第一个选项卡处于脱机状态并且正在加载第二个选项卡,则不会使用localStoragecookies它甚至可以工作。

注意:Safari 和 IE11 尚不支持此功能:(

记下它的浏览器兼容性

但是,有一个polyfill可以完成这项工作。

@Eric 你有没有机会用一个对象调用 channel.postMessage { data : 'another-tab' }
2021-04-27 13:42:21
我也不得不检查 msg.data
2021-04-29 13:42:21
我不得不使用(msg.data === 'another-tab')它来让它工作。
2021-05-01 13:42:21
不,我使用的与您的示例相同channel.postMessage('another-tab')这是对象在控制台中的样子: MessageEvent {isTrusted: true, data: "another-tab", origin: "http://localhost:8080", lastEventId: "", source: null, …}
2021-05-11 13:42:21

编辑2:

这就是这个答案中提到的确切内容,您需要 2 个 ID:

  1. 一个随机的
  2. 一个一致的(这实际上是我们的SSID,因为你限制了单个浏览器的标签,最好得到生成的表单浏览器的唯一参数)

您可以从浏览器的用户代理生成一致的,也可以从服务器端获取。将它们都存储在服务器端。
将随机一个存储在window.name特定选项卡的属性中。
每 1~2 秒向您的服务器发送一次心跳,其中包含一致 ID 和随机 ID。如果服务器未能接收到心跳,它会清理数据库并取消注册死客户端。
在每个浏览器的请求中,检查window.name值。如果丢失,请与服务器端检查上一个选项卡是否已关闭(从数据库中清除)。

如果是,则为客户端生成一个新对,如果否,则拒绝它们。


我想到了两个建议:
  1. 服务器端(更好):提供所有客户端、用户名和密码。要求他们在第一次访问您的网站时输入他们的凭据。然后在每个其他请求中,检查具有所述凭据的用户是否已经登录。
  客户 *
         |
         |
      服务器 ---> 检查是否
                  已登录
                     或不?
                  _____________
                   | |
                  是 否
                   | |
                 允许拒绝
                  他们他们
  1. 客户端:如果您确实需要对此进行严格检查,请使用evercookiealready-logged-in在客户端计算机上存储cookie。

旁注:要知道客户端的每一次尝试都根本不安全!客户端应该帮助服务器端,它不应该被用作唯一的安全来源。甚至 evercookies 也可以删除,所以请尝试一下我的第一个建议。


**编辑:**

Evercookie 在存储最安全的僵尸 cookie 方面确实做得很好,但由于库本身对于浏览器来说有点重(每次存储 cookie 需要超过 100 毫秒),因此不建议在现实世界的 Web 应用程序中使用。

如果您使用服务器端解决方案,请改用这些:

@杰森不!如果您使用服务器端解决方案,请不要使用 evercookie,而是使用它:Way around ASP.NET session is shared across multiple tab windowsevercookie 适用于非常安全的解决方案,例如银行业务等(查看我编辑过的答案)。
2021-04-27 13:42:21
if server fails to receive the heartbeat它被定义为多长时间fail to receive
2021-05-04 13:42:21
@Jason 实际上,您应该尝试创建一个 GUID,而不是一个随机的 GUID,一个真正的 GUID,它可以重新生成算法。我的意思是你应该尝试编写一个从浏览器生成 ID 的算法。看看我对答案的第二次编辑(我现在要写)
2021-05-10 13:42:21
谢谢回复。一些问题 。1:服务器端,我使用会话来存储用户的凭据,我有一个 login.aspx 页面来登录用户,因为一个浏览器中的选项卡共享会话,所以如果我在一个选项卡(tabA)中登录系统,并复制整个url(如www.xx.com/some/test.aspx),并从另一个选项卡(tabB)打开它,浏览器会认为用户已登录,而在tabB中,它不会重定向到login.aspx页面。现在我检查evercookie是否可以解决这个问题,希望可以,谢谢
2021-05-11 13:42:21
谢谢,通过“绕过...”的解决方案,是否将每个标签页都作为一个会话?不是直接解决我的问题而是以另一种方式解决我的问题?
2021-05-14 13:42:21

我知道这篇文章已经很老了,但如果它对任何人有帮助,我最近研究了使用 localStorage 和 sessionStorage 基本上做同样的事情。

Anthony 的回答类似,它设置了一个时间间隔以确保原始选项卡保持条目新鲜,以便如果浏览器崩溃或以某种方式关闭而不调用卸载事件(包含在注释中,但不是用于测试目的的代码的一部分),然后在应用程序在新的浏览器窗口中正常运行之前,会有短暂的延迟。

显然,您可以更改“制表符好”、“制表符不好”的条件来执行您想要的任何逻辑。

哦,还有,createGUID 方法只是一个使会话标识符唯一的实用程序......它来自这个对上一个问题的回答(想确保我没有因此而受到赞扬)。

https://jsfiddle.net/yex8k2ts/30/

let localStorageTimeout = 15 * 1000; // 15,000 milliseconds = 15 seconds.
let localStorageResetInterval = 10 * 1000; // 10,000 milliseconds = 10 seconds.
let localStorageTabKey = 'test-application-browser-tab';
let sessionStorageGuidKey = 'browser-tab-guid';

function createGUID() {
  let guid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
    /*eslint-disable*/
    let r = Math.random() * 16 | 0,
      v = c == 'x' ? r : (r & 0x3 | 0x8);
    /*eslint-enable*/
    return v.toString(16);
  });

  return guid;
}

/**
 * Compare our tab identifier associated with this session (particular tab)
 * with that of one that is in localStorage (the active one for this browser).
 * This browser tab is good if any of the following are true:
 * 1.  There is no localStorage Guid yet (first browser tab).
 * 2.  The localStorage Guid matches the session Guid.  Same tab, refreshed.
 * 3.  The localStorage timeout period has ended.
 *
 * If our current session is the correct active one, an interval will continue
 * to re-insert the localStorage value with an updated timestamp.
 *
 * Another thing, that should be done (so you can open a tab within 15 seconds of closing it) would be to do the following (or hook onto an existing onunload method):
 *      window.onunload = () => { 
                localStorage.removeItem(localStorageTabKey);
      };
 */
function testTab() {
  let sessionGuid = sessionStorage.getItem(sessionStorageGuidKey) || createGUID();
  let tabObj = JSON.parse(localStorage.getItem(localStorageTabKey)) || null;

    sessionStorage.setItem(sessionStorageGuidKey, sessionGuid);

  // If no or stale tab object, our session is the winner.  If the guid matches, ours is still the winner
  if (tabObj === null || (tabObj.timestamp < new Date().getTime() - localStorageTimeout) || tabObj.guid === sessionGuid) {
    function setTabObj() {
      let newTabObj = {
        guid: sessionGuid,
        timestamp: new Date().getTime()
      };
      localStorage.setItem(localStorageTabKey, JSON.stringify(newTabObj));
    }
    setTabObj();
    setInterval(setTabObj, localStorageResetInterval);
    return true;
  } else {
    // An active tab is already open that does not match our session guid.
    return false;
  }
}

if (testTab()) {
  document.getElementById('result').innerHTML = 'tab is good';
} else {
  document.getElementById('result').innerHTML = 'tab is bad';
}
当我打开 2 个标签时,第二个给我“标签不好”和第一个“标签好”。当我第一次关闭并且我只有一个选项卡时,倒数第二个选项卡仍然显示“选项卡坏了”
2021-04-16 13:42:21