35

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

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

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

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

4

10 回答 10

29

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

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

这是我在母版页中的 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+

希望这可以帮助。

于 2015-09-24T11:40:44.287 回答
16

更新 - 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.data === 'another-tab') {
        // message received from 2nd tab
        alert('Cannot open multiple instances');
      }
    });

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

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

记下它的浏览器兼容性

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

于 2020-06-06T12:18:14.610 回答
12

编辑2:

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

  1. 随机一张
  2. 一个一致的(实际上这将是我们的 SSID,因为您限制了单个浏览器的选项卡,所以最好获取生成的表单浏览器的唯一参数)

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

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


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

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


**编辑:**

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

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

于 2012-06-13T05:00:05.750 回答
6

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

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

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

哦,而且,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';
}
于 2017-08-16T15:35:32.470 回答
1

同样的问题(和解决方案):https ://sites.google.com/site/sarittechworld/track-client-windows

类似: http: //www.codeproject.com/Articles/35859/Detect-and-prevent-multiple-windows-or-tab-usage-i

于 2012-06-13T04:45:07.713 回答
1

解决此问题的最佳方法是拥有一次性会话 ID。

例如,每个页面都包含一个会话 ID,它对一次访问有效,是唯一的,并且是随机的。当点击任何一个链接时,它会使用 & 使会话 ID 无效,并且新页面将有一个新的会话 ID。

这将迫使用户始终在最新的窗口或选项卡中浏览,并且还可以防止会话通过网络窃取。任何重用旧会话 ID 的尝试都应立即终止该用户的活动会话 ID。

在会话管理系统中存储可以从页面 X 访问的页面也很重要。因此,如果页面 X(具有会话 ID abc)包含指向页面 1、2 和 3 的链接,则任何尝试访问具有会话 ID abc 的页面 4 , 将失败并终止会话。

这将迫使用户始终拥有一个会话跟踪,并始终遵循站点上的逻辑。任何前进、后退、使用历史记录或完整记录,或打开多个窗口或选项卡的尝试都将失败,并在所有窗口、选项卡和设备中注销用户。

所有这些都可以完全在服务器端实现,无需任何客户端逻辑。

于 2015-11-25T22:42:31.977 回答
0

你为什么要这样做?

可以尝试做一些丑陋的黑客攻击,但结果是:你无法完全抑制这种行为。

这无法通过 JavaScript 解决,因为用户总是有可能在他的浏览器中禁用了 JavaScript,或者只允许某个子集。

用户可以打开一个新的浏览器、使用不同的计算机等来一次访问多个页面。

但更重要的是:

此外,您的站点将是唯一具有此行为的站点,因此这会使使用您站点的每个人感到困惑,因为它不像网站那样工作。每个试图打开第二个标签的人都会认为:“这很奇怪。这个网站很糟糕,因为它与网站应该不同。我不会再来了!” ;-)

于 2012-06-13T04:32:03.873 回答
0

扩展rehman_00001 的答案以处理您希望在新选项卡上显示警报的情况。

const channel = new BroadcastChannel('tab');
let isOriginal = true;

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

channel.addEventListener('message', (msg) => {
    if (msg.data === 'another-tab' && isOriginal) {
        // message received from 2nd tab
        // reply to all new tabs that the website is already open
        channel.postMessage('already-open');
    }
    if (msg.data === 'already-open') {
        isOriginal = false;
        // message received from original tab
        // replace this with whatever logic you need
        alert('Cannot open multiple instances');
    }
});

于 2022-02-10T17:24:26.563 回答
0
window.addEventListener('load', function () {
    if (localStorage.getItem('web_browser') == null) {
        // new tab
        localStorage.setItem('web_browser', 'true');
        window.addEventListener('unload', function() {
            localStorage.removeItem('web_browser');
        })
    } else {
        // duplicate tab
        return;
    }
})

将此脚本放在 html 页面的开头,您不希望用户复制当前页面或选项卡。

于 2022-01-23T11:22:31.597 回答
0

我写这个是为了阻止呼叫中心页面在多个选项卡中被访问。它运作良好,纯粹是客户端。如果检测到新选项卡,只需更新else if部件以执行您想要的操作。

// helper function to set cookies
function setCookie(cname, cvalue, seconds) {
    var d = new Date();
    d.setTime(d.getTime() + (seconds * 1000));
    var expires = "expires="+ d.toUTCString();
    document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
}

// helper function to get a cookie
function getCookie(cname) {
    var name = cname + "=";
    var decodedCookie = decodeURIComponent(document.cookie);
    var ca = decodedCookie.split(';');
    for(var i = 0; i < ca.length; i++) {
        var c = ca[i];
        while (c.charAt(0) == ' ') {
            c = c.substring(1);
        }
        if (c.indexOf(name) == 0) {
            return c.substring(name.length, c.length);
        }
    }
    return "";
}

// Do not allow multiple call center tabs
if (~window.location.hash.indexOf('#admin/callcenter')) {
    $(window).on('beforeunload onbeforeunload', function(){
        document.cookie = 'ic_window_id=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
    });

    function validateCallCenterTab() {
        var win_id_cookie_duration = 10; // in seconds

        if (!window.name) {
            window.name = Math.random().toString();
        }

        if (!getCookie('ic_window_id') || window.name === getCookie('ic_window_id')) {
            // This means they are using just one tab. Set/clobber the cookie to prolong the tab's validity.
            setCookie('ic_window_id', window.name, win_id_cookie_duration);
        } else if (getCookie('ic_window_id') !== window.name) {
            // this means another browser tab is open, alert them to close the tabs until there is only one remaining
            var message = 'You cannot have this website open in multiple tabs. ' +
                'Please close them until there is only one remaining. Thanks!';
            $('html').html(message);
            clearInterval(callCenterInterval);
            throw 'Multiple call center tabs error. Program terminating.';
        }
    }

    callCenterInterval = setInterval(validateCallCenterTab, 3000);
}
于 2017-04-29T03:58:42.997 回答