使用Forms Authentication實現用戶註冊、登錄

使用Forms Authentication實現用戶註冊、登錄

一 基礎知識

本文示例代碼:http://www.codeplex.com/a/Release/ProjectReleases.aspx?ReleaseId=9518

前言

  本來使用Forms Authentication進行用戶驗證的方式是最常見的,但系統地闡明其方法的文章並不多見,網上更多的文章都是介紹其中某一部分的使用方法或實現原理,而更多的朋友則發文詢問如何從頭到尾完整第實現用戶的註冊、登錄。因此,本文計劃通過一個實際的例子,介紹如何基於Forms Authentication實現:

  • 用戶註冊(包括密碼的加密存儲)
  • 用戶登錄(包括密碼的驗證、設置安全Cookie)
  • 用戶實體替換(使用自己的類型作爲HttpContext.User的類型)

  有關Forms Authentication的原理等內容不屬於本文的討論範疇,大家可以通過在搜索引擎中輸入“Forms Authentication”、“Forms身份驗證”、“窗體身份驗證”等關鍵詞來查看更多資源。本文僅從實用的角度介紹如何使用這一技術。

不使用Membership

  本文介紹的實現方式不依賴ASP.NET 2.0提供的Membership功能。這主要是因爲,如果使用Membership,就必須用aspnet_regsql.exe實用工具配置數據庫,否則就得自己寫自定義的MembershipProvider。

  如果用aspnet_regsql.exe配置數據庫,就會導致數據庫中出現很多我們實際並不需要的表或字段。此外更重要的是,默認的SqlMembershipProvider給很多數據表添加了ApplicationID列,其初衷可能是希望可以將多個應用程序的用戶全部放在一個庫裏,但又能彼此隔離。但實際情況是,每個應用程序都在其自身的數據庫中保存用戶數據。因此,引入這個ApplicationID無端地在每次查找用戶時增加了額外的條件。

  另一方面,如果考慮自己實現一個MembershipProvider,因爲工作量巨大,有點得不償失。

  但是,如果不使用Membership,也就無法享受ASP.NET 2.0中新增的Login等控件的便利了。

與Forms Authentication相關的配置

  在web.config文件中,<system.web>/<authentication>配置節用於對驗證進行配置。爲<authentication>節點提供mode="Forms"屬性可以啓用Forms Authentication。一個典型的<authentication>配置節如下所示:

<authentication mode="Forms">
     <forms
         name=".ASPXAUTH"
         loginUrl="login.aspx"
         defaultUrl="default.aspx"
         protection="All"
         timeout="30"
         path="/"
         requireSSL="false"
         slidingExpiration="false"
         enableCrossAppRedirects="false"
         cookieless="UseDeviceProfile"
         domain=""
     />
</authentication>

  以上代碼使用的均是默認設置,換言之,如果你的哪項配置屬性與上述代碼一致,則可以省略該屬性。例如<forms name="MyAppAuth" />。下面依次介紹一下各種屬性:

  • name——Cookie的名字。Forms Authentication可能會在驗證後將用戶憑證放在Cookie中,name屬性決定了該Cookie的名字。通過FormsAuthentication.FormsCookieName屬性可以得到該配置值(稍後介紹FromsAuthentication類)。
  • loginUrl——登錄頁的URL。通過FormsAuthentication.LoginUrl屬性可以得到該配置值。當調用FormsAuthentication.RedirectToLoginPage()方法時,客戶端請求將被重定向到該屬性所指定的頁面。loginUrl的默認值爲“login.aspx”,這表明即便不提供該屬性值,ASP.NET也會嘗試到站點根目錄下尋找名爲login.aspx的頁面。
  • defaultUrl——默認頁的URL。通過FormsAuthentication.DefaultUrl屬性得到該配置值。
  • protection——Cookie的保護模式,可取值包括All(同時進行加密和數據驗證)、Encryption(僅加密)、Validation(僅進行數據驗證)和None。爲了安全,該屬性通常從不設置爲None。
  • timeout——Cookie的過期時間。
  • path——Cookie的路徑。可以通過FormsAuthentication.FormsCookiePath屬性得到該配置值。
  • requireSSL——在進行Forms Authentication時,與服務器交互是否要求使用SSL。可以通過FormsAuthentication.RequireSSL屬性得到該配置值。
  • slidingExpiration——是否啓用“彈性過期時間”,如果該屬性設置爲false,從首次驗證之後過timeout時間後Cookie即過期;如果該屬性爲true,則從上次請求該開始過timeout時間才過期,這意味着,在首次驗證後,如果保證每timeout時間內至少發送一個請求,則Cookie將永遠不會過期。通過FormsAuthentication.SlidingExpiration屬性可以得到該配置值。
  • enableCrossAppRedirects——是否可以將以進行了身份驗證的用戶重定向到其他應用程序中。通過FormsAuthentication.EnableCrossAppRedirects屬性可以得到該配置值。爲了安全考慮,通常總是將該屬性設置爲false。
  • cookieless——定義是否使用Cookie以及Cookie的行爲。Forms Authentication可以採用兩種方式在會話中保存用戶憑據信息,一種是使用Cookie,即將用戶憑據記錄到Cookie中,每次發送請求時瀏覽器都會將該Cookie提供給服務器。另一種方式是使用URI,即將用戶憑據當作URL中額外的查詢字符串傳遞給服務器。該屬性有四種取值——UseCookies(無論何時都使用Cookie)、UseUri(從不使用Cookie,僅使用URI)、AutoDetect(檢測設備和瀏覽器,只有當設備支持Cookie並且在瀏覽器中啓用了Cookie時才使用Cookie)和UseDeviceProfile(只檢測設備,只要設備支持Cookie不管瀏覽器是否支持,都是用Cookie)。通過FormsAuthentication.CookieMode屬性可以得到該配置值。通過FormsAuthentication.CookiesSupported屬性可以得到對於當前請求是否使用Cookie傳遞用戶憑證。
  • domain——Cookie的域。通過FormsAuthentication.CookieDomain屬性可以得到該配置值。

  以上針對<system.web>/<authentication>/<forms>節點的介紹非常簡略,基本上是對於文檔進行的額外說明。有關<forms>節點的更多說明,請參見MSDN文檔(http://msdn2.microsoft.com/zh-cn/library/1d3t3c61(VS.85).aspx)。

FormsAuthentication類

  FormsAuthentication類用於輔助我們完成窗體驗證,並進一步完成用戶登錄等功能。該類位於system.web.dll程序集的System.Web.Security命名空間中。通常在Web站點項目中可以直接使用這個類,如果是在類庫項目中使用這個類,請確保引用了system.web.dll。

  前一節已經介紹了FormsAuthentication類的所有屬性。這一節將介紹該類少數幾個常用的方法。

  RedirectToLoginPage方法用於從任何頁面重定向到登錄頁,該方法有兩種重載方式:

public static void RedirectToLoginPage ()
public static void RedirectToLoginPage (string extraQueryString)

  兩種方式均會使瀏覽器重定向到登錄頁(登錄頁的URL由<forms>節點的loginUrl屬性指出)。第二種重載方式還能夠提供額外的查詢字符串。

  RedirectToLoginPage通常在任何非登錄頁的頁面中調用。該方法除了進行重定向之外,還會向URL中附加一個ReturnUrl參數,該參數即爲調用該方法時所在的頁面的URL地址。這是爲了方便登錄後能夠自動回到登錄前所在的頁面。

  RedirectFromLoginPage方法用於從登錄頁跳轉回登錄前頁面。這個“登錄前”頁面即由訪問登錄頁時提供的ReturnUrl參數指定。如果沒有提供ReturnUrl參數(例如,不是使用RedirectToLoginPage方法而是用其他手段重定向到或直接訪問登錄頁時),則該方法會自動跳轉到由<forms>節點的defaultUrl屬性所指定的默認頁。

  此外,如果<forms>節點的enableCrossAppRedirects屬性被設置爲false,ReturnUrl參數所指定的路徑必須是當前Web應用程序中的路徑,否則(如提供其他站點下的路徑)也將返回到默認頁。

  RedirectFromLoginPage方法有兩種重載形式:

public static void RedirectFromLoginPage
    (string userName, bool createPersistentCookie)

public static void RedirectFromLoginPage 
    (string userName, bool createPersistentCookie, string strCookiePath)

  userName參數表示用戶的標識(如用戶名、用戶ID等);createPersistentCookie參數表示是否“記住我”;strCookiePath參數表示Cookie路徑。

  RedirectFromLoginPage方法除了完成重定向之外,還會將經過加密(是否加密取決於<forms>節點的protection屬性)的用戶憑據存放到Cookie或Uri中。在後續訪問中,只要Cookie沒有過期,則將可以通過HttpContext.User.Identity.Name屬性得到這裏傳入的userName屬性。

  此外,FormsAuthentication還有一個SignOut方法,用於完成用戶註銷。其原理是從Cookie或Uri中移除用戶憑據。

小結

  好了,至此所需要掌握的基礎知識就齊備了,接下來我們將實現用戶註冊、登錄等功能。

二 用戶註冊與登錄

  從這一部分開始,我們將通過一個實際的完整示例來看一下如何實現用戶註冊與登錄。在介紹註冊與登錄之前,我們首先介紹一下如何判斷用戶是否已登錄,並未後面的示例編寫一些基礎代碼。

判斷用戶是否已經登錄

  首先,在Web站點項目中添加一個MasterPage,例如MasterPage.master。在這個母版頁的ContentPlaceHolder控件之前、<From>標籤之內插入如下代碼:

<asp:Panel ID="pnlAnonymous" runat="server">
     <asp:LinkButton ID="btnLogin" runat="server" Text="登錄" OnClick="btnLogin_Click">
     </asp:LinkButton> |
     <asp:HyperLink ID="lnkRegister" runat="server" NavigateUrl="~/register.aspx" Text="註冊">
     </asp:HyperLink>
</asp:Panel>
<asp:Panel ID="pnlLoggedin" runat="server">
     歡迎您,<asp:Label ID="lblUserName" runat="server"></asp:Label>!
     [<asp:LinkButton ID="btnLogout" runat="server" Text="註銷" 
         onclick="btnLogout_Click"></asp:LinkButton>]
</asp:Panel>
<asp:Panel ID="pnlNavigate" runat="server">
     <asp:HyperLink ID="lnkDefault" runat="server" NavigateUrl="~/default.aspx" Text="首頁">
     </asp:HyperLink> |
     <asp:HyperLink ID="lnkTest" runat="server" NavigateUrl="~/test.aspx" Text="測試頁">
     </asp:HyperLink>
</asp:Panel>

  這裏提供了三個Panel控件——pnlAnonymous、pnlLoggedin和pnlNavigate。pnlAnonymous用於在用戶未登錄時顯示“登錄”和“註冊”鏈接;pnlLoggedin用於在用戶已登錄時顯示用戶信息(如用戶名和到用戶個人信息頁的鏈接等,這裏僅顯示用戶名),以及一個“註銷”按鈕;pnlNavigate在任何時候都顯示,是站點的導航欄。

  現在我們要實現的是,判斷用戶是否登錄,並顯示pnlAnonymous和pnlLoggedin二者之一。這裏,如果是使用ASP.NET 2.0 Membership,則可以方便地使用LoginView、LoginName和LoginStatus等控件實現這些功能;然而我們不得不爲此忍受Membership所帶來的龐大而繁多的數據庫對象,或者花更多時間去編寫自定義的MembershipPorvider。

  這一系列的第一部分曾介紹過,如果用戶已經登錄,則可以從HttpContext.User.Identity.Name得到已登錄用戶的標識(通常是用戶名)。然而,如果用戶未登錄,這個值則爲空字符串。因此,通過判斷該值是否爲空字符串,即可的值用戶是否已登錄。




 

  由此,我們在MasterPage的後臺代碼中添加Page_Init方法,在頁面初始化時判斷用戶是否登錄,並顯示對應的Panel控件;此外,在用戶已登錄時,在pnlLoggedin中的歡迎語裏插入已登錄用戶的用戶名。

protected void Page_Init(object sender, EventArgs e)
{
     // 判斷用戶是否已登錄。
     if(HttpContext.Current.User.Identity.Name == "")
     {
         // 用戶未登錄。
         pnlAnonymous.Visible = true;
         pnlLoggedin.Visible = false;
     }
     else
     {
         // 用戶已登錄。
         pnlAnonymous.Visible = false;
         pnlLoggedin.Visible = true;

         lblUserName.Text = HttpContext.Current.User.Identity.Name;
     }
}

  此外,我們還向項目中添加了default.aspx、login.aspx、register.aspx、test.aspx這樣四個頁面。其中test.aspx用於檢驗重定向到登錄頁後的ReturnUrl參數值,和登錄後回到之前頁面的效果。

用戶註冊

  用戶註冊部分並不屬於驗證的範疇,因此Forms Authentication也沒有提供過多的支持。然而用戶註冊是比較簡單的,只需通過一個頁面蒐集用戶信息,並存放到數據庫中即可。在這個示例中,爲了方便和突出真正要討論的內容,用戶信息並不是真的放到數據庫中的,而是放在一個集合裏,不過通過一個DataAccess類來隱藏這種差異。理論上,一旦關閉應用程序並重新開啓,放在集合中的數據就會丟失。然而實際上,再重複運行這裏的示例時,用戶數據並不會丟失,即便應用程序有所改動也是。這是因爲ASP.NET 2.0對於Web項目都是動態重新編譯的,應用程序從未直接終止過。

  此外,就用戶的數據實體類而言,我們僅提供了註冊、登錄所必需的屬性——用戶名、密碼散列值、密碼Salt值。

  爲了進行用戶註冊,我們建立了register.aspx頁面,該頁面僅負責蒐集用戶信息,真正的創建用戶動作在App_Code下的Membership類中完成。注意,此Membership非彼Membership,與ASP.NET 2.0 Membership沒有任何關係。

  首先在Membership類中添加一個靜態方法CreateUser,用於創建用戶。該方法完成用戶實體的創建和密碼的散列等功能。其代碼如下所示:

 

public static void CreateUser(string userName, string password)
{
     UserObject user = new UserObject();
     user.Name = userName;
     user.PasswordSalt = GenerateSalt();
     user.PasswordHash = EncodePassword(password, user.PasswordSalt);

     DataAccess.AddUser(user);
}


 這段代碼非常簡單,其中用到了GenerateSalt和EncodePassword方法,這兩個方法用於完成salt值的生成和密碼的加salt散列。其代碼抽取自MembershipProvider類,進行了精簡如下所示:

public const string PasswordHashAlgorithmName = "SHA1";

static string EncodePassword(string password, string salt)
{
     byte[] src = Encoding.Unicode.GetBytes(password);
     byte[] saltbuf = Convert.FromBase64String(salt);
     byte[] dst = new byte[saltbuf.Length + src.Length];
     byte[] inArray = null;
     Buffer.BlockCopy(saltbuf, 0, dst, 0, saltbuf.Length);
     Buffer.BlockCopy(src, 0, dst, saltbuf.Length, src.Length);

     HashAlgorithm algorithm = HashAlgorithm.Create(PasswordHashAlgorithmName);
     inArray = algorithm.ComputeHash(dst);

     return Convert.ToBase64String(inArray);
}

static string GenerateSalt()
{
     byte[] data = new byte[0x10];
     new RNGCryptoServiceProvider().GetBytes(data);
     return Convert.ToBase64String(data);
}

    然後在register.aspx頁面中創建蒐集用戶信息的表單,這個表單非常簡單,僅需在ContentPlaceHolder1內添加如下代碼:

<table>
<tr>
<td>用戶名:</td>
<td><asp:TextBox ID="txtUserName" runat="server"></asp:TextBox></td>
</tr>
<tr>
<td>密碼:</td>
<td><asp:TextBox ID="txtPassword" runat="server" TextMode="Password"></asp:TextBox></td>
</tr>
<tr><td colspan="2"><asp:Button ID="btnOK" runat="server" Text="確定" 
         onclick="btnOK_Click" /></td></tr>
</table>
<asp:Label ID="lblMessage" runat="server"></asp:Label>

注意這比實際的註冊頁要簡單許多,沒有對用戶名和密碼的格式做驗證,也沒有讓用戶重複輸入密碼以確保沒有鍵入錯誤。

  在“確定”按鈕的事件處理器中添加如下代碼,完成註冊功能:

protected void btnOK_Click(object sender, EventArgs e)
{
     try
     {
         Membership.CreateUser(txtUserName.Text, txtPassword.Text);

         lblMessage.Text = "用戶創建成功!";
     }
     catch(Exception ex)
     {
         lblMessage.Text = "錯誤:" + ex.Message;
     }
}

用戶登錄

  用戶登錄大致由下面幾個步驟完成:

  • 根據用戶名取得與用戶相關的信息(用戶實體對象);
  • 判斷用戶密碼是否正確;
  • 設置用戶憑據Cookie並重定向到登錄前頁面。

 

  我們主要還是在Membership類中來完成這些工作。爲Membership類添加一個Login方法:

public static void Login(string userName, string password, bool rememberMe)
{
     // 獲取用戶。
     UserObject user = DataAccess.GetUserByName(userName);
     if(user == null)
         throw new ArgumentException("用戶不存在!", "UserName");

     // 檢查密碼是否正確。
     string pwdHash = EncodePassword(password, user.PasswordSalt);
     if(pwdHash != user.PasswordHash)
         throw new ArgumentException("密碼錯誤!", "Password");

     // 設置安全Cookie並進行重定向。
     FormsAuthentication.RedirectFromLoginPage(userName, rememberMe);
}

  這裏重點在於檢查密碼,不過其工作方式已經在上一篇文章中介紹過了,此處不再贅述。讀者應該注意到,對密碼進行散列使用的是已經存放在數據實體中的Salt值,而不是重新生成Salt值。

  這段示例代碼通過拋異常來返回錯誤信息。讀者在實際編寫這個方法時,可以創建多個Exception類的派生類,分別表示不同的錯誤;或者採取其他無需拋異常的方式。這一點仁者見仁,智者見智。

  接下來,提供一個簡單的登錄頁,完成收集用戶名和密碼的工作。login.aspx頁面的結構也很簡單,只需在<ContentPlaceHolder1>中添加下列代碼:

<table>
<tr>
<td>用戶名:</td>
<td><asp:TextBox ID="txtUserName" runat="server"></asp:TextBox></td>
</tr>
<tr><td>密碼:</td>
<td><asp:TextBox ID="txtPassword" runat="server" TextMode="Password"></asp:TextBox></td>
</tr>
<tr>
<td colspan="2">
<asp:Button ID="btnLogin" runat="server" Text="登錄" onclick="btnLogin_Click" />
<asp:CheckBox ID="chkRememberMe" runat="server" Text="記住我" />
</td></tr>
</table>
<asp:Label ID="lblMessage" runat="server"></asp:Label>

最後,在“登錄”按鈕的事件處理器中添加如下代碼,調用剛剛寫好的Membership.Login方法,完成登錄。

protected void btnLogin_Click(object sender, EventArgs e)
{
     try
     {
         Membership.Login(txtUserName.Text, txtPassword.Text, chkRememberMe.Checked);
     }
     catch(Exception ex)
     {
         lblMessage.Text = "錯誤:" + ex.Message;
     }
}

  至此,用戶登錄功能就OK了。由於之前已經完成了判斷用戶是否登錄的代碼,現在就可以運行示例,註冊一個用戶並嘗試從各個頁面進入登錄頁進行登錄了。

用戶註銷

  至此,我們已經爲用戶進入我們的系統提供了寬敞大路;但作爲一個負責任的程序,我們還得確保當用戶離開我們的程序時,能夠“揮一揮衣袖”不留下一絲痕跡。這個安全離開的行爲,我們稱之爲“註銷”;要抹去的“痕跡”也就是登錄時留下的用戶憑據Cookie。

  本文的第一部分已經介紹過,FormsAuthentication.SignOut方法可以協助完成這一任務。我們在MasterPage中也預先放置好了一個“註銷”鏈接按鈕,在這裏,只要爲該按鈕添加如下事件處理器即可:

protected void btnLogout_Click(object sender, EventArgs e)
{
     FormsAuthentication.SignOut();
     Response.Redirect(Request.RawUrl);
}

  在這裏,首先通過調用FormsAuthentication.SignOut方法移除了用戶憑證Cookie。這個方法並不能自動完成跳轉,因此我麼必須自己完成跳轉任務。關於註銷後跳轉到哪個頁面,每個應用程序都有自己的方式(如跳到登錄頁或跳到首頁等),這裏選擇的是刷新當前頁。

小結

  好了,到現在爲止,完整的用戶註冊、登錄功能就都實現了。由於有了FormsAuthentication類,這一任務已經非常簡單了。這一部分僅僅是從實踐的角度順序操作了一下。

 

  下一部分將介紹如何將我們自己的用戶實體放到HttpContext.User屬性中。

三 用戶實體替換

IPrincipal和IIdentity

  通過查閱文檔,我們可以看到HttpContext.User屬性的類型是IPrincipal接口。然而我們知道,接口通常是不能直接訪問的,其背後必定隱藏了一個實現了該接口的對象。那麼這個實際對象的類型是什麼呢?

  讓我們在前面示例的MasterPage的Page_Init方法上加一個斷點,再次運行程序,可以得到HttpContext.User屬性的真正類型是System.Security.Principal.GenericPrincipal。

  查看IPrincipal接口的定義,可以看到它只有一個公開屬性——Identity,其類型是這裏要提到的另外一個重要接口IIdentity。通過上面的斷點跟蹤,我們還能知道對於GenericPrincipal而言,其Identity屬性的實際類型是GenericIdentity,也是位於System.Security.Principal命名空間中。

  由此,我們引出了.NET Framework中關於Principal(實體)的整個類型系統。所有這些類型都位於mscorlib.dll程序集中,由此也可以看出,這套模型隸屬於整個系統的基石。

實現自己的IPrincipal

  要想用自己的實體對象替換HttpContext.User,就必須讓自己的實體對象實現IPrincipal接口;通常還必須伴隨着實現IIdentity接口。

  目前系統中有的是一個數據實體對象。一般而言,實現IPrincipal接口有一下兩種方式——

編寫單獨的類型實現IPrincipal接口,並在其中包含數據實體對象;修改數據實體對象使其實現IPrincipal接口。

  對於這兩種方式而言,其Identity屬性可以通過以下三種方式實現——

  • 使用.NET Framework提供的GenericIdentity;
  • 創建自定義的類,實現Identity接口;
  • 修改數據實體對象或自定義的實體類,讓它們同時實現IPrincipal和IIdentity接口。

  對於簡單的應用程序而言,通常可以修改數據實體對象,使其同時實現IPrincipal和IIdentity接口。而就複雜的分層架構應用程序,則建議在邏輯層創建分別實現了IPrincipal和IIdentity接口的對象。本文的示例明顯屬於前一種情況,因此我們考慮修改作爲數據實體類的UserObject類,讓其實現兩個接口。以下是修改完畢的UserObject類:

public class UserObject : IPrincipal, IIdentity
{
     /// <summary>
     /// 用戶名。
     /// </summary>
     public string Name;

     /// <summary>
     /// 密碼散列值。
     /// </summary>
     public string PasswordHash;

     /// <summary>
     /// 密碼salt值。
     /// </summary>
     public string PasswordSalt;

     #region IIdentity Members

     public string AuthenticationType
     {
         get
         {
              return "Froms";
         }
     }

     public bool IsAuthenticated
     {
         get
         {
              return true;
         }
     }

     string IIdentity.Name
     {
         get
         {
              return this.Name;
         }
     }

     #endregion

     #region IPrincipal Members

     public IIdentity Identity
     {
         get
         {
              return this;
         }
     }

     public bool IsInRole(string role)
     {
         return false;
     }

     #endregion
}

  首先我們來看一下對IIdentity接口的實現。該接口要求三個屬性——AuthenticationType、IsAuthenticated和Name。AuthenticationType表示該用戶標識所使用的驗證類型,這裏返回的是“Forms”;IsAuthenticated屬性表示當前用戶是否已經通過驗證(即是否已登錄。在這個例子裏,我們只針對已登錄用戶進行實體替換,所以這個屬性總是返回true。通常,實際的Web應用程序編寫時還有一種習慣,就是爲未登錄用戶(稱之爲匿名用戶)也提供一個用戶實體對象,此時就需要爲IsAuthenticated提供邏輯,判斷用戶是否已通過驗證了。最後IIdentity接口還要求對象提供一個Name屬性,在這裏,由於已經存在了Name字段,因此才用“顯示接口實現”來提供Name屬性,返回對象自身的Name字段即可。

  接下來我們看一下IPrincipal接口的實現。該接口要求提供一個Identity屬性和一個IsInRole方法。由於UserObject類本身已經實現了IIdentity接口,因此在Identity屬性中直接reutren this即可。因爲我們這個示例不涉及用戶分組(角色)方面的技術,因此IsInRole方法總是返回false。

用戶實體替換

  用戶實體替換即使用我們自己編寫的類型的實例來替換HttpContext.User屬性。實體替換應該發生在HttpApplication的PostAuthenticateRequest事件發生時,因爲此時ASP.NET已經從客戶端得到了用戶憑證Cookie並進行了解密和校驗。

  我們既可以編寫一個HttpModule來處理PostAuthenticateRequest事件,也可以在Global..asax文件中添加時間處理器。這裏爲了簡單,我們選擇在Global.asax中添加如下事件處理器:

void Application_PostAuthenticateRequest(object sender, EventArgs e)
{
     HttpApplication app = (HttpApplication)sender;
     if(app.Context.User.Identity.Name != "")  // 僅在已登錄時替換
     {
         UserObject user = DataAccess.GetUserByName(app.Context.User.Identity.Name);
         app.Context.User = user;
         Thread.CurrentPrincipal = user;
     }
}

  在這裏我們首先進行了判斷,如果用戶已登錄,才進行實體替換。當然你也可以選擇未未登錄用戶也提供一個匿名用戶實體。

  接下來,我們通過本來已經存放在HttpContext.User.Identity中的用戶標識得到了數據實體對象,然後分別將其賦予HttpContext.User和Thread.CurrentPrincipal。

  至此,我們的示例代碼就完工了。沒有提到的是,完成了這一步之後,你就可以通過類似下面的代碼在任何可以訪問到HttpContext的地方獲取用戶實體了:

UserObject user = HttpContext.Current.User as UserObject;
if(user != null)
{
       // 可以使用user
}
else
{
       // 用戶未登錄
}
      

  需要注意,由於在這裏我們僅對已登錄用戶進行了用戶實體替換,所以代碼使用as關鍵字進行類型轉換並結合if語句進行判斷是必需的。

小結

  好吧,這一部分說的是用戶實體替換。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章