簡體   English   中英

跨多個項目的 ASP.NET 用戶身份驗證

[英]ASP.NET User Authentication across multiple projects

我正在現有系統上構建一個 ASP.NET UI,它由每個項目的單獨 SQL 服務器數據庫組成。 “企業”數據庫列出了所有當前項目,允許匿名用戶選擇要工作的項目。項目名稱存儲在會話變量中。 當需要登錄時,用戶名/密碼/角色等將從項目名稱指示的數據庫中獲取。 我已經實現了自己的基本成員資格和角色提供程序來執行此操作,並更改了 web.config 以指定特定頁面所需的角色。 (我不使用標准的 ASP.NET 配置工具來管理用戶,我有使用我的用戶表的現有應用程序)。

這一切最初似乎都有效,但我發現在授權系統檢查當前用戶所屬的角色以確定頁面是否可訪問時,會話變量尚未加載。 因此,如果我們在 web.config 中有一個 < allow roles="xxx" > 那么授權系統會在加載會話數據之前觸發,因此在我知道應該使用哪個項目數據庫之前。

[特別是:調用 RoleProvider.GetRolesForUser 時 HttpContext.Current.Session 為 null]

任何解決過這個問題的人都應該確切地知道我在說什么。 因此,我的問題是:

A) 這種情況下的“最佳實踐”解決方案是什么?

B)我可以將項目名稱存儲在授權階段可用的其他地方(不在會話變量中)嗎?

[更新:是的 - 我們可以使用 cookie,假設我們不需要 cookieless 操作]

C)有沒有辦法在這個較早的時間手動獲取會話變量?

我嘗試了一個選項來緩存 cookie 中的角色,但在使用該選項進行了幾分鍾的測試后,我發現 GetRolesForUsers 仍在被調用。

謝謝

更新:
這是對根本問題的另一種描述,它表明“應用程序可以將此信息緩存在 Cache 或 Application 對象中。”:
http://connect.microsoft.com/VisualStudio/feedback/details/104452/session-is-null-in-call-to-getrolesforuser

更新:
這看起來與此處發現的問題相同:
擴展 RoleProvider GetRolesForUser()

更新:
有一個關於在 FormsAuthenticationTicket 中使用 UserData 的建議,但即使沒有登錄我也需要這些數據。

更新:這些天我通過使用通配符 SSL 證書以更簡單的方式解決了這個問題,該證書允許我為每個項目配置子域,因此項目選擇直接在 URL 中指定(並且每個項目都有自己的子域)。 在沒有子域的本地主機上運行時,我仍然純粹出於測試目的使用 cookie hack。

原解決方案:

我還沒有找到關於這種情況的任何“最佳實踐”,但這是我確定的:

1)為了支持匿名用戶在項目(即SQL數據庫)之間切換,我只是使用一個會話變量來跟蹤項目選擇。 我有一個全局屬性,它使用此項目選擇在需要時提供相應的 SQL 連接字符串。

2) 為了支持在應用了角色限制的頁面上調用 GetRolesForUser(),我們不能使用會話變量,因為如前所述,當實際調用 GetRolesForUser() 時會話變量尚未初始化(我已經在請求周期的這個早期發現沒有辦法強制它存在)。

3) 唯一的選擇是使用 cookie,或使用 Forms Authentication 票證的 UserData 字段。 我研究了許多關於使用鏈接到存儲在應用程序緩存中的對象的會話/cookie/ID(當會話不可用時可用)的理論,但最終正確的選擇是將此數據放在身份驗證票中。

4) 如果用戶登錄到項目,則是通過 ProjectName/UserName 對,因此在我們跟蹤用戶身份驗證的任何地方,我們都需要這兩個數據。 在簡單的測試中,我們可以將工單中的用戶名和項目名稱放在單獨的 cookie 中,但是它們可能會不同步。 例如,如果我們為項目名稱使用會話 cookie 並在登錄時勾選“記住我”(為身份驗證票創建永久 cookie),那么當會話 cookie 過期(瀏覽器關閉)時,我們可以得到用戶名但沒有項目名稱)。 因此,我手動將項目名稱添加到身份驗證票的 UserData 字段中。

5) 我還沒有弄清楚如何在不顯式設置 cookie 的情況下操作 UserData 字段,這意味着我的解決方案無法在“無cookie”會話模式下工作。

最終的代碼變得相對簡單。

我在登錄頁面中覆蓋了 LoginView 的 Authenticate 事件:

    //
    // Add project name as UserData to the authentication ticket.
    // This is especially important regarding the "Remembe Me" cookie - when the authentication
    // is remembered we need to know the project and user name, otherwise we end up trying to 
    // use the default project instead of the one the user actually logged on to.
    //
    // http://msdn.microsoft.com/en-us/library/kybcs83h.aspx
    // http://msdn.microsoft.com/en-us/library/system.web.ui.webcontrols.login.remembermeset(v=vs.100).aspx
    // http://www.hanselman.com/blog/AccessingTheASPNETFormsAuthenticationTimeoutValue.aspx
    // http://www.csharpaspnetarticles.com/2009/02/formsauthentication-ticket-roles-aspnet.html
    // http://www.hanselman.com/blog/HowToGetCookielessFormsAuthenticationToWorkWithSelfissuedFormsAuthenticationTicketsAndCustomUserData.aspx
    // http://stackoverflow.com/questions/262636/cant-set-formsauthenicationticket-userdata-in-cookieless-mode
    //
    protected void LoginUser_Authenticate(object sender, AuthenticateEventArgs e)
    {
        string userName = LoginUser.UserName;
        string password = LoginUser.Password;
        bool rememberMe = LoginUser.RememberMeSet;
        if ( [ValidateUser(userName, password)] )
        {
            // Create the Forms Authentication Ticket
            FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
                1,
                userName,
                DateTime.Now,
                DateTime.Now.AddMinutes(FormsAuthentication.Timeout.TotalMinutes),
                rememberMe,
                [ ProjectName ],
                FormsAuthentication.FormsCookiePath);

            // Create the encrypted cookie
            HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(ticket));
            if (rememberMe)
                cookie.Expires = DateTime.Now.AddMinutes(FormsAuthentication.Timeout.TotalMinutes);

            // Add the cookie to user browser
            Response.Cookies.Set(cookie);

            // Redirect back to original URL 
            // Note: the parameters to GetRedirectUrl are ignored/irrelevant
            Response.Redirect(FormsAuthentication.GetRedirectUrl(userName, rememberMe));
        }
    }

我有這個全局方法來返回項目名稱:

    /// <summary>
    /// SQL Server database name of the currently selected project.
    /// This name is merged into the connection string in EventConnectionString.
    /// </summary>
    public static string ProjectName
    {
        get
        {
            String _ProjectName = null;
            // See if we have it already
            if (HttpContext.Current.Items["ProjectName"] != null)
            {
                _ProjectName = (String)HttpContext.Current.Items["ProjectName"];
            }
            // Only have to do this once in each request
            if (String.IsNullOrEmpty(_ProjectName))
            {
                // Do we have it in the authentication ticket?
                if (HttpContext.Current.User != null)
                {
                    if (HttpContext.Current.User.Identity.IsAuthenticated)
                    {
                        if (HttpContext.Current.User.Identity is FormsIdentity)
                        {
                            FormsIdentity identity = (FormsIdentity)HttpContext.Current.User.Identity;
                            FormsAuthenticationTicket ticket = identity.Ticket;
                            _ProjectName = ticket.UserData;
                        }
                    }
                }
                // Do we have it in the session (user not logged in yet)
                if (String.IsNullOrEmpty(_ProjectName))
                {
                    if (HttpContext.Current.Session != null)
                    {
                        _ProjectName = (string)HttpContext.Current.Session["ProjectName"];
                    }
                }
                // Default to the test project
                if (String.IsNullOrEmpty(_ProjectName))
                {
                    _ProjectName = "Test_Project";
                }
                // Place it in current items so we do not have to figure it out again
                HttpContext.Current.Items["ProjectName"] = _ProjectName;
            }
            return _ProjectName;
        }
        set 
        {
            HttpContext.Current.Items["ProjectName"] = value;
            if (HttpContext.Current.Session != null)
            {
                HttpContext.Current.Session["ProjectName"] = value;
            }
        }
    }

您不能將項目選擇回發到某個頁面,將該選擇添加到會話中,然后重定向到適當的受保護頁面,身份驗證將在其中啟動並強制登錄嗎?

ASP.NET 會話不會以 cookie 的形式創建,直到您至少在其中放置一項。

暫無
暫無

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

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