本文轉自:http://www.cnblogs.com/yjmyzz/archive/2010/08/29/1812038.html
認證(authentication) 就是 "判斷用戶有沒有登錄?",好比windows系統,沒登錄就無法使用(不管你是用Administrator或Guest用戶,總之要先正確登錄后,才能進入系統)。
授權(authorization) 就是"用戶登錄后的身份/角色識別",好比"管理員用戶"登錄windows后,能安裝軟件、修改windows設置等所有操作,而Guest用戶登錄后,只有做有限的操作(比如安裝軟件就被禁止了)。
.net中與"認證"對應的是IIdentity接口,而與"授權"對應的則是IPrincipal接口,這二個接口的定義均在命名空間System.Security.Principal中:

using System; using System.Runtime.InteropServices; namespace System.Security.Principal { [ComVisible(true)] public interface IIdentity { string AuthenticationType { get; } bool IsAuthenticated { get; } string Name { get; } } }

using System; using System.Runtime.InteropServices; namespace System.Security.Principal { [ComVisible(true)] public interface IPrincipal { IIdentity Identity { get; } bool IsInRole(string role); } }
應該注意到:IPrincipal接口中包含着一個只讀的IIdentity,這也跟最開始提到的概念一致:識別身份的前提是先登錄,只有登錄成功后能進一步確認身份。
用Membership/Role做過asp.net開發的朋友們,看到這二個接口的定義,應該會覺得很眼熟,想想我們在Asp.Net頁面中是如何判斷用戶是否登錄以及角色的?

protected void Page_Load(object sender, EventArgs e) { HttpContext ctx = HttpContext.Current; if (ctx.User.Identity.IsAuthenticated && ctx.User.IsInRole("管理員")) { //管理員該做的事,就寫在這里 } else { //Hi,您不是管理員,別胡來! } }
這段代碼再熟悉不過了,沒錯!membership/role的原理就是基於這二個接口的,如果再對HttpContext.Current.User刨根問底,能發現:HttpContext.Current.User本身就是一個IPrincipal接口的實例。
在winform中,,我們知道:每個程序不管它是不是多線程,總歸是有一個默認的主線程的。所以只要把主線程的CurrentPrincipal與登錄后的_principal關聯起來后,其它任何窗體,都可以直接用它來做判斷,如果判斷通過,則可以這樣或那樣(包括創建多線程進行自己的處理),如果判斷不通過,則可以拒絕繼續操作。
Thread.CurrentPrincipal = _principal;//將其附加到當前線程的CurrentPrincipal
再來考慮一下Webform,當然,你可以直接使用從Asp.Net2.0就支持的membership/role機制,但membership/role默認只支持sqlserver數據庫(通過membership provider for oracle也可以支持oracle,但總有一些數據庫不被支持,比如access、mysql、sqlite、db2等),假如你不想把用戶名/密碼這類信息保存在sqlserver中(甚至不想保存在數據庫中,比如:xml),這時候就得開動腦筋了。
其實...就算不用membership/role,上面提到的這二個接口仍然是可以使用的,但有一個問題:winform中,IPrincipal接口的實例可以一直存儲在內存中(直到程序退出),所以其它窗口就能繼續訪問它,以便做進一步的判斷,但是在webform中,頁面本身是無狀態的,一旦服務器輸出html到客戶端瀏覽器后,客戶端的頁面就與服務器再無瓜葛了(你甚至可以離線瀏覽,前提是不刷新),那么最后的認證信息保存在什么地方呢?
答案就是客戶端的瀏覽器Cookie!所以在WebForm中的做法稍有不同:
創建一個webApplication,里面新建4個頁面:login.aspx,logout.aspx,default.aspx,gotoUrl.aspx,這四個頁面的作用如下:
login.aspx : 登錄頁面 logout.aspx: 用來處理用戶注銷 (非必需,但建議把注銷邏輯放在這里,以便任何需要注銷的地方重復利用) deafult.aspx: 登錄完成后的顯示頁面 gotoUrl.aspx : 登錄完成后,用來輔助做頁面跳轉的頁面(非必需,但建議加上)
login.aspx代碼:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Login.aspx.cs" Inherits="LoginTest.Login" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title></title> </head> <body> <form id="form1" runat="server"> <table> <tr> <td>用戶名:</td> <td> <asp:TextBox ID="txtUserName" runat="server" style="width:200px"></asp:TextBox></td> </tr> <tr> <td>密 碼:</td> <td> <asp:TextBox ID="txtPassword" runat="server" TextMode="Password" style="width:200px"></asp:TextBox> </td> </tr> <tr> <td></td> <td> <asp:Button ID="Button1" runat="server" Text="登 錄" onclick="Button1_Click" /> </td> </tr> </table> </form> </body> </html>
后台代碼:

using System; using System.Web; using System.Web.Security; namespace LoginTest { public partial class Login : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { } protected void Button1_Click(object sender, EventArgs e) { string user = this.txtUserName.Text; //讀取用戶名 string password = this.txtPassword.Text; //讀取密碼 if (ValidateUser(user, password) == true) //ValidateUser方法用來驗證用戶合法性的 { //建立表單驗證票據 FormsAuthenticationTicket Ticket = new FormsAuthenticationTicket(1, user, DateTime.Now, DateTime.Now.AddMinutes(30), true, "管理員,會員", "/"); //使用webcongfi中定義的方式,加密序列化票據為字符串 string HashTicket = FormsAuthentication.Encrypt(Ticket); //將加密后的票據轉化成cookie HttpCookie UserCookie = new HttpCookie(FormsAuthentication.FormsCookieName, HashTicket); //添加到客戶端cookie Context.Response.Cookies.Add(UserCookie); //登錄成功后重定向 Response.Redirect("GotoUrl.aspx?returnUrl=" + Server.UrlEncode("Default.aspx")); } else { //登錄失敗后的處理 } } /// <summary> /// 驗證用戶名/密碼是否正確 /// </summary> /// <param name="userName"></param> /// <param name="pwd"></param> /// <returns></returns> private bool ValidateUser(string userName, string pwd) { return true; //當然實際開發中,您可以到數據庫里查詢校驗,這里只是示例而已 } } }
GotoUrl.aspx:這個頁面只是單純的輔助跳轉而已,所以aspx頁面本身不用加什么代碼,只需要在后置cs代碼里簡單處理一下:

using System; namespace LoginTest { public partial class GotoUrl : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { string _returnUrl = Request["returnUrl"]; if (string.IsNullOrEmpty(_returnUrl)) { _returnUrl = "~/default.aspx"; } Response.Redirect(_returnUrl); } } }
接下來應該是Default.aspx了,這里只是演示,所以沒有后置代碼,判斷的邏輯全寫在default.aspx本身:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="LoginTest.Default" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title></title> </head> <body> <form id="form1" runat="server"> <div> <% if (User.Identity.IsAuthenticated) { Response.Write("<span style='color:red'>" + User.Identity.Name + "</span>已登錄!"); if (User.IsInRole("管理員")) { Response.Write(" 當前用戶角色:管理員"); } if (User.IsInRole("會員")) { Response.Write(",會員。"); } Response.Write(" <a href='logout.aspx'>安全退出</a>"); } else { Response.Write("請先<a href='login.aspx'>登錄</a>"); } %> </div> </form> </body> </html>
最后一個是注銷頁面logout.aspx,類似的,這個頁面本身只負責注銷cookie票據,所以界面上沒東西,只有后置代碼:

using System; using System.Web.Security; namespace LoginTest { public partial class Logout : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { FormsAuthentication.SignOut(); Response.Redirect("default.aspx"); } } }
修改web.config成下面這樣:

<?xml version="1.0"?> <configuration> <system.web> <compilation debug="true" targetFramework="4.0" /> <authentication mode="Forms"> <forms name=".ASPXAUTH" loginUrl="login.aspx" timeout="30" path="/" requireSSL="false" domain=""> </forms> </authentication> </system.web> </configuration>
運行,認證已經成功了!但是好象還有點問題:並沒有識別出身份!(即login.aspx.cs中代碼指定的"管理員,會員"角色)。靜下心來想想問題出在哪里?
在winform中,我們用
IPrincipal _principal = new GenericPrincipal(_identity, new string[] { "管理員" }); Thread.CurrentPrincipal = _principal;//將其附加到當前線程的CurrentPrincipal
給_principal授權為"管理員"(當然還能給它更多的角色),然后將其賦值為線程的CurrentPrincipal,所以就ok了,但是webform中並沒有Thread.CurrentPrincipal,而且http本身又是無狀態的,下一次http請求,根本無法記得上次請求時的狀態(就好象每次http請求都是重新投胎一樣,前世忘記得一干二凈),幸好:微軟為asp.net搞出一個上下文Context的概念,一個webApplication中,雖然http協議本身是無狀態的,但是每個aspx頁面被請求時,總會附帶一個HttpContext上下文,可以用它來找回一些前世的記憶,而且文章最開頭提到了 HttpContext.Current.User本身就是IPrincipal,這不就是Thread.CurrentPrincipal的變種么?
新建一個Global.ascx,打開后置代碼,內容如下:

using System; using System.Security.Principal; using System.Web; using System.Web.Security; namespace LoginTest { public class Global : System.Web.HttpApplication { protected void Application_Start(object sender, EventArgs e) { } protected void Session_Start(object sender, EventArgs e) { } protected void Application_BeginRequest(object sender, EventArgs e) { } /// <summary> /// 每個aspx頁面要求認證時被觸發 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> protected void Application_AuthenticateRequest(object sender, EventArgs e) { HttpContext _ctx = HttpContext.Current; if (_ctx.User != null) { if (_ctx.User.Identity.IsAuthenticated == true) //認證成功的用戶,才進行授權處理 { FormsIdentity _Identity = (FormsIdentity)_ctx.User.Identity; string[] Roles = _Identity.Ticket.UserData.Split(','); //將角色字符串,即login.aspx.cs中的“管理員,會員”,變成數組 _ctx.User = new GenericPrincipal(_Identity, Roles); //將帶有角色的信息,重新生成一個GenericPrincipal賦值給User,相當於winform中的Thread.CurrentPrincipal = _principal } } } protected void Application_Error(object sender, EventArgs e) { } protected void Session_End(object sender, EventArgs e) { } protected void Application_End(object sender, EventArgs e) { } } }
再測試一下,結果就正常了。