阻止人们将我的网站加载到多个标签上

时间:2012-06-13 04:22:31

标签: javascript cookies browser tabs

我希望用户从浏览器中的一个标签浏览我的网站。如何才能做到这一点?我会使用javascript和cookies吗?

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

需要注意的事项 - 如果用户将地址从 www.example.com/action/door/mine.aspx 更改为 www .example.com - 应该可以正常工作,因为用户位于相同的(原始)标签中。

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

8 个答案:

答案 0 :(得分:20)

我为此创建了一个简单的解决方案。母版页布局创建选项卡GUID并将其存储在选项卡的sessionStorage区域中。在存储区域使用事件监听器我将标签GUID写入站点localStorage区域。然后,侦听器将选项卡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 +

希望这有帮助。

答案 1 :(得分:9)

更新-2020

客户端实施:

我们可以利用Broadcast Channel 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尚不支持此功能:(

在其browser compatibility上记笔记。

但是,有polyfill个可用的文件。

答案 2 :(得分:7)

<强> EDIT2:

这是this answer提到的确切内容,你需要2个ID:

  1. 一个随机的
  2. 一个一致的(实际上这将是我们的SSID,因为你限制了单个浏览器的标签,最好从浏览器的独特参数中生成)
  3. 您可以从浏览器的用户代理生成一致的或从服务器端获取它。将它们都存储在服务器端 将随机存储在window.name属性中,该属性是特定于制表符的 每1~2秒向您的服务器发送一次包含一致ID和随机ID的心跳。如果服务器无法接收心跳,它会清理数据库并取消注册死客户端。
    在每个浏览器的请求中,检查window.name的值。如果丢失,请检查服务器端是否关闭了上一个选项卡(从数据库中清除)。

    如果是,如果没有,为客户生成一对新的,拒绝他。

    <小时/> 最重要的两条建议:

    1. 服务器端(更好): 为您的所有客户提供用户名和密码。在首次访问您的网站时请求他们输入他们的凭据。然后在每个其他请求上,检查具有所述凭据的用户是否已登录。
    2.   Client *
               |
               |
            Server ---> Check whether
                        Already logged
                           or not?
                        ______________
                         |          |
                        yes         no
                         |          |
                       permit     reject
                        him        him
      
      1. 客户端: 如果您确实需要对此进行强有力的检查,请使用evercookie在客户端的计算机上存储already-logged-in Cookie。
      2. 旁注: 请注意,客户端的每次尝试都不安全!客户端应该帮助服务器端,它不应该被用作唯一的安全源。 even evercookies can be deleted所以,请给我第一个建议。

        <小时/> 修改

        Evercookie在存储大多数安全的僵尸cookie时确实做得很好但是因为浏览器本身对浏览器来说有点沉重(每次存储cookie需要超过100毫秒),所以不建议在现实世界中使用它网络应用。

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

答案 3 :(得分:5)

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

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

显然,你会改变“tab is good”,“tab is bad”条件来做你想做的任何逻辑。

哦,而且,createGUID方法只是一个实用程序,可以使会话标识符唯一...它是从this answer到之前的问题(想要确保我没有被认可)

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';
}

答案 4 :(得分:1)

为什么你想这样做?

可以尝试做一些丑陋的黑客攻击,但结果会是:没有方式可以完全抑制此行为。

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

用户可以打开新浏览器,使用其他计算机等一次访问多个页面。

但更重要的是:

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

答案 5 :(得分:1)

答案 6 :(得分:0)

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

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

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

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

这将强制用户始终拥有一个会话轨道,并始终遵循网站上的逻辑。任何前进,后退,使用历史记录或日志输入或打开多个窗口或选项卡的尝试都将失败,并在所有窗口,选项卡和设备中注销用户。

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

答案 7 :(得分: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);
}