本文分別以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 name=".ASPXCOOKIEAUTH" loginUrl="Login.aspx" protection="All" timeout="30" path="/" />
</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框架默認應用程序配置文件中注冊的,如下:
下面簡單分析UrlAuthorizationModule的源碼,便可了解驗證模型是如何驗證在web.config中指定的授權規則.
UrlAuthorizationModule在應用程序初始化時向HttpApplication::AuthorizeRequest事件(安全模塊已驗證用戶授權時發生)注冊委托代碼,該代碼內部調用AuthorizationConfig::IsUserAllowed.方法實現截圖如下:
上面代碼又調用了AuthorizationConfigRule::IsUserAllowed方法,截圖如下:
由於HttpApplication::AuthorizeRequest事件是在HttpApplication::AuthenticateRequest事件之後執行的(請看我提到過的事件列表), 在前面介紹的AuthenticateRequest事件中我們修改了Context.User對象,而且加入了角色信息,所以AuthorizeRequest事件在驗證用戶的權限時發現Context.User對象中什麼都有,所以它才允許用戶訪問請求的資源,否則請求將被返回到指定的頁面.以上就是ASP.NET1.1的原理,怎麼樣你理解了嗎?
ASP.NET2.0你仍可以用這樣的機制,但又增加新特性.下面就看看在ASP.NET2.0中是如何實現的吧!
ASP.NET 2.0 實現方式
ASP.NET2.0的實現方式與ASP.NET1.1實現方式大同小異,同樣支持前一版本的安全模型.不過又新增了成員資格與角色管理授權模型.這裡就介紹ASP.NET2.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" name=".ASPXFORMSAUTH" path="/" defaultUrl="Index.aspx"></forms>
</authentication>
<authorization>
<deny users="?" />/*匿名用戶*/
</authorization>
</system.web>
l 使用成員資格驗證登錄
ASP.NET 成員資格主要用於ASP.NET Forms 身份驗證,配合ASP.NET2.0登錄控件可以不用寫任何代碼就能實現Forms身份驗證.
首先創建登錄頁Login.aspx,將登錄控件托入到窗體中即可,不用寫任何登錄事件代碼,相比ASP.NET1.1節省時間不是一點半點.
l 使用角色管理進行訪問授權
角色管理可以幫助您管理授權,使您能夠指定應用程序中的用戶可訪問的資源.角色管理可讓您通過將用戶分配到相應角色來對其進行分組,從而更容易控制訪問權限.
1. 啟用角色管理
要啟用該功能,修改web.config文件在<system.web>配置節點內增加子節點如下:
<roleManager enabled="true" cacheRolesInCookie="true" />
cacheRolesInCookie屬性代表是否緩存角色信息,這樣不用每次都從數據庫中獲取角色,以提高應用程序的性能.
但是將角色放在Cookie裡總是風險的,它可能被篡改,然後用它訪問未被授權的資源.
不過可以在用戶每次登錄時,先使用Role API的DeleteCookie方法刪除這個緩存Cookie, 這樣可使風險小一點.
推薦代碼:
if(Membership.ValidateUser(username, password)){
Roles.DeleteCookie();
FormsAuthentication.RedirectFromLoginPage(username, false);
}
特別提示:應用程序配置中當角色管理可用並且提供者程序為AspNetSqlProvider時, SqlRoleProvider的GetRolesForUser方法會調用System.Web.DataAccess.SqlConnectionHelper類的私有靜態方法EnsureSqlExpressDBFile創建一個空的aspnetdb.mdf本地數據庫,該數據庫包含成員資格與角色管理,所需要的表結構等信息.
2. 配置成員與角色
使用VS2008自帶的配置工具設定成員與角色是最簡單不過的了,點擊菜單欄中的[項目],下拉菜單的最後一項[ASP.NET配置],在彈出窗體中設置成員與角色關系.如下圖展示:
在安全選項卡內有管理用戶與角色的內容,如下圖:
本示例創建了一個用戶”iori”與一個角色”Admins”,並且指定了該用戶是Admins角色的成員.
另外該工具還會自動創建本地數據庫(如果還沒創建).與它相關的配置在machine.config文件中指定,如下圖所示,你可以更改數據庫的文件名,默認為”aspnetdb.mdf”.
好了,通過完成上面的幾個步驟,網站內容就己經受到授權機制的保護了,可以用剛剛添加的用戶試試登錄吧.
相比上一版本,ASP.NET2.0在Forms身份驗證裡為開發人員節省很多時間,幾乎不用開發人員寫任何代碼,方便了許多,下面讓我們一探究竟.
l ASP.NET 2.0 安全模型驗證授權的原理
1. 驗證原理
在.net1.1中開發人員必須為登錄頁編寫登錄事件,用來驗證用戶輸入的用戶名與密碼是否有效,而ASP.NET 2.0中引入了成員資格提供程序與標准登錄服務器控件,它們隱式使用Forms 身份驗證,登錄控件己經包含了驗證用戶名的程序邏輯,也就是說登錄控件會把用戶輸入的用戶名與密碼自動與成員資格數據庫中的用戶進行匹配,如果成功匹配就將特定Cookie寫入客戶端.
2. 授權原理
還是拿UrlAuthorizationModule說事兒, 如果不啟用角色管理,實現方式與ASP.NET1.1差不多,不過由於ASP.NET2.0加入了角色管理模型,角色管理模型使用兩個類: RolePrincipal與RoleManagerModule來實現角色授權.如果應用程序配置的角色管理可用時,這兩個新對象將被應用到aspx頁面的生命周期中, 由於RoleManagerModule 被初始化時會向HttpApplication對象的事件PostAuthenticateRequest加載委托代碼,該代碼會將app.Context.User對象包裝成RolePrincipal對象.
PostAuthenticateRequest事件是在ASP.NET 2.0中加入的,該事件發生在AuthenticateRequest事件之後,代表安全模塊已建立了用戶標識,所以在這個事件中使用用戶標識重新生成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)
{
//判斷開發人員是否在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為真,代表開發人員自己創建了角色信息,
//RoleManagerModule就不會 生成RolePrincipal 對象了.
return;
}
}
......
if (!(context.User is RolePrincipal))
{
context.User = new RolePrincipal(context.User.Identity);
}
Thread.CurrentPrincipal = context.User;
注意:我們並沒有像ASP.NET1.1中那樣在AuthenticateRequest事件中生成User對象.但是User對象會在FormsAuthenticationModuleHTTP模塊中使用Forms身份驗證的特定Cookie重新被包裝成一個GenericPrincipal對象(角色為空).
在說明RolePrincipal對象有什麼用之前,需要了解這個對象是何時被用到的.
在UrlAuthorizationModule被初始化時中向HttpApplication對象注冊的事件AuthorizeRequest被觸發.
在這個事件中會調用RolePrincipal對象(就是Context.User)的方法IsInRole, IsInRole方法會自動查找角色提供程序(本示例使用默認提供程序AspNetSqlProvider,數據庫為前面自動生成的ASPNETDB.MDF),並驗證用戶角色,代碼截圖如下:
而IsUserAllowed方法最終會調用RolePrincipal對角的IsInRole方法來判斷當前用戶是否擁有某角色,方法截圖如下:
以上這些便是ASP.NET2.0成員資格與角色管理實現Forms身份驗證的原理與實現,不過默認的成員資格與角色數據庫的字段一般並不能滿足具體項目的需要.好在ASP.NET 2.0中提供了可擴展提拱程序模型,開發人員可以定制成員資格提供程序與角色管理模型.
結束語
網站應用程序的身份驗證和授權方法是一項具有挑戰性的任務,而Forms身份驗證在網站建設中提供了重要的安全性優勢,通過提供用戶配置文件以及對角色的支持, 簡化了程序員通常需要編寫大量代碼才能完成的工作.如果讀者還有什麼問題或者對以上描述有不同的見解,歡迎與我聯系互相交流!