本文分别以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.vevb.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.vevb.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" });
被触发.