2012-06-13 51 views
12

我希望用戶從只瀏覽我的網站瀏覽器中的一個標籤。如何才能做到這一點?我會使用JavaScript和Cookie嗎?阻止有人將我的網站加載到多個標籤上

例如,我有一個網站:www.example.com - 我希望我的客戶端只能夠從一個單一的標籤訪問該網站在一個瀏覽器。如果他們打開另一個選項卡並加載站點(或站點的​​子頁面) - 我想要一個警報「無法打開多個實例」,然後將它們重定向到錯誤頁面。

一旦需要注意的一點 - 如果用戶改變從www.example.com/action/door/mine.aspxwww.example.com地址 - 這應該做工精細,監守該用戶位於相同(原始)選項卡中。

任何幫助將不勝感激。提前致謝。

+0

那麼你可以有一點點的JavaScript後心跳請求飄飛與當前用戶的會話ID,你檢查這AJAX心跳在未來的頁面加載前對於本次會話,如果是,則終止它。 – gideon

+2

您可能可以爲有限的一組案件做到這一點,但總的來說這是不可能的。用戶可以禁用部分或全部腳本,拒絕訪問cookie和歷史記錄,並且某些瀏覽器允許在選項卡中啓動全新的會話,因此除了IP嗅探(服務器可以輕鬆完成)之外,您運氣不佳。 – RobG

回答

7

EDIT2:

這是其在this answer提到確切的事情,你需要的ID 2 :

  1. 隨機一個
  2. 一以貫一(這將是我們的SSID實際上,因爲你限制了單一瀏覽器的標籤,這是更好地得到生成的表單瀏覽器的唯一參數)

您可以從瀏覽器的用戶代理生成一致的一個或得到它來自服務器端。將它們都存儲在服務器端。
將特定於標籤的window.name屬性中隨機的一個存儲。
每隔1〜2秒發送一次心跳給您的服務器,其中包含一致的ID和隨機的ID。如果服務器無法收到心跳,它將清理數據庫並取消註冊死客戶端。
根據每個瀏覽器的請求,檢查值爲window.name。如果缺失,請與服務器端檢查先前的選項卡是否關閉(從數據庫清除)。

如果是,則爲客戶端生成一個新對,如果不是,拒絕他。在我心中的頂部


兩點建議:

  1. 服務器端(更好):提供所有的客戶端,用戶名和密碼。請求他們第一次訪問您的網站時輸入他們的憑據。然後在其他任何請求上,檢查具有所述憑證的用戶是否已經登錄。
 
    Client * 
     | 
     | 
     Server ---> Check whether 
        Already logged 
        or not? 
        ______________ 
        |   | 
        yes   no 
        |   | 
       permit  reject 
        him  him 
  • 客戶端:如果你真的需要這樣一個強有力的檢查,使用evercookie存儲客戶端的計算機上的cookie的already-logged-in
  • 側面說明:知道,在客戶端的每一次嘗試是不安全了!客戶端應該幫助服務器端,它不應該被用作唯一的安全來源。 even evercookies can be deleted所以,給我第一個建議。


    編輯:

    Evercookie真的做得很好,在存儲最安全的殭屍餅乾永遠但由於庫本身是瀏覽器有點重(存儲cookie的時間超過100毫秒,每次)它不是真的建議在真實世界的web應用程序中使用。

    使用這些替代,如果你與服務器端的解決方案去:

    +0

    感謝您的回覆。一些問題 。 1:服務器端,我使用會話來存儲用戶的憑據,我有一個login.aspx頁面來登錄用戶,因爲一個瀏覽器中的標籤共享會話,所以如果我在一個標籤(tabA)登錄系統,並複製整個網址(如www.xx.com/some/test.aspx),並從另一個選項卡(tabB)打開它,瀏覽器會考慮用戶已登錄,並在tabB中,它不會重定向到login.aspx頁面。現在我檢查如果evercookie可以解決這個問題,希望它,謝謝 – Jason

    +0

    @Jason不!如果你使用服務器端解決方案,不要使用evercookie,而是使用它:[圍繞多個選項卡窗口共享ASP.NET會話](http://stackoverflow.com/a/2839183/1055628)。 evercookie是非常非常安全的解決方案,如銀行業務等(請看我的編輯答案)。 – Sepehr

    +0

    感謝,解決方法是「繞過...」,它是否將每個標籤頁作爲單個會話?不是直接用另一種方式解決我的問題嗎? – Jason

    1

    爲什麼你想這樣做嗎?

    可能會嘗試做一些醜陋的黑客攻擊,但結果是:有沒有的方式,你可以完全抑制這種行爲。

    這無法通過JavaScript解決,因爲用戶總是有可能在瀏覽器中禁用了JavaScript,或者只允許某個子集。

    用戶可以打開新的瀏覽器,使用其他計算機等一次訪問多個頁面。

    但更重要的是:

    此外,您的網站將是有這種行爲的唯一網站爲此這會混淆大家,它使用您的網站,因爲它並不像網站工作應該管用。每個試圖打開第二個標籤的人都會這樣想:「這很奇怪,這個網站很糟糕,因爲它與網站應該不一樣,我不會再來!」 ;-)

    +2

    感謝Hippo,因爲我們的應用程序有幾個功能,如日誌系統記錄客戶端的動作,並且使用多個選項卡,系統會感到困惑,不知道發生了什麼,如果js禁用,軌道將會很難,那麼,我們無能爲力,如果是這樣,他不能使用我們的系統。這很奇怪,但不幸的是我們必須這樣做。 – Jason

    +0

    通常,如果某個會話在服務器上進行管理,並且禁用多個選項卡比允許用戶同時啓動兩個嚮導更容易。 – otonglet

    15

    我創建這個簡單的解決方案。主頁面佈局創建一個選項卡GUID並將其存儲在選項卡的sessionStorage區域中。在存儲區域上使用事件偵聽器,我將選項卡GUID寫入站點localStorage區域。然後,監聽器將選項卡GUID與寫入站點存儲的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 +

    希望這有助於。

    +0

    謝謝。非常酷的解決方案 –

    +0

    它不適合我。我需要在加載時調用任何js函數嗎? –

    +0

    @DanielASathishKumar所有JS應該在頁面中,並且您應該在頁面加載時運行register_tab_GUID()。 – Darren

    0

    解決此問題的最佳方法是擁有一次性會話ID。

    例如,每個頁面包含一個會話ID,它對一次訪問有效,是唯一的,並且是隨機的。 點擊任意一個鏈接時,將使用&使會話ID無效,並且新頁面將具有新的會話ID。

    這將強制用戶總是瀏覽最新的窗口或標籤,並且還可以防止會話被竊取。 任何重複使用舊會話ID的嘗試都應立即終止該用戶的活動會話ID。

    在會話管理系統中,從頁面X訪問哪些頁面也很重要。因此,如果頁面X(帶有會話ID abc)包含指向頁面1,2和3的鏈接,則任何嘗試訪問頁面4使用會話ID abc將會失敗,並且會終止會話。

    這將強制用戶總是有一個單一的會話軌道,並始終遵循網站上的邏輯。任何嘗試前進,回退,使用歷史記錄或日誌條目或打開多個窗口或選項卡都將失敗,並在所有窗口,選項卡和設備中註銷用戶。

    所有這些都可以在服務器端完全實現,沒有任何客戶端邏輯。

    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); 
    } 
    
    1

    我知道這個職位是很老,但在情況下,它可以幫助任何人,我最近看着基本上是做使用的localStorage和sessionStorage的同樣的事情。

    類似Anthony's answer,它設置一個時間間隔以確保始發選項卡保持條目新鮮,以便在瀏覽器崩潰或以某種方式關閉時不調用卸載事件(包含在註釋中但不包含在用於測試目的的代碼的一部分中),那麼在應用程序在新的瀏覽器窗口中正常運行之前就會有一段短暫的延遲。

    很明顯,你會改變「選項卡是好的」,「選項卡是壞」的條件來做任何你想要的邏輯。

    哦,而且,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'; 
    }