在做網站的時候,都會用到使用者登入的功能。對於一些敏感的資源,我們只希望被授權的使用者才能夠訪問,這讓然需要使用者的身分識別驗證。對於初學者,通常將使用者登入資訊存放在Session中,筆者在剛接觸到asp.net的時候就是這麼做的。當我將使用者資訊存在在Session中時,常常會遇到Session丟失導致使用者無法正常訪問被授權的資源,保持使用者登入狀態時的安全性問題,無休其實,在asp.net中,我們有更好的解決方案,那就是通過Forms身分識別驗證,從而對使用者進行授權,這種方法可以輕鬆的保持使用者的登入狀態(如果使用者想這樣),便捷的使用者授權配置,增強的安全性等好處。廢話不再多說,下面我們來做一個簡單的用
在做例子之前,我們先定義如下使用者類,類名為SampleUser,代碼如下:
public partial class SampleUser { string username; public string UserName { get { return username; } set { username = value; } } string userpwd; public string UserPWD { get { return userpwd; } set { userpwd = value; } } public override bool Equals(object obj) { SampleUser other = obj as SampleUser; if (other == null || other.UserName != this.UserName) return false; return true; } } public partial class SampleUser { public static ListuserList = new List{ new SampleUser() { UserName = "01", UserPWD = "123"}, new SampleUser() { UserName = "02", UserPWD = "123" }, new SampleUser() { UserName = "03", UserPWD = "123" }, new SampleUser() { UserName = "04", UserPWD = "123" }, }; public static SampleUser GetUser(string userName) { return userList.Find(u=>u.UserName == userName); } }
在類SampleUser中,定義了UserName和UserPWD兩個欄位,分別用來儲存使用者的登入名稱和密碼資訊。在SampleUser類的另一部分中,我們提供了一個使用者的靜態類表,用來代替儲存在資料庫中的使用者資訊,提供一個方法GetUser,用來擷取使用者資訊。
在這個例子中,我們示範使用者必須進行登入才能訪問網站的資源,如果沒有登入,則將使用者導航到login.aspx頁面中。
第一步,在web.config中添加配置資訊,說明網站要使用Forms身分識別驗證,並指定登入頁面和預設登入成功後的跳轉頁面,然後指定拒絕未登入使用者的訪問,代碼如下:
<authentication mode="Forms"> <forms loginUrl="~/Login.aspx" defaultUrl="~/Default.aspx" /> </authentication> <authorization> <deny users="?"/> </authorization>
完成這一步後,我們再開啟Default.aspx頁面,在沒有登入的情況下,頁面會被導航到Login.aspx頁面,我們的第一步的目的已經達到了。
第二步,完成Login.aspx的頁面邏輯。在頁面中添加兩個TextBox控制項,用來輸入使用者名稱和密碼;添加一個CheckBox控制項,用來選擇是否保持登入狀態;添加一個Button控制項,響應使用者的登入操作。相應的代碼如下:
<fieldset> <legend>使用者登陸</legend> <div> 使用者名稱:<asp:TextBox ID="txtUserID" runat="server" Width="150" /><br /><br /> 密 碼:<asp:TextBox ID="txtUserPWD" runat="server" TextMode="Password" Width="150" /><br /><br /> <asp:CheckBox ID="cbSaveUserName" runat="server" Checked="true" Text="保持登入狀態" /> </div><br /> <asp:Literal ID="ltMessage" Text="" runat="server" Visible="false" /> <br /> <p> <asp:Button ID="btnLogin" Text="登陸" runat="server" OnClick="btnLogin_Click" /> </p> </fieldset>
接下來完成後台代碼,添加登陸按鈕的幕後處理方法:對使用者名稱和密碼進行驗證,如果驗證通過,則為使用者名稱建立一個身分識別驗證票據,並將其添加到響應的Cookie中。代碼如下:
protected void btnLogin_Click(object sender, EventArgs e) { string userID = this.txtUserID.Text.Trim(); string userPWD = this.txtUserPWD.Text.Trim(); SampleUser userEx = SampleUser.GetUser(userID); if (userEx == null) { ltMessage.Text = "使用者不存在!"; ltMessage.Visible = true; return; } if (userEx.UserPWD != userPWD) { ltMessage.Text = "使用者名稱或密碼錯誤,請重新輸入!"; ltMessage.Visible = true; return; } //添加票據,並將使用者導航到預設頁面 FormsAuthentication.RedirectFromLoginPage(userEx.UserName, this.cbSaveUserName.Checked); }
完成這一步後,我們就已經完成了簡單Froms驗證的功能。運行程式,你會發現,這裡存在一個問題!!!
你發現了嗎?當我們被導航到login.aspx時,這個頁面的樣式丟失了!這是因為我們對整個網站的資源進行了訪問限制,如果沒有登陸,使用者不僅無法訪問.aspx頁面,甚至連css檔案、js檔案都無法訪問。顯然,這不是我們想要的,因為這些資源並不是敏感的資源。在通常情況下,我們只希望對部分檔案夾中的檔案進行驗證訪問限制,而不是整個網站,例如,我們允許只對User檔案夾下的頁面進行訪問限制,因為這個檔案夾中存放的是使用者的私人資訊,這些資訊是敏感的。這該如何?呢?
為了完成示範分目錄驗證,我們在項目中添加一個User檔案夾,並添加UserInfo.aspx、 UserLogin.aspx兩個頁面。UserInfo.aspx用來展示使用者資訊,它的商務邏輯我們不是我們關心的,UserLogin.aspx頁面用來讓使用者登陸,代碼跟Login.aspx頁面幾乎完全相同。
第一步:修改Web.config檔案,允許匿名使用者存取系統資源。
<authorization> <allow users="?"/> </authorization>
第二步:在User檔案夾下添加一個Web.config檔案,修改代碼,拒絕匿名使用者存取該檔案夾下的資源。
<authorization> <deny users="?"/> </authorization>
完成這兩步後,我們訪問UserInfo.aspx時,如果沒有登陸,則會被導航到~/User/UserLogin.aspx頁面,當登陸後,又會被導航到~/User/UserInfo.aspx頁面。這個時侯,我們的登陸頁面樣式並沒有丟失,這說明我們的設定檔起作用了。
接下來,我們想在UserInfo.aspx頁面中顯示出已登陸使用者的使用者名稱和密碼(這裡完全是為了示範如何擷取登陸使用者資料才這樣做的,通常使用者的密碼是不會展示的)。在進行登陸後,使用者的票據資訊被加密儲存在Cookie中,這個票據中,有已登入使用者的名稱資訊,我們通過擷取票據中的使用者名稱,即可擷取到完整的使用者資訊。
為了顯示使用者資訊,我們在頁面中放置兩個Label控制項,代碼如下:
<h2><p>使用者名稱:<asp:Label ID="lblUserName" Text="" runat="server" /></p><p>密 碼:<asp:Label ID="lblUserPWD" Text="" runat="server" /></p></h2>
然後,我們在頁面的Load方法中,擷取並展示使用者資訊:
if (this.Context.User != null && this.Context.User.Identity != null && this.Context.User.Identity.IsAuthenticated){ SampleUser user = SampleUser.GetUser(this.Context.User.Identity.Name); if (user != null) { this.lblUserName.Text = user.UserName; this.lblUserPWD.Text = user.UserPWD; }}
再次運行我們的代碼,當使用者登陸後(如果保持登陸狀態,即使關掉並重新開啟瀏覽器),我們都可以擷取到已登入使用者的Name,從而擷取使用者的對象。
如果要退出登陸,我們只需要刪除儲存在Cookie中的票證資訊即可,這個功能Forms驗證已經幫我們完成,代碼很簡單:
FormsAuthentication.SignOut(); //退出登陸
在本文中,沒有涉及到角色的驗證,這是因為通過在設定檔中指定角色這種方法並不夠靈活,如果角色是可以在程式中維護的,那麼我們在這裡的指定就形同虛設了。感興趣的朋友可以自行學習,也並不複雜。在本文的結尾,附上詳細的Forms驗證在Web.config中的配置說明:
<forms name="name" loginUrl="URL" defaultUrl="URL" protection="[All|None|Encryption|Validation]" timeout="[MM]" path="path" requireSSL="[true|false]" slidingExpiration="[true|false]"> enableCrossAppRedirects="[true|false]" cookieless="[UseUri|UseCookie|AutoDetect|UseDeviceProfile]" domain="domain name" ticketCompatibilityMode="[Framework20|Framework40]"> <credentials>...</credentials> </forms>
name:指定要用於身分識別驗證的 HTTP Cookie。如果正在一台伺服器上運行多個應用程式並且每個應用程式都需要唯一的 Cookie,則必須在每個應用程式的 Web.config 檔案中配置 Cookie 名稱。預設值為 ".ASPXAUTH"。
loginUrl:指定如果找不到任何有效身分識別驗證 Cookie,將請求重新導向到的用於登入的 URL。預設值為 login.aspx。
defaultUrl:定義在身分識別驗證之後用於重新導向的預設 URL。預設值為 "default.aspx"。
protection:指定 Cookie 使用的加密類型(如果有)。預設值為 All。
timeout:指定 Cookie 到期前逝去的時間(以整數分鐘為單位)。如果 SlidingExpiration 屬性為 true,則 timeout 屬性是滑動值,會在接收到上一個請求之後的指定時間(以分鐘為單位)後到期。 為防止危及效能並避免向開啟 Cookie 警告的使用者發出多個瀏覽器警告,當指定的時間逝去大半時將更新 Cookie。這可能導致精確性受損。預設值為 "30"(30 分鐘)。
path:為應用程式發出的 Cookie 指定路徑。預設值是斜杠 ( /),這是因為大多數瀏覽器是區分大小寫,如果路徑大小寫不匹配,瀏覽器不會送回 Cookie。
requireSSL:指定是否需要 SSL 串連來傳輸身分識別驗證 Cookie。預設值為 False。
slidingExpiration:指定是否啟用可調到期時間。可調到期將 Cookie 的當前身分識別驗證時間重設為在單個會話期間收到每個請求時到期。預設值為 True。
enableCrossAppRedirects:表明是否將通過身分識別驗證的使用者重新導向到其他 Web 應用程式中的 URL。預設值為 False。
cookieless:定義是否使用 Cookie 以及 Cookie 的行為。預設值為 UseDeviceProfile.
domain:指定在傳出 Forms 身分識別驗證 Cookie 中設定的可選域。此設定的優先順序高於 httpCookies 元素中使用的域。預設值為空白字串 ("")。
ticketCompatibilityMode:指定在 Forms 身分識別驗證中對於票證到期日使用國際標準時間 (UTC) 還是本地時間。預設值為 Framework20。
子項目 credentials:允許選擇在設定檔中定義名稱和密碼憑據。您還可以實現自訂的密碼架構,以使用外部源(如資料庫)來控制驗證。