簡體   English   中英

如何防止用戶擁有同一個 Web 應用程序的多個實例

[英]How to prevent a user from having multiple instances of the Same Web application

我想知道是否有可能確定用戶是否已經有一個 web 瀏覽器打開我正在處理的 web 應用程序。 他們似乎可以打開同一個 web 應用程序的多個實例,然后單擊按鈕以讀取他們之前使用過的信息,從而進入他們當前正在處理的輸入屏幕。

但是發生的事情是,它似乎搞砸了 Session 個變量,然后用戶將用他們的新作品更新他們以前的作品。 或者他們會一起刪除他們以前的作品或者誰知道......

編輯我之前已經看到過使用網上銀行 web 應用程序完成此操作。 如果您已經登錄,新的 window 會親切地告訴您您已經打開了該應用程序。 在我的例子中,用戶不需要登錄。

有沒有一種簡單的方法可以確定他們是否已經將瀏覽器 window 打開到 web 應用程序,如果是,只需關閉瀏覽器或顯示不同的頁面,讓他們知道他們一次只能打開 1 個?

謝謝

首先,沒有,其次,你不應該嘗試。

彈出窗口策略不起作用(並且會使用戶煩惱)。 我有我的瀏覽器設置為“打開窗口作為標簽”,而選擇是否拆分一關到另一個窗口。 或者在某些情況下,是否要運行不同的瀏覽器 - 而不僅僅是同一個瀏覽器的另一個實例 - 以在同一站點上顯示另一個頁面。

相反,迷你會話ID將失敗,因為服務器無法跟蹤請求是否來自與現有會話相同的用戶 有些人可能正在使用同一台機器,即使使用相同的用戶名; 或者一個人可能在一台或多台機器上有幾個單獨的登錄會話。

只需將您的協議與“會話”變量進行比較,並確保最后提交的更改是持久的更改。

您所要做的就是為Hidden Input控件的值和Page Load事件中的會話變量以及回發分配值,並根據會話變量中的值檢查局部變量的值。 如果值不匹配,您可以將用戶重定向到登錄頁面或告訴他們該頁面的會話不再有效的頁面等。

例:

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
    Try
        If Not IsPostBack Then
            'SET LOCAL VARIABLE AND SESSION VARIABLE TO A UNIQUE VALUE
            'IF THE USER HAS CHANGED TABS THIS WILL CHANGE THE VALUE OF THE SESSION VARIABLE
            Me.HiddenInput.value = New Guid().ToString
            Me.Session.Add("PageGuid", Me.HiddenInput.value)

        Else 
            'ON POSTBACK, CHECK TO SEE IF THE USER HAS OPENED A NEW TAB
            'BY COMPARING THE VALUE OF THE LOCAL VALUE TO THE VALUE OF THE SESSION VARIABLE

            If me.HiddenInput.value <> CType(Session("PageGuid"), String) Then

                'THE VALUES DO NOT MATCH, MEANING THE USER OPENED A NEW TAB.
                'REDIRECT THE USER SOMEWHERE HARMLESS

                Response.Redirect("~/Home.aspx")

            Else

                'THE VALUES MATCH, MEANING THE USER HAS NOT OPENED A NEW TAB
                'PERFORM NORMAL POSTBACK ACTIONS

                ...

            End If
        End If
    Catch ex As Exception
        Me.Session.Add("ErrorMessage", BusinessLogic.GetErrorMessage(ex))
        Me.Response.Redirect("~/ErrorPage.aspx", False)
    End Try
End Sub

您可以為輸入表單的每個實例分配一個“迷你會話”ID,然后使用AJAX使用該ID ping服務器。 如果用戶在有活動ID時嘗試請求相同的表單,則應顯示錯誤消息。 如果服務器在一段時間內沒有聽到ping,則使迷你會話到期。 (這基本上是一個非常簡單的鎖定策略)

我使用打開具有特定ID的新窗口的技巧,並始終確保將打開的任何頁面始終使用該窗口。

不利的一面是,他們必須為你的網站關閉彈出窗口攔截器。 它適用於公司網站。

if (useOwnWindow && window.name != 'YourAPP'){
    var w = window.open(document.location, 'YourAPP', 'toolbar=no,status=yes,resizable=yes,scrollbars=yes');
    if (w==null){
        alert("Please turn off your pop-up blocker");
    }else{
        window.open('','_parent','');
        self.opener="";
        self.close();
    }
 }

請注意useOwnWindow標志,如果開發人員使用它, 我們可以多次打開它

有沒有一種簡單的方法可以確定他們是否已經在Web應用程序中打開了瀏覽器窗口,如果是這樣,只需關閉瀏覽器或顯示不同的頁面,讓他們知道他們一次只能打開1個?

簡而言之,沒有。
在沒有編寫某種activeX控件的情況下,沒有可編寫的代碼可以阻止用戶打開(單獨的)IE / FF等實例並讓一個實例檢測到另一個。

當用戶打開另一個選項卡或其他窗口或甚至另一個瀏覽器時,您可以停止頁面功能

  $(window).blur(function(){

// code to stop functioning or close the page  

});

我用這種方式部分解決了這個問題。 我的應用程序要求用戶進行身份驗證...因此他們輸入用戶名和密碼。 當他們點擊提交按鈕時,我將兩個數據存儲到會話變量中,即我將用戶名存儲到Session(“LoginName”)中。

所以這就是訣竅:在登錄頁面的頁面加載事件中,只測試Session(“LoginName”)是否在用戶輸入之前有值。 如果是這樣,這是一個多會話,你可以停止用戶或其他什么。

像(我使用VB):

    If Not Page.IsPostBack Then
       Try
          If Session("LoginName") <> "" Then
             Response.Redirect("www.mysite.com")
             Exit Sub
          End If
       Catch ex As Exception
          ' Do something
       End Try

PRO:非常簡單

CON:用戶可以復制內頁的地址並將其粘貼到同一瀏覽器的另一個選項卡中...這就是為什么我說“部分”

我建議您為頁面散列ViewState並將其存儲在會話變量中,然后將其作為Response返回。

然后,對於請求,首先檢查返回的ViewState的哈希值與會話變量中的哈希值,如果它們不匹配,則不處理頁面上的任何更改並向用戶顯示通知或將其重定向到錯誤頁。

您要覆蓋的Page類的兩種方法是;

protected override object LoadPageStateFromPersistenceMedium()

protected override void SavePageStateToPersistenceMedium(object viewState)

我們已經在slicethepie.com上實現了這個問題的解決方案。 用戶(或“偵察員”)只允許一次打開一個瀏覽器窗口,以確保他們收聽他們正在收看的音樂以供審閱。

當偵察員要求第一首曲目在他們的偵察會話中進行審核時,我們會在他們的帳戶上設置一個新的Guid並返回此“密鑰”以及他們要審核的曲目的詳細信息。 碰巧這個鍵的收件人是一部flash電影,但並非必須如此。 密鑰與偵察員的評論一起重新提交,我們檢查它是否與保存的密鑰匹配。 如果沒有,他們就會在新窗口中開始一個新的球探會議。

我並不是說這種方法是萬無一失的,但它可以很容易地適應你的需求。

如果有人復制網站網址並將其粘貼到新窗口或標簽中,該窗口/標簽的瀏覽器歷史記錄將為空...所以您可以使用javascript並查看歷史記錄。

if (history.length == 1) {  //0 for IE, 1 for Firefox
    // This is a new window or a new tab.
}

現在,您可以提示用戶關閉選項卡,或禁用該頁面(例如, disabled所有元素)。

我遇到了同樣的問題並使用網絡套接字解決了它(我使用pusher與php)。 當用戶登陸頁面時,我設置了一個變量:

var firstTimeHere = true;

然后我在javascript中創建了一個websocket函數,其中userid作為函數名稱的一部分,如:

var pingUser123 = function(){...}; 

然后我使用ajax來查詢觸發websocket的php端的服務器:

$this->trigger("pingUser123");

websocket在任意數量的瀏覽器和機器上的任意數量的窗口中觸發該功能的所有javascript實例。 從那里我可以看到這是第一次這個瀏覽器被ping。 如果是這樣的話,它就是唯一的例子,如果沒有,那就是第二個。

var pingUser123 = function(){
    if(firstTimeHere){
       firstTimeHere = false;
    }else{
        app.stop();
        alert("Only one window at a time please.");
    }
};
 <script type="text/javascript">
        window.onload = function(){
        if (document.cookie.indexOf("_instance=true") === -1) {
        document.cookie = "_instance=true";
        // Set the onunload function
        window.onunload = function(){
            document.cookie ="_instance=true;expires=Thu, 01-Jan-1970 00:00:01 GMT";
        };
        // Load the application
        }
        else {
             alert(" Security Alerts.You Are Opening Multiple Window. This window will now close.");
             var win = window.open("about:blank", "_self"); win.close();
        // Notify the user
        }
        };
    </script>

您可以檢查用戶是否有多個活動會話。 這可能有效,但可能會有問題,因為沒有好的方法來確定會話是否真正活躍。

我有一個js代碼,這段代碼只讓你有一個頁面實例,但只在運行這個js代碼的頁面中。 示例:如果將js代碼放在page1.html和page2.html中,則代碼將允許page1或page2的一個實例。 如果只將js放在page1中,page1將只有一個實例,而page2將有多個實例。

只需復制並粘貼您的網站,然后更改邏輯的最后幾行。

碼:

var statusWindow = document.getElementById('status');

(function (win) {
    //Private variables
    var _LOCALSTORAGE_KEY = 'WINDOW_VALIDATION';
    var RECHECK_WINDOW_DELAY_MS = 100;
    var _initialized = false;
    var _isMainWindow = false;
    var _unloaded = false;
    var _windowArray;
    var _windowId;
    var _isNewWindowPromotedToMain = false;
    var _onWindowUpdated;


    function WindowStateManager(isNewWindowPromotedToMain, onWindowUpdated) {
        //this.resetWindows();
        _onWindowUpdated = onWindowUpdated;
        _isNewWindowPromotedToMain = isNewWindowPromotedToMain;
        _windowId = Date.now().toString();

        bindUnload();

        determineWindowState.call(this);

        _initialized = true;

        _onWindowUpdated.call(this);
    }

    //Determine the state of the window 
    //If its a main or child window
    function determineWindowState() {
        var self = this;
        var _previousState = _isMainWindow;

        _windowArray = localStorage.getItem(_LOCALSTORAGE_KEY);

        if (_windowArray === null || _windowArray === "NaN") {
            _windowArray = [];
        }
        else {
            _windowArray = JSON.parse(_windowArray);
        }

        if (_initialized) {
            //Determine if this window should be promoted
            if (_windowArray.length <= 1 ||
               (_isNewWindowPromotedToMain ? _windowArray[_windowArray.length - 1] : _windowArray[0]) === _windowId) {
                _isMainWindow = true;
            }
            else {
                _isMainWindow = false;
            }
        }
        else {
            if (_windowArray.length === 0) {
                _isMainWindow = true;
                _windowArray[0] = _windowId;
                localStorage.setItem(_LOCALSTORAGE_KEY, JSON.stringify(_windowArray));
            }
            else {
                _isMainWindow = false;
                _windowArray.push(_windowId);
                localStorage.setItem(_LOCALSTORAGE_KEY, JSON.stringify(_windowArray));
            }
        }

        //If the window state has been updated invoke callback
        if (_previousState !== _isMainWindow) {
            _onWindowUpdated.call(this);
        }

        //Perform a recheck of the window on a delay
        setTimeout(function () {
            determineWindowState.call(self);
        }, RECHECK_WINDOW_DELAY_MS);
    }

    //Remove the window from the global count
    function removeWindow() {
        var __windowArray = JSON.parse(localStorage.getItem(_LOCALSTORAGE_KEY));
        for (var i = 0, length = __windowArray.length; i < length; i++) {
            if (__windowArray[i] === _windowId) {
                __windowArray.splice(i, 1);
                break;
            }
        }
        //Update the local storage with the new array
        localStorage.setItem(_LOCALSTORAGE_KEY, JSON.stringify(__windowArray));
    }

    //Bind unloading events  
    function bindUnload() {
        win.addEventListener('beforeunload', function () {
            if (!_unloaded) {
                removeWindow();
            }
        });
        win.addEventListener('unload', function () {
            if (!_unloaded) {
                removeWindow();
            }
        });
    }

    WindowStateManager.prototype.isMainWindow = function () {
        return _isMainWindow;
    };

    WindowStateManager.prototype.resetWindows = function () {
        localStorage.removeItem(_LOCALSTORAGE_KEY);
    };

    win.WindowStateManager = WindowStateManager;
})(window);

var WindowStateManager = new WindowStateManager(true, windowUpdated);

function windowUpdated() {
    //"this" is a reference to the WindowStateManager
    //statusWindow.className = (this.isMainWindow() ? 'main' : 'child');
    if (this.isMainWindow() === false) {
        //alert("This is not the main window")
        location.href = "yourpage.html";


    }
    else {
        //alert("This is the main window")
    }

}
//Resets the count in case something goes wrong in code
//WindowStateManager.resetWindows()

正如其他人所提到的,你無法阻止用戶在不訴諸ActiveX或其他惡意的情況下啟動新會話。 基本問題是您無法知道用戶是關閉舊瀏覽器窗口還是將其保持打開狀態。

但是,您可以做的是,一旦用戶登錄到新的會話,就會使之前的會話無效(類似於Instant Messaging客戶端的行為方式)。

在每次登錄時,為數據庫中的用戶分配新的GUID。 還將此GUID存儲在會話緩存中(無需將其傳送到頁面,無論如何都不適用於GET請求)。 在每個頁面請求上,將分配給數據庫中用戶的GUID與會話高速緩存中的GUID進行比較。 如果它們不匹配,則返回“您已從其他地方登錄”響應。

更新我的觸發器有點太快了。 這不會阻止用戶在同一瀏覽器進程中打開多個選項卡/窗口的情況。 因此,您必須將此解決方案與Dave Anderson建議結合使用,以存儲ViewState哈希(或簡稱GUID),以便只允許會話中最后一個提供的頁面回發。

安全更新此外,您只能依賴此框架以方便用戶,因為它不安全。 任何一半體面的黑客都能夠繞過這些措施。

與其阻止用戶打開多個 windows,不如嘗試修復 web 應用程序以將 window state 保留在客戶端而不是服務器中。 開發單頁應用程序更容易將 state 保存在瀏覽器中而不是服務器中,但您也可以使用多頁應用程序將 state 保存在瀏覽器中(而不是服務器中)。

你可以通過window.name來做到這一點。 在java腳本中,window.name在每個新選項卡上都有空值。 在登錄頁面上設置window.name值並保存在會話中。

window.name = Math.random() + "_YourApplication"

現在在主頁/布局頁面上檢查此window.name。 如果用戶包含多個選項卡,請將其注銷。

 if (!window.name || window.name != '@Session["WindowName"]') {
    //Log Off code
 }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM