|
ASP.NET 安全性的工作原理
網(wǎng)站在安全性方面有一個(gè)常見(jiàn)的要求:特定的頁(yè)面僅允許某些成員或其他經(jīng)過(guò)身份驗(yàn)證的用戶瀏覽.充分利用Forms身份驗(yàn)證是最好的方式.
身份驗(yàn)證
從實(shí)現(xiàn)機(jī)制來(lái)說(shuō)ASP.NET1.1與ASP.NET2.0的安全模型是一致的.首先配置網(wǎng)站為Forms 身份驗(yàn)證模式,之后用戶訪問(wèn)網(wǎng)站的URL,Forms 身份驗(yàn)證系統(tǒng)會(huì)將未經(jīng)身份驗(yàn)證的請(qǐng)求重定向到指定的登錄頁(yè).用戶輸入憑據(jù)(用戶名密碼)并提交該頁(yè).如果驗(yàn)證程序驗(yàn)證用戶的身份合法,則系統(tǒng)會(huì)向客戶端發(fā)出一個(gè)特定 Cookie(.NET1.1不支持無(wú)Cookie模式),它代表用戶的身份驗(yàn)證票據(jù).這樣后續(xù)的請(qǐng)求中,客戶端瀏覽器會(huì)把該Cookie一同發(fā)送致服務(wù)器,如果該Cookie有效則用戶通過(guò)身份驗(yàn)證并允許對(duì)原始請(qǐng)求的資源的訪問(wèn).
授權(quán)
如果用戶的請(qǐng)求被驗(yàn)證通過(guò)了,但是他請(qǐng)求的URL是否允許用戶訪問(wèn)了呢,這就用到了授權(quán).可以通過(guò)應(yīng)用程序配置文件來(lái)進(jìn)行授友也可以在程序中使用代碼來(lái)驗(yàn)證用戶是否有資格訪問(wèn)該資源.如果授權(quán)失敗,則 ASP.NET 將用戶重定向到登錄頁(yè).如果用戶已被授權(quán),則將允許用戶訪問(wèn)受保護(hù)資源.
ASP.NET1.1實(shí)現(xiàn)方式
ASP.NET1.1的實(shí)現(xiàn)方式非常簡(jiǎn)單,不過(guò)我們還是需要手寫一些代碼的,下面我們就一步一步地實(shí)現(xiàn).應(yīng)用程序配置節(jié)的詳細(xì)說(shuō)明請(qǐng)參考MSDN相關(guān)文檔.
l 配置應(yīng)用程序使用 Forms 身份驗(yàn)證,編輯web.config文件
復(fù)制代碼 代碼如下:
<configuration>
<system.web>
<authentication mode="Forms">
<forms name=".ASPXCOOKIEAUTH" loginUrl="Login.ASPx" protection="All" timeout="30" path="/" />
</authentication>
<authorization>
<deny users="?" /> <!―拒絕匿名 -->
</authorization>
......
</system.web>
<location path="Admin"><!―配置授權(quán),只允許擁有Admins角色的用戶訪問(wèn)這個(gè)目錄下的文件(*.aspx)-->
<system.web>
<authorization>
<allow roles="Admins"/><!―雖然下面配置為拒絕所有用戶,但是allow的優(yōu)先級(jí)比deny高.-->
<deny users="*" /><!―拒絕所有用戶 -->
<!―
一個(gè)用戶或角色必須特別指定為拒絕,才能拒絕該用戶或角色對(duì)URL的權(quán)限.如果上面的示例沒(méi)有指定<deny users="*" />元素,則將允許所有通過(guò)身份驗(yàn)證的用戶訪問(wèn)所請(qǐng)求的 URL,而不考慮其所屬的角色.
-->
</authorization>
</system.web>
</location>
</configuration>
l 創(chuàng)建登錄頁(yè)面Login.ASPx
頁(yè)面預(yù)覽如下,代碼詳細(xì)參考本文附件的項(xiàng)目源碼.

創(chuàng)建用戶身份主體
ASP.NET1.1安全模型提供了四種授權(quán)方法,這四種方法都使用HttpContext.User對(duì)象進(jìn)行驗(yàn)證授權(quán).
l 使用應(yīng)用程序配置進(jìn)行授權(quán),只有具有指定角色的用戶才能訪問(wèn)web.config所在的文件夾與子文件夾
<authorization>
<allow roles="Admins"/>
<deny users="?"/>
</authorization>
l 使用PrinciplePermissionAttribute控制對(duì)類和方法的訪問(wèn),只允許角色為Admins的成員才能調(diào)用該方法
[System.Security.Permissions.PrincipalPermission(System.Security.Permissions.SecurityAction.Demand,Role=” Admins”)]
public static bool MethodName()
{
...
}
l 以編程方式使用PrinciplePermission類控制對(duì)代碼塊的訪問(wèn),只允許角色為Admins的成員調(diào)用Demand之后的代碼
public static bool MethodName()
{
System.Security.Permissions.PrincipalPermission perm = new System.Security.Permissions.PrincipalPermission(null, "Admins");
perm.Demand();
...
}
l 使用Iprincipal.IsInRole方法,只允許角色為Admins的成員運(yùn)行if中的代碼,大部分情況我們都使用這種方法判斷用戶是否有權(quán)限.
public static bool MethodName()
{
if (HttpContext.Current.User.IsInRole("Admins"))
{
//some code
}
}
針對(duì)以上的特點(diǎn),程序員必須在合適的地方創(chuàng)建HttpContext.User對(duì)象,以達(dá)到驗(yàn)證模型的要求.開(kāi)發(fā)人員必須編寫HttpApplication:: AuthenticateRequest事件.該事件的發(fā)生代表著用戶己經(jīng)通過(guò)Forms身份驗(yàn)證.
在Global.asax中實(shí)現(xiàn)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);
//獲取在登錄驗(yàn)證時(shí)加入驗(yàn)證票據(jù)的用戶所擁有的角色,但真正開(kāi)發(fā)時(shí)請(qǐng)不這樣做,建議從數(shù)據(jù)庫(kù)中獲取該用戶角色信息.
//因?yàn)镃ookie本身有長(zhǎng)度的限制,并且將用戶角色存儲(chǔ)到客戶端也不是安全的行為.
//大家想想如果Cookie不限制大小,那么它的尺寸大到幾MB或GB時(shí),客戶端與服務(wù)器的每一次通迅,將是怎樣的一種情況了,呵呵.
//這里僅展示如何將角色信息加入到用戶主體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中實(shí)現(xiàn)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;
}
}
其實(shí)FormsAuthenticationModule會(huì)自動(dòng)生成一個(gè)User對(duì)象,只不過(guò)這個(gè)對(duì)象的角色列表為空,它只能是代表通過(guò)身份驗(yàn)證,而不能通過(guò)授權(quán),因?yàn)槲覀兿拗屏四夸浀脑L問(wèn)角色,所以開(kāi)發(fā)人員必須實(shí)現(xiàn)上面代碼,才能滿足我們的要足,如果說(shuō)你的網(wǎng)站僅需要通過(guò)身份驗(yàn)證的話,就可不必實(shí)現(xiàn)這些方法了.
用戶在請(qǐng)求URL時(shí),ASP.NET請(qǐng)求通道會(huì)連續(xù)觸發(fā)一堆的事件,這些事件完成了一系列任務(wù),其中就包括Forms身份驗(yàn)證事件與授權(quán)事件.如下所示:
BeginRequest 請(qǐng)求開(kāi)始事件
AuthenticateRequest 驗(yàn)證通過(guò)事件 (上面兩段代碼就是在這個(gè)事件中被執(zhí)行)
PostAuthenticateRequest 用戶標(biāo)識(shí)己建立時(shí)發(fā)生 ASP.NET 2.0引入的事件,后面會(huì)講到.
AuthorizeRequest 當(dāng)安全模塊已驗(yàn)證用戶授權(quán)時(shí)發(fā)生
....其它事件略;
正是這些事件的壘加促成了ASP.NET框架驗(yàn)證模型的實(shí)現(xiàn), 而且通過(guò)完成上面的幾個(gè)步驟,網(wǎng)站內(nèi)容就己經(jīng)受到授權(quán)機(jī)制的保護(hù)了.
下面讓我們看看ASP.NE安全模型是如何做到授權(quán)的.
l ASP.NET 1.1 安全模型驗(yàn)證授權(quán)的原理
在 ASP.NET 中,有兩種方式限制對(duì)資源訪問(wèn)的權(quán)限:文件授權(quán)與URL 授權(quán),這里我們僅討倫后者.
URL 授權(quán)由 UrlAuthorizationModule 執(zhí)行,它將用戶和角色映射到 ASP.NET 應(yīng)用程序中的 URL.這個(gè)模塊可用于有選擇地允許或拒絕特定用戶或角色對(duì)應(yīng)用程序的任意部分(通常在web.config文件中為目錄指定授權(quán)用戶或角色)的訪問(wèn)權(quán)限.
HTTP模塊是在ASP.NET框架默認(rèn)應(yīng)用程序配置文件中注冊(cè)的,如下:

下面簡(jiǎn)單分析UrlAuthorizationModule的源碼,便可了解驗(yàn)證模型是如何驗(yàn)證在web.config中指定的授權(quán)規(guī)則.
UrlAuthorizationModule在應(yīng)用程序初始化時(shí)向HttpApplication::AuthorizeRequest事件(安全模塊已驗(yàn)證用戶授權(quán)時(shí)發(fā)生)注冊(cè)委托代碼,該代碼內(nèi)部調(diào)用AuthorizationConfig::IsUserAllowed.方法實(shí)現(xiàn)截圖如下:
上面代碼又調(diào)用了AuthorizationConfigRule::IsUserAllowed方法,截圖如下:
由于HttpApplication::AuthorizeRequest事件是在HttpApplication::AuthenticateRequest事件之后執(zhí)行的(請(qǐng)看我提到過(guò)的事件列表), 在前面介紹的AuthenticateRequest事件中我們修改了Context.User對(duì)象,而且加入了角色信息,所以AuthorizeRequest事件在驗(yàn)證用戶的權(quán)限時(shí)發(fā)現(xiàn)Context.User對(duì)象中什么都有,所以它才允許用戶訪問(wèn)請(qǐng)求的資源,否則請(qǐng)求將被返回到指定的頁(yè)面.以上就是ASP.NET1.1的原理,怎么樣你理解了嗎?
ASP.NET2.0你仍可以用這樣的機(jī)制,但又增加新特性.下面就看看在ASP.NET2.0中是如何實(shí)現(xiàn)的吧!
ASP.NET2.0的實(shí)現(xiàn)方式與ASP.NET1.1實(shí)現(xiàn)方式大同小異,同樣支持前一版本的安全模型.不過(guò)又新增了成員資格與角色管理授權(quán)模型.這里就介紹ASP.NET2.0新增的內(nèi)容.
l 應(yīng)用程序配置新增屬性
<system.web>
<authentication mode="Forms">
//defaultUrl是ASP.NET2.0版本新增的屬性, 在驗(yàn)證模型重定向URL時(shí)將重定向到的URL.默認(rèn)值為"default.ASPx".
//雖然ASP.NET1.1版本沒(méi)有該屬性,但程序中的默認(rèn)為"default.ASPx".還是ASP.NET2.0的配置更為靈活.
<forms loginUrl="logon.ASPx" protection="All" name=".ASPXFORMSAUTH" path="/" defaultUrl="Index.ASPx"></forms>
</authentication>
<authorization>
<deny users="?" />/*匿名用戶*/
</authorization>
</system.web>
l 使用成員資格驗(yàn)證登錄
ASP.NET 成員資格主要用于ASP.NET Forms 身份驗(yàn)證,配合ASP.NET2.0登錄控件可以不用寫任何代碼就能實(shí)現(xiàn)Forms身份驗(yàn)證.
首先創(chuàng)建登錄頁(yè)Login.ASPx,將登錄控件托入到窗體中即可,不用寫任何登錄事件代碼,相比ASP.NET1.1節(jié)省時(shí)間不是一點(diǎn)半點(diǎn).
l 使用角色管理進(jìn)行訪問(wèn)授權(quán)
角色管理可以幫助您管理授權(quán),使您能夠指定應(yīng)用程序中的用戶可訪問(wèn)的資源.角色管理可讓您通過(guò)將用戶分配到相應(yīng)角色來(lái)對(duì)其進(jìn)行分組,從而更容易控制訪問(wèn)權(quán)限.
1. 啟用角色管理
要啟用該功能,修改web.config文件在<system.web>配置節(jié)點(diǎn)內(nèi)增加子節(jié)點(diǎn)如下:
<roleManager enabled="true" cacheRolesInCookie="true" />
cacheRolesInCookie屬性代表是否緩存角色信息,這樣不用每次都從數(shù)據(jù)庫(kù)中獲取角色,以提高應(yīng)用程序的性能.
但是將角色放在Cookie里總是風(fēng)險(xiǎn)的,它可能被篡改,然后用它訪問(wèn)未被授權(quán)的資源.
不過(guò)可以在用戶每次登錄時(shí),先使用Role API的DeleteCookie方法刪除這個(gè)緩存Cookie, 這樣可使風(fēng)險(xiǎn)小一點(diǎn).
推薦代碼:
if(Membership.ValidateUser(username, password)){
Roles.DeleteCookie();
FormsAuthentication.RedirectFromLoginPage(username, false);
}
特別提示:應(yīng)用程序配置中當(dāng)角色管理可用并且提供者程序?yàn)?/SPAN>ASPNETSqlProvider時(shí), SqlRoleProvider的GetRolesForUser方法會(huì)調(diào)用System.Web.DataAccess.SqlConnectionHelper類的私有靜態(tài)方法EnsureSqlExpressDBFile創(chuàng)建一個(gè)空的ASPNETdb.mdf本地?cái)?shù)據(jù)庫(kù),該數(shù)據(jù)庫(kù)包含成員資格與角色管理,所需要的表結(jié)構(gòu)等信息.
2. 配置成員與角色
使用VS2008自帶的配置工具設(shè)定成員與角色是最簡(jiǎn)單不過(guò)的了,點(diǎn)擊菜單欄中的[項(xiàng)目],下拉菜單的最后一項(xiàng)[ASP.NET配置],在彈出窗體中設(shè)置成員與角色關(guān)系.如下圖展示:
在安全選項(xiàng)卡內(nèi)有管理用戶與角色的內(nèi)容,如下圖:
本示例創(chuàng)建了一個(gè)用戶”iori”與一個(gè)角色”Admins”,并且指定了該用戶是Admins角色的成員.
另外該工具還會(huì)自動(dòng)創(chuàng)建本地?cái)?shù)據(jù)庫(kù)(如果還沒(méi)創(chuàng)建).與它相關(guān)的配置在machine.config文件中指定,如下圖所示,你可以更改數(shù)據(jù)庫(kù)的文件名,默認(rèn)為”ASPNETdb.mdf”.
好了,通過(guò)完成上面的幾個(gè)步驟,網(wǎng)站內(nèi)容就己經(jīng)受到授權(quán)機(jī)制的保護(hù)了,可以用剛剛添加的用戶試試登錄吧.
相比上一版本,ASP.NET2.0在Forms身份驗(yàn)證里為開(kāi)發(fā)人員節(jié)省很多時(shí)間,幾乎不用開(kāi)發(fā)人員寫任何代碼,方便了許多,下面讓我們一探究竟.
l ASP.NET 2.0 安全模型驗(yàn)證授權(quán)的原理
1. 驗(yàn)證原理
在.NET1.1中開(kāi)發(fā)人員必須為登錄頁(yè)編寫登錄事件,用來(lái)驗(yàn)證用戶輸入的用戶名與密碼是否有效,而ASP.NET 2.0中引入了成員資格提供程序與標(biāo)準(zhǔn)登錄服務(wù)器控件,它們隱式使用Forms 身份驗(yàn)證,登錄控件己經(jīng)包含了驗(yàn)證用戶名的程序邏輯,也就是說(shuō)登錄控件會(huì)把用戶輸入的用戶名與密碼自動(dòng)與成員資格數(shù)據(jù)庫(kù)中的用戶進(jìn)行匹配,如果成功匹配就將特定Cookie寫入客戶端.
2. 授權(quán)原理
還是拿UrlAuthorizationModule說(shuō)事兒, 如果不啟用角色管理,實(shí)現(xiàn)方式與ASP.NET1.1差不多,不過(guò)由于ASP.NET2.0加入了角色管理模型,角色管理模型使用兩個(gè)類: RolePrincipal與RoleManagerModule來(lái)實(shí)現(xiàn)角色授權(quán).如果應(yīng)用程序配置的角色管理可用時(shí),這兩個(gè)新對(duì)象將被應(yīng)用到ASPx頁(yè)面的生命周期中, 由于RoleManagerModule 被初始化時(shí)會(huì)向HttpApplication對(duì)象的事件PostAuthenticateRequest加載委托代碼,該代碼會(huì)將app.Context.User對(duì)象包裝成RolePrincipal對(duì)象.
PostAuthenticateRequest事件是在ASP.NET 2.0中加入的,該事件發(fā)生在AuthenticateRequest事件之后,代表安全模塊已建立了用戶標(biāo)識(shí),所以在這個(gè)事件中使用用戶標(biāo)識(shí)重新生成RolePrincipal對(duì)象.
下面為委托代碼的節(jié)選.
......//省略若干代碼
HttpApplication application = (HttpApplication) source;
HttpContext context = application.Context;
if (this._eventHandler != null)
{
RoleManagerEventArgs e = new RoleManagerEventArgs(context);
this._eventHandler(this, e);
if (e.RolesPopulated)
{
//判斷開(kāi)發(fā)人員是否在Global.asax中寫了事件處理程序,如下顯示的代碼.
/*
//這里演示如何在Global.asax中自定義角色
void RoleManager_GetRoles(object sender, RoleManagerEventArgs e)
{
if (e.Context.Request.IsAuthenticated)
{
e.Context.User = new GenericPrincipal(new GenericIdentity(e.Context.User.Identity.Name), new string[] { "Admins" });
e.RolesPopulated = true;
}
}
*/
//如果e.RolesPopulated為真,代表開(kāi)發(fā)人員自己創(chuàng)建了角色信息,
//RoleManagerModule就不會(huì) 生成RolePrincipal 對(duì)象了.
return;
}
}
......
if (!(context.User is RolePrincipal))
{
context.User = new RolePrincipal(context.User.Identity);
}
Thread.CurrentPrincipal = context.User;
注意:我們并沒(méi)有像ASP.NET1.1中那樣在AuthenticateRequest事件中生成User對(duì)象.但是User對(duì)象會(huì)在FormsAuthenticationModuleHTTP模塊中使用Forms身份驗(yàn)證的特定Cookie重新被包裝成一個(gè)GenericPrincipal對(duì)象(角色為空).
在說(shuō)明RolePrincipal對(duì)象有什么用之前,需要了解這個(gè)對(duì)象是何時(shí)被用到的.
在UrlAuthorizationModule被初始化時(shí)中向HttpApplication對(duì)象注冊(cè)的事件AuthorizeRequest被觸發(fā).
在這個(gè)事件中會(huì)調(diào)用RolePrincipal對(duì)象(就是Context.User)的方法IsInRole, IsInRole方法會(huì)自動(dòng)查找角色提供程序(本示例使用默認(rèn)提供程序ASPNETSqlProvider,數(shù)據(jù)庫(kù)為前面自動(dòng)生成的ASPNETDB.MDF),并驗(yàn)證用戶角色,代碼截圖如下:

而IsUserAllowed方法最終會(huì)調(diào)用RolePrincipal對(duì)角的IsInRole方法來(lái)判斷當(dāng)前用戶是否擁有某角色,方法截圖如下:
以上這些便是ASP.NET2.0成員資格與角色管理實(shí)現(xiàn)Forms身份驗(yàn)證的原理與實(shí)現(xiàn),不過(guò)默認(rèn)的成員資格與角色數(shù)據(jù)庫(kù)的字段一般并不能滿足具體項(xiàng)目的需要.好在ASP.NET 2.0中提供了可擴(kuò)展提拱程序模型,開(kāi)發(fā)人員可以定制成員資格提供程序與角色管理模型.
結(jié)束語(yǔ)
網(wǎng)站應(yīng)用程序的身份驗(yàn)證和授權(quán)方法是一項(xiàng)具有挑戰(zhàn)性的任務(wù),而Forms身份驗(yàn)證在網(wǎng)站建設(shè)中提供了重要的安全性優(yōu)勢(shì),通過(guò)提供用戶配置文件以及對(duì)角色的支持, 簡(jiǎn)化了程序員通常需要編寫大量代碼才能完成的工作.如果讀者還有什么問(wèn)題或者對(duì)以上描述有不同的見(jiàn)解,歡迎與我聯(lián)系互相交流!
AspNet技術(shù):ASP.NET Internet安全Forms身份驗(yàn)證方法,轉(zhuǎn)載需保留來(lái)源!
鄭重聲明:本文版權(quán)歸原作者所有,轉(zhuǎn)載文章僅為傳播更多信息之目的,如作者信息標(biāo)記有誤,請(qǐng)第一時(shí)間聯(lián)系我們修改或刪除,多謝。