本文分別以ASP.NET1.1與ASP.NET2.0在Forms 身份驗證上的實現方法,以及ASP.NET2.0較上一版本有哪些改進或變化進行說明.相信讀者都己經看過許多類似這樣的文章,不倫是在網上或是某些專業書籍上,最近又有模式&實踐小組成員發布WCF安全模型指南,可見構建網站安全總是不過時的話題,作者認為此文也絕對是您應該收藏的參考資料.
ASP.NET 安全性的工作原理
網站在安全性方面有一個常見的要求:特定的頁面僅允許某些成員或其他經過身份驗證的用戶瀏覽.充分利用Forms身份驗證是最好的方式.
身份驗證
從實現機制來說ASP.NET1.1與ASP.NET2.0的安全模型是一致的.首先配置網站為Forms 身份驗證模式,之后用戶訪問網站的URL,Forms 身份驗證系統會將未經身份驗證的請求重定向到指定的登錄頁.用戶輸入憑據(用戶名密碼)并提交該頁.如果驗證程序驗證用戶的身份合法,則系統會向客戶端發出一個特定 Cookie(.NET1.1不支持無Cookie模式),它代表用戶的身份驗證票據.這樣后續的請求中,客戶端瀏覽器會把該Cookie一同發送致服務器,如果該Cookie有效則用戶通過身份驗證并允許對原始請求的資源的訪問.
授權
如果用戶的請求被驗證通過了,但是他請求的URL是否允許用戶訪問了呢,這就用到了授權.可以通過應用程序配置文件來進行授友也可以在程序中使用代碼來驗證用戶是否有資格訪問該資源.如果授權失敗,則 ASP.NET 將用戶重定向到登錄頁.如果用戶已被授權,則將允許用戶訪問受保護資源.
ASP.NET1.1實現方式
ASP.NET1.1的實現方式非常簡單,不過我們還是需要手寫一些代碼的,下面我們就一步一步地實現.應用程序配置節的詳細說明請參考MSDN相關文檔.
l 配置應用程序使用 Forms 身份驗證,編輯web.config文件
復制代碼 代碼如下:
<configuration>
<system.web>
<authentication mode="Forms">
<forms loginUrl="Login.aspx" protection="All" timeout="30" path="http://www.49028c.com/" />
</authentication>
<authorization>
<deny users="?" /> <!—拒絕匿名 -->
</authorization>
......
</system.web>
<location path="Admin"><!—配置授權,只允許擁有Admins角色的用戶訪問這個目錄下的文件(*.aspx)-->
<system.web>
<authorization>
<allow roles="Admins"/><!—雖然下面配置為拒絕所有用戶,但是allow的優先級比deny高.-->
<deny users="*" /><!—拒絕所有用戶 -->
<!—
一個用戶或角色必須特別指定為拒絕,才能拒絕該用戶或角色對URL的權限.如果上面的示例沒有指定<deny users="*" />元素,則將允許所有通過身份驗證的用戶訪問所請求的 URL,而不考慮其所屬的角色.
-->
</authorization>
</system.web>
</location>
</configuration>
l 創建登錄頁面Login.aspx
頁面預覽如下,代碼詳細參考本文附件的項目源碼.

創建用戶身份主體
ASP.NET1.1安全模型提供了四種授權方法,這四種方法都使用HttpContext.User對象進行驗證授權.
l 使用應用程序配置進行授權,只有具有指定角色的用戶才能訪問web.config所在的文件夾與子文件夾
<authorization>
<allow roles="Admins"/>
<deny users="?"/>
</authorization>
l 使用PrinciplePermissionAttribute控制對類和方法的訪問,只允許角色為Admins的成員才能調用該方法
[System.Security.Permissions.PrincipalPermission(System.Security.Permissions.SecurityAction.Demand,Role=” Admins”)]
public static bool MethodName()
{
...
}
l 以編程方式使用PrinciplePermission類控制對代碼塊的訪問,只允許角色為Admins的成員調用Demand之后的代碼
public static bool MethodName()
{
System.Security.Permissions.PrincipalPermission perm = new System.Security.Permissions.PrincipalPermission(null, "Admins");
perm.Demand();
...
}
l 使用Iprincipal.IsInRole方法,只允許角色為Admins的成員運行if中的代碼,大部分情況我們都使用這種方法判斷用戶是否有權限.
public static bool MethodName()
{
if (HttpContext.Current.User.IsInRole("Admins"))
{
//some code
}
}
針對以上的特點,程序員必須在合適的地方創建HttpContext.User對象,以達到驗證模型的要求.開發人員必須編寫HttpApplication:: AuthenticateRequest事件.該事件的發生代表著用戶己經通過Forms身份驗證.
在Global.asax中實現Application_AuthenticateRequest.
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
HttpApplication app = (HttpApplication)sender;
HttpCookie cookie = Request.Cookies[FormsAuthentication.FormsCookieName];
if (cookie != null)
{
string encryptedTicket = cookie.Value;
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(encryptedTicket);
//獲取在登錄驗證時加入驗證票據的用戶所擁有的角色,但真正開發時請不這樣做,建議從數據庫中獲取該用戶角色信息.
//因為Cookie本身有長度的限制,并且將用戶角色存儲到客戶端也不是安全的行為.
//大家想想如果Cookie不限制大小,那么它的尺寸大到幾MB或GB時,客戶端與服務器的每一次通迅,將是怎樣的一種情況了,呵呵.
//這里僅展示如何將角色信息加入到用戶主體GenericPrincipal中.
string[] roles = ticket.UserData.Split(new char[] { ',' });//獲取角色
FormsIdentity identity = new FormsIdentity(ticket);
System.Security.Principal.GenericPrincipal user = new System.Security.Principal.GenericPrincipal(identity, roles);
app.Context.User = user;
//app.Context.User = new System.Security.Principal.GenericPrincipal(new System.Web.Security.FormsIdentity(FormsAuthentication.Decrypt(cookie.Value)), new string[]{"Admins"});
}
}
或者在Global.asax中實現FormsAuthentication_Authenticate效果是一樣的.
void FormsAuthentication_OnAuthenticate(object sender, FormsAuthenticationEventArgs e)
{
HttpCookie cookie = Request.Cookies[FormsAuthentication.FormsCookieName];
if (cookie != null)
{
string encryptedTicket = cookie.Value;
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(encryptedTicket);
string[] roles = ticket.UserData.Split(new char[] { ',' });
FormsIdentity identity = new FormsIdentity(ticket);
System.Security.Principal.GenericPrincipal user = new System.Security.Principal.GenericPrincipal(identity, roles);
e.Context.User = user;
}
}
其實FormsAuthenticationModule會自動生成一個User對象,只不過這個對象的角色列表為空,它只能是代表通過身份驗證,而不能通過授權,因為我們限制了目錄的訪問角色,所以開發人員必須實現上面代碼,才能滿足我們的要足,如果說你的網站僅需要通過身份驗證的話,就可不必實現這些方法了.
用戶在請求URL時,ASP.NET請求通道會連續觸發一堆的事件,這些事件完成了一系列任務,其中就包括Forms身份驗證事件與授權事件.如下所示:
BeginRequest 請求開始事件
AuthenticateRequest 驗證通過事件 (上面兩段代碼就是在這個事件中被執行)
PostAuthenticateRequest 用戶標識己建立時發生 ASP.NET 2.0引入的事件,后面會講到.
AuthorizeRequest 當安全模塊已驗證用戶授權時發生
....其它事件略;
正是這些事件的壘加促成了ASP.NET框架驗證模型的實現, 而且通過完成上面的幾個步驟,網站內容就己經受到授權機制的保護了.
下面讓我們看看ASP.NE安全模型是如何做到授權的.
l ASP.NET 1.1 安全模型驗證授權的原理
在 ASP.NET 中,有兩種方式限制對資源訪問的權限:文件授權與URL 授權,這里我們僅討倫后者.
URL 授權由 UrlAuthorizationModule 執行,它將用戶和角色映射到 ASP.NET 應用程序中的 URL.這個模塊可用于有選擇地允許或拒絕特定用戶或角色對應用程序的任意部分(通常在web.config文件中為目錄指定授權用戶或角色)的訪問權限.
HTTP模塊是在ASP.NET框架默認應用程序配置文件中注冊的,如下:


上面代碼又調用了AuthorizationConfigRule::IsUserAllowed方法,截圖如下:

ASP.NET2.0你仍可以用這樣的機制,但又增加新特性.下面就看看在ASP.NET2.0中是如何實現的吧!
ASP.NET 2.0 實現方式
l 應用程序配置新增屬性
<system.web>
<authentication mode="Forms">
//defaultUrl是ASP.NET2.0版本新增的屬性, 在驗證模型重定向URL時將重定向到的URL.默認值為"default.aspx".
//雖然ASP.NET1.1版本沒有該屬性,但程序中的默認為"default.aspx".還是ASP.NET2.0的配置更為靈活.
<forms loginUrl="logon.aspx" protection="All" path="http://www.49028c.com/" defaultUrl="Index.aspx"></forms>
</authentication>
<authorization>
<deny users="?" />/*匿名用戶*/
</authorization>
</system.web>
l 使用成員資格驗證登錄
l 使用角色管理進行訪問授權
1. 啟用角色管理
<roleManager enabled="true" cacheRolesInCookie="true" />
cacheRolesInCookie
推薦代碼:
if(Membership.ValidateUser(username, password)){
Roles.DeleteCookie();
FormsAuthentication.RedirectFromLoginPage(username, false);
}
2. 配置成員與角色

在安全選項卡內有管理用戶與角色的內容,如下圖:

本示例創建了一個用戶”iori”與一個角色”Admins”,并且指定了該用戶是Admins角色的成員.
另外該工具還會自動創建本地數據庫(如果還沒創建).與它相關的配置在machine.config文件中指定,如下圖所示,你可以更改數據庫的文件名,默認為”aspnetdb.mdf”.

l ASP.NET 2.0 安全模型驗證授權的原理
1. 驗證原理
2. 授權原理
.,RolePrincipal對象.
,代表安全模塊已建立了用戶標識,所以在這個事件中使用用戶標識重新生成RolePrincipal對象.
下面為委托代碼的節選.
......//省略若干代碼
HttpApplication application = (HttpApplication) source;
HttpContext context = application.Context;
if (this._eventHandler != null)
{
RoleManagerEventArgs e = new RoleManagerEventArgs(context);
this._eventHandler(this, e);
if (e.RolesPopulated)
{
/*
void RoleManager_GetRoles(object sender, RoleManagerEventArgs e)
{
if (e.Context.Request.IsAuthenticated)
{
(e.Context.User.Identity.Name), new string[] { "Admins" });
被觸發.