Asp.net的身份验证有有三种,分别是"Windows | Forms | Passport",其中又以Forms验证用的最多,也最灵活。
Forms 验证方式对基于用户的验证授权提供了很好的支持,可以通过一个登录页面验证用户的身份,将此用户的身份发回到客户端的Cookie,之后此用户再访问这个 web应用就会连同这个身份Cookie一起发送到服务端。服务端上的授权设置就可以根据不同目录对不同用户的访问授权进行控制了。
问题来了,在实际是用中我们往往需要的是基于角色,或者说基于用户组的验证和授权。对一个网站来说,一般的验证授权的模式应该是这样的:根据实际需求把用户分成不同的身份,就是角色,或者说是用户组,验证过程不但要验证这个用户本身的身份,还要验证它是属于哪个角色的。而访问授权是根据角色来设置的,某些角色可以访问哪些资源,不可以访问哪些资源等等。要是基于用户来授权访问将会是个很不实际的做法,用户有很多,还可能随时的增减,不可能在配置文件中随时的为不断增加的新用户去增加访问授权的。
下面大概的看一下Forms的过程。
Forms身份验证基本原理:
一 身份验证
要采用Forms身份验证,先要在应用程序根目录中的Web.config中做相应的设置:
<authentication mode="forms">
<forms name=".ASPXAUTH " slidingExpiration="true" loginUrl="/login.aspx" timeout="30" path= "/" domain=".abc.com">
</forms>
</authentication>
其中<authentication mode= "forms"> 表示本应用程序采用Forms验证方式。
1. <forms>标签中的name表示指定要用于身份验证的 HTTP Cookie。默认情况下,name 的值是 .ASPXAUTH。采用此种方式验证用户后,以此用户的信息建立一个FormsAuthenticationTicket类型的身份验证票,再加密序列化为一个字符串,最后将这个字符串写到客户端的name指定名字的Cookie中.一旦这个Cookie写到客户端后,此用户再次访问这个web应用时会将连同Cookie一起发送到服务端,服务端将会知道此用户是已经验证过的.
再看一下身份验证票都包含哪些信息呢,我们看一下FormsAuthenticationTicket类:
CookiePath: 返回发出 Cookie 的路径。注意,窗体的路径设置为 /。由于窗体区分大小写,这是为了防止站点中的 URL 的大小写不一致而采取的一种保护措施。这在刷新 Cookie 时使用
Expiration: 获取 Cookie 过期的日期/时间。
IsPersistent: 如果已发出持久的 Cookie,则返回 true。否则,身份验证 Cookie 将限制在浏览器生命周期范围内。
IssueDate: 获取最初发出 Cookie 的日期/时间。
Name: 获取与身份验证 Cookie 关联的用户名。
UserData :获取存储在 Cookie 中的应用程序定义字符串。
Version: 返回字节版本号供将来使用。
2. <forms>标签中的loginUrl指定如果没有找到任何有效的身份验证 Cookie,为登录将请求重定向到的 URL。默认值为 default.aspx。loginUrl指定的页面就是用来验证用户身份的,一般此页面提供用户输入用户名和密码,用户提交后由程序来根据自己的需要来验证用户的合法性(大多情况是将用户输入信息同数据库中的用户表进行比较),如果验证用户有效,则生成同此用户对应的身份验证票,写到客户端的 Cookie,最后将浏览器重定向到用户初试请求的页面.一般是用FormsAuthentication.RedirectFromLoginPage 方法来完成生成身份验证票,写回客户端,浏览器重定向等一系列的动作.
public static void RedirectFromLoginPage( string userName, bool createPersistentCookie, string strCookiePath );
其中:
userName: 就是此用户的标示,用来标志此用户的唯一标示,不一定要映射到用户账户名称.
createPersistentCookie: 标示是否发出持久的 Cookie。
若不是持久Cookie,Cookie的有效期Expiration属性有当前时间加上web.config中timeout的时间,每次请求页面时,在验证身份过程中,会判断是否过了有效期的一半,要是的话更新一次cookie的有效期;若是持久cookie,Expiration属性无意义,这时身份验证票的有效期有cookie的Expires决定,RedirectFromLoginPage方法给Expires属性设定的是50年有效期。
strCookiePath: 标示将生成的Cookie的写到客户端的路径,身份验证票中保存这个路径是在刷新身份验证票Cookie时使用(这也是生成Cookie的Path),若没有strCookiePath 参数,则使用web.config中 path属性的设置。
这里可以看到,此方法参数只有三个,而身份验证票的属性有七个,不足的四个参数是这么来的:
IssueDate: Cookie发出时间由当前时间得出,
Expiration:过期时间由当前时间和下面要说的<forms>标签中timeout参数算出。此参数对非持久性cookie有意义。
UserData: 这个属性可以用应用程序写入一些用户定义的数据,此方法没有用到这个属性,只是简单的将此属性置为空字符串,请注意此属性,在后面我们将要使用到这个属性。
Version: 版本号由系统自动提供.
RedirectFromLoginPage 方法生成生成身份验证票后,会调用FormsAuthentication.Encrypt 方法,将身份验证票加密为字符串,这个字符串将会是以.ASPXAUTH为名字的一个Cookie的值。这个Cookie的其它属性的生成:Domain,Path属性为确省值,Expires视createPersistentCookie参数而定,若是持久 cookie,Expires设为50年以后过期;若是非持久cookie,Expires属性不设置。
生成身份验证Cookie后,将此Cookie加入到Response.Cookies中,等待发送到客户端。
最后RedirectFromLoginPage方法调用FormsAuthentication.GetRedirectUrl 方法获取到用户原先请求的页面,重定向到这个页面。
3. <forms>标签中的timeout和path,是提供了身份验证票写入到Cookie过期时间和默认路径。
以上就是基于Forms身份验证的过程,它完成了对用户身份的确认。下面介绍基于Forms身份验证的访问授权。
二 访问授权
验证了身份,是要使用这个身份,根据不同的身份我们可以进行不同的操作,处理,最常见的就是对不同的身份进行不同的授权,Forms验证就提供这样的功能。Forms授权是基于目录的,可以针对某个目录来设置访问权限,比如,这些用户可以访问这个目录,那些用户不能访问这个目录。
同样,授权设置是在你要控制的那个目录下的web.config文件中来设置:
<authorization>
<allow users="comma-separated list of users"
roles="comma-separated list of roles"
verbs="comma-separated list of verbs" />
<deny users="comma-separated list of users"
roles="comma-separated list of roles"
verbs="comma-separated list of verbs" />
</authorization>
<allow>标签表示允许访问,其中的属性
1. users:一个逗号分隔的用户名列表,这些用户名已被授予对资源的访问权限。问号 (?) 允许匿名用户;星号 (*) 允许所有用户。
2. roles:一个逗号分隔的角色列表,这些角色已被授予对资源的访问权限。
3. verbs:一个逗号分隔的 HTTP 传输方法列表,这些 HTTP 传输方法已被授予对资源的访问权限。注册到 ASP.NET 的谓词为 GET、HEAD、POST 和 DEBUG。
<deny>标签表示不允许访问。其中的属性同上面的。
在运行时,授权模块迭代通过 <allow> 和 <deny> 标记,直到它找到适合特定用户的第一个访问规则。然后,它根据找到的第一项访问规则是 <allow> 还是 <deny> 规则来允许或拒绝对 URL 资源的访问。Machine.config 文件中的默认身份验证规则是 <allow users="*"/>,因此除非另行配置,否则在默认情况下会允许访问。
那么这些user 和roles又是如何得到的呢?下面看一下授权的详细过程:
1. 一旦一个用户访问这个网站,就行登录确认了身份,身份验证票的cookie也写到了客户端。之后,这个用户再次申请这个web的页面,身份验证票的 cookie就会发送到服务端。在服务端,asp.net为每一个http请求都分配一个HttpApplication对象来处理这个请求,在 HttpApplication.AuthenticateRequest事件后,安全模块已建立用户标识,就是此用户的身份在web端已经建立起来,这个身份完全是由客户端发送回来的身份验证票的cookie建立的。
2. 用户身份在HttpContext.User 属性中,在页面中可以通过Page.Context 来获取同这个页面相关的HttpContext对象。对于Forms验证,HttpContext.User属性是一个GenericPrincipal 类型的对象,GenericPrincipal只有一个公开的属性Identity,有个私有的m_role属性,是string[]类型,存放此用户是属于哪些role的数组,还有一个公开的方法IsInRole(string role),来判断此用户是否属于某个角色。
由于身份验证票的cookie中根本没有提供role这个属性,就是说Forms身份验证票没有提供此用户的role信息,所以,对于Forms验证,在服务端得到的GenericPrincipal 用户对象的m_role属性永远是空的。
3. GenericPrincipal. Identity 属性是一个FormsIdentity类型的对象,这个对象有个Name属性,就是此用户的标示,访问授权就是将此属性做为user来进行授权验证的。 FormsIdentity还有一个属性,就是Ticket属性,此属性是身份验证票FormsAuthenticationTicket类型,就是之前服务器写到客户端的身份验证票。
服务器在获取到身份验证票FormsAuthenticationTicket对象后,查看这个身份验证票是不是非持久的身份验证,是的话要根据web.config中timeout属性设置的有效期来更新这个身份验证票的cookie(为避免危及性能,在经过了超过一半的指定时间后更新该 Cookie。这可能导致精确性上的损失。持久性 Cookie 不超时。)
4. 在HttpApplication.ResolveRequestCache事件之前,asp.net开始取得用户请求的页面,建立 HttpHandler控制点。这就意味着,在HttpApplication.ResolveRequestCache事件要对用户访问权限就行验证,看此用户或角色是否有权限访问这个页面,之后在这个请求的生命周期内再改变此用户的身份或角色就没有意义了。
以上是Forms验证的全过程,可以看出,这个Forms验证是基于用户的,没有为角色的验证提供直接支持。身份验证票 FormsAuthenticationTicket 中的Name属性是用户标示,其实还有一个属性UserData,这个属性可以由应用程序来写入自定义的一些数据,我们可以利用这个字段来存放role的信息,从而达到基于角色验证的目的。
一 身份验证
在web.config的<authentication>的设置还是一样:/login.aspx 验证用户合法性页面中,在验证了用户的合法性后,还要有个取得此用户属于哪些role的过程,这个看各个应用的本身如何设计的了,一般是在数据库中会有个 use_role表,可以从数据库中获得此用户属于哪些role,在此不深究如何去获取用户对应的role,最后肯定能够获得的此用户对应的所有的 role用逗号分割的一个字符串。
在上面的非基于角色的方法中,我们用了 FormsAuthentication.RedirectFromLoginPage 方法来完成生成身份验证票,写回客户端,浏览器重定向等一系列的动作。这个方法会用一些确省的设置来完成一系列的动作,在基于角色的验证中我们不能用这一个方法来实现,要分步的做,以便将一些定制的设置加进来:
<authentication mode="forms">
<forms name=".ASPXAUTH "domain=".abc.com" loginUrl="/login.aspx" timeout="30" path= "/">
</forms>
</authentication>
1. 首先要根据用户标示,和用户属于的角色的字符串来创建身份验证票
public FormsAuthenticationTicket(
int version, //设为1
string name, //用户标示
DateTime issueDate, //Cookie 的发出时间, 设置为 DateTime.Now
DateTime expiration, //过期时间
bool isPersistent, //是否持久性(根据需要设置,若是设置为持久性,(在发出cookie时,cookie的Expires设置一定要设置)
string userData, //这里用上面准备好的用逗号分割的role字符串
string cookiePath // 设为"/",这要同发出cookie的路径一致,因为刷新cookie要用这个路径
);
FormsAuthenticationTicket Ticket = new FormsAuthenticationTicket (1,"kent",DateTime.Now, DateTime.Now.AddMinutes(30), false,UserRoles,"/") ;
2. 生成身份验证票的Cookie
2.1 将身份验证票加密序列化成一个字符串
string HashTicket = FormsAuthentication.Encrypt (Ticket) ;
2.2 生成cookie
HttpCookie UserCookie = new HttpCookie(FormsAuthentication.FormsCookieName, HashTicket) ;
FormsAuthentication.FormsCookieName 是用来获取web.config中设置的身份验证cookie的名字,缺省为" .ASPXAUTH".
若身份验证票中的isPersistent属性设置为持久类,则这个cookie的Expires属性一定要设置,这样这个cookie才会被做为持久cookie保存到客户端的cookie文件中.
3. 将身份验证票Cookie输出到客户端
Response.Cookies.Add(UserCookie)
通过Response.Cookies.Add(UserCookie) 将身份验证票Cookie附加到输出的cookie集合中,发送到客户端.
4. 重定向到用户申请的初试页面.
验证部分代码(这部分代码是在login.aspx页面上点击了登录按钮事件处理代码):
二 基于角色访问授权
private void Buttonlogin_Click(object sender, System.EventArgs e)
{
string user = TextBoxUser.Text; //读取用户名
string password = TextBoxPassword.Text; //读取密码
if(Confirm(user,password) == true) //confirm方法用来验证用户合法性的
{
string userRoles = UserToRole(user); //调用UserToRole方法来获取role字符串
FormsAuthenticationTicket Ticket = new FormsAuthenticationTicket (1,user,DateTime.Now, DateTime.Now.AddMinutes(30), false,userRoles,"/") ; //建立身份验证票对象
string HashTicket = FormsAuthentication.Encrypt (Ticket) ; //加密序列化验证票为字符串
HttpCookie UserCookie = new HttpCookie(FormsAuthentication.FormsCookieName, HashTicket) ;
//生成Cookie
FormsAuthentication.SetAuthCookie(user, false); //输出Cookie
Response.Redirect(FormsAuthentication.GetRedirectUrl(user, false)); // 重定向到用户申请的初始页面
}
else
{
// 用户身份未被确认时的代码
}
}
//此方法用来验证用户合法性的
private bool Confirm(string user,string password)
{
//相应的代码
}
//此方法用来获得的用户对应的所有的role用逗号分割的一个字符串
private string UserToRole(string user)
{
//相应的代码
}
这里我们要做的是,将客户端保存的身份验证票中UserData中保存的表示角色的信息恢复到在服务端表示用户身份的GenericPrincipal对象中(记住,原来的验证过程中, GenericPrincipal对象只包含了用户信息,没有包含role信息)
一个Http请求的过程中,HttpApplication.AuthenticateRequest事件表示安全模块已建立用户标识,就是此用户的身份在web端已经建立起来, 在这个事件之后我们就可以获取用户身份信息了.
在 HttpApplication.ResolveRequestCache事件之前,asp.net开始取得用户请求的页面,建立HttpHandler 控制点,这时就已经要验证用户的权限了,所以恢复用户角色的工作只能在HttpApplication.AuthenticateRequest事件和 HttpApplication.ResolveRequestCache事件之间的过程中做.
我们选择Application_AuthorizeRequest事件中做这个工作,可以在global.asax文件中处理HttpApplication的所有的事件,代码如下:
protected void Application_AuthorizeRequest(object sender, System.EventArgs e)
{
HttpApplication App = (HttpApplication) sender;
HttpContext Ctx = App.Context ; //获取本次Http请求相关的HttpContext对象
if (Ctx.Request.IsAuthenticated == true) //验证过的用户才进行role的处理
{
FormsIdentity Id = (FormsIdentity)Ctx.User.Identity ;
FormsAuthenticationTicket Ticket = Id.Ticket ; //取得身份验证票
string[] Roles = Ticket.UserData.Split (',') ; //将身份验证票中的role数据转成字符串数组
Ctx.User = new GenericPrincipal (Id, Roles) ; //将原有的Identity加上角色信息新建一个GenericPrincipal表示当前用户,这样当前用户就拥有了role信息
}
}
访问者同时具有了user和role信息,就可以据此在web.config中用role来控制用户的访问权限了.
1、基于Windows的身份验证将<system.web>元素下的<authentication> 设置为 Windows;基于Forms的身份验证将<system.web>元素下的<authentication> 设置为 Forms。
2、基于Forms的身份验证时,设置<system.web>元素下的<authentication> 元素的 <forms> 子元素,示例如下,仅为说明
<authentication mode="Forms">
<forms name=".VS2005_Form" loginUrl="~/Security/Login.aspx" defaultUrl="~/Default.aspx"
protection="All" timeout="30" path="/" requireSSL="false"
slidingExpiration="true" enableCrossAppRedirects="false"
cookieless="UseDeviceProfile">
</forms>
</authentication>
<forms>元素的属性说明如下
1) cookieless - 身份验证可以将 Forms 身份验证票存储在 Cookie 中也可以以无 Cookie 的表示形式存储在 URL 上。有效值如下:
·UseDeviceProfile - 默认值表示 ASP.NET 根据预先计算得到的浏览器配置文件来确定存储票证的位置。
·AutoDetect - 选项使 ASP.NET 动态确定浏览器是否支持 Cookie。
·UseUri - 强制实施无 Cookie 票证
·UseCookies - 强制实施有 Cookie 票证。
2) defaultUrl - 指定在成功登录后,请求将重定向到的默认 URL。
3) domain - 指定包含 Forms 身份验证票的 HttpCookie 的 Domain 属性的值。显式设置此属性可使应用程序共享同一个 Cookie,前提是这些应用程序共享某个 DNS 命名空间的一个公共部分(例如,如果 domain 属性设置为“cnblogs.com”,则 webabcd.cnblogs.com 和 dudu.cnblogs.com可以共享一个 Cookie)。
4) enableCrossAppRedirects - Forms 身份验证允许以查询字符串变量或窗体 POST 变量的形式在应用程序之间传递 Forms身份验证票。将此属性设置为 true 可使 FormsAuthenticationModule 能够从查询字符串或窗体 POST 变量提取票证。
5) loginUrl - 指定未经身份验证的用户的请求将被重定向到的 URL。该 URL 可以在同一台计算机上或在远程计算机上。如果是在远程计算机上,则两台计算机上 machineKey 配置元素中的 decryptionkey 和 validationKey 属性都需要使用相同的值。
6) name - 用于身份验证的 HTTP Cookie 的名称。注意,如果多个应用程序需要在一台计算机上使用基于窗体的身份验证服务,并且每个应用程序都希望由应用程序隔离 Forms 身份验证 Cookie,则每个应用程序都应配置一个唯一的 Cookie 值。为避免在 URL 中产生依赖项,在设置身份验证 Cookie 时,ASP.NET 还使用“/”作为 Path 值,以便将这些 Cookie 发送回站点上的每个应用程序。
7) path - 用于发出的 Cookie 的路径。默认值为“/”,以避免路径中大小写不匹配的造成的困难,因为在返回 Cookie 时,浏览器是严格区分大小写的。共享服务器环境中的应用程序应使用此指令来维护专用 Cookie。(它们还可以使用 API 在运行时指定路径来发出 Cookie。)
8) protection - 用于保护 Cookie 数据的方法。有效值如下:
·All - 同时使用数据验证和加密来保护 Cookie。所配置的数据验证算法是基于 <machinekey> 元素的。如果密钥足够长(48 个字符),默认情况下将使用 AES 进行加密。All 是默认(和建议)值。
·None - 用于仅将 Cookie 用于个性化设置并且安全性要求不高的站点。加密和验证都可以被禁用。尽管以此方式使用 Cookie 需谨慎,但对于使用 .NET Framework 实现个性化设置的任何方法,此设置提供了最佳性能。
·Encryption - 使用 AES、TripleDES 或 DES 加密 Cookie,但不对 Cookie 进行数据验证。这类 Cookie 容易受到精心选择的纯文本的攻击。
·Validation - 不加密 Cookie 的内容,但验证 Cookie 数据在传输过程中是否未被更改。若要创建 Cookie,验证密钥在缓冲区中与 Cookie 数据连接,并且计算出 MAC 并将其追加到输出的 Cookie。
9) requireSSL - 如果设置为 true,则 Forms 身份验证会设置 Forms 身份验证 Cookie 的安全位。兼容的浏览器只将 Cookie 通过 SSL 连接发送回 ASP.NET。注意,如果使用无 Cookie Forms 身份验证,则此设置无效。
10) slidingExpiration - 如果设置为 true,则 Forms 身份验证将定期更新 Forms 身份验证票的生存期。无论票证是包含在 Cookie 中,还是以无 Cookie 的格式包含在 URL 中,都会进行此操作。
11) timeout - 时间量(以整数分钟为单位),经过该时间量之后,Cookie 则会过期。默认值是 30。超时属性是一个可调值,从收到上次请求的时间开始计算,它将在 n 分钟后过期。为了避免对性能产生负面影响,也为了避免那些打开了 Cookie 警告的应用程序产生多个浏览器警告,Cookie 在超时时间过半时更新。(这意味着在某些情况下可能会出现精度损失。)(注意:createPersistentCookie为true的话,则该属性失效,过期时间将为50年)
3、授权用户和角色设置<system.web>元素下的<authorization>元素,示例如下,仅为说明
<authorization>
<allow VERB="POST" users="webabcd@gmail.com" />
<allow roles="admin" />
<deny users="*" />
<allow VERB="GET" users="abc,xyz" />
<deny users="?" />
</authorization>
注:可以把授权用户和角色设置的配置写在某个文件夹内,则所做的配置只作用于该文件夹内,自动继承外面的配置。
allow - 允许
deny - 拒绝
users - 用户(多用户用逗号隔开)
roles - 角色(多角色用逗号隔开)
verb - 指定http方法,post或get
* - 所有用户
? - 匿名(未经过身份验证的)用户
4、分路径设置授权用户和角色设置,示例如下,仅为说明
<location path="folder">
<system.web>
<authorization>
<deny users="?"/>
<allow users="*"/>
</authorization>
</system.web>
</location>
<location path="abc.aspx">
<system.web>
<authorization>
<allow roles="Administrators" />
<deny users="*"/>
</authorization>
</system.web>
</location>
<location>元素的path属性可以是文件夹也可以是某一文件
5、<membership>元素设置,示例如下,仅为说明
<membership defaultProvider="SqlMembershipProvider">
<providers>
<clear/>
<add name="SqlMembershipProvider"
type="System.Web.Security.SqlMembershipProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
connectionStringName="SqlConnectionString"
enablePasswordRetrieval="false"
enablePasswordReset="true"
requiresQuestionAndAnswer="false"
applicationName="/"
requiresUniqueEmail="false"
passwordFormat="Hashed"
maxInvalidPasswordAttempts="3"
minRequiredPasswordLength="3"
minRequiredNonalphanumericCharacters="0"
passwordAttemptWindow="10"
passwordStrengthRegularExpression="" />
</providers>
</membership>
enablePasswordRetrieval - 是否可以检索用户密码(总是false)
enablePasswordReset - 是否允许用户重置其密码
requiresQuestionAndAnswer - 是否要求在创建用户时提供密码提示问题和答案
applicationName - 自定义成员资格提供程序的应用程序的名称
requiresUniqueEmail - 电子邮件地址是否必须是唯一的
passwordFormat - 存储的密码的格式
maxInvalidPasswordAttempts - 指定允许的无效密码或无效密码提示问题答案尝试的次数。当无效尝试的次数达到配置的值时,将锁定该成员资格用户
minRequiredPasswordLength - 密码所要求的最小长度
minRequiredNonalphanumericCharacters - 有效密码中必须包含的最少特殊字符数
passwordAttemptWindow - 跟踪失败的尝试所用的时间(以分钟为单位)。每当发生另一次失败时都将重置窗口。如果达到了允许的无效密码或密码提示问题答案的最大尝试次数,将锁定成员资格用户
passwordStrengthRegularExpression - 用于验证密码的正则表达式
6、<roleManager>元素设置,示例如下,仅为说明
<roleManager defaultProvider="SqlRoleProvider"
enabled="true"
cacheRolesInCookie="true"
cookieName=".VS2005_Role"
cookieTimeout="30"
cookiePath="/"
cookieRequireSSL="false"
cookieSlidingExpiration="true"
cookieProtection="All">
<providers>
<add
name="SqlRoleProvider"
type="System.Web.Security.SqlRoleProvider"
connectionStringName="SqlConnectionString"
applicationName="/" />
</providers>
</roleManager>
各属性详细说明参看MSDN,索引处查找“roleManager 元素”
7、使用加密密码
<authentication>
<credentials passwordFormat="SHA1" >
<user name="Mary" password="94F85995C7492EEC546C321821AA4BECA9A3E2B1"/>
<user name="John" password="5753A498F025464D72E088A9D5D6E872592D5F91"/>
</credentials>
</authentication>
使用FormsAuthentication.HashPasswordForStoringInConfigFile(String password, String passwordFormat)生成密码
Clear - 密码以明文形式存储
SHA1 - 密码存储为 SHA1 摘要
MD5 - 密码存储为 MD5 摘要
8、使用用户帐户模拟,在<system.web>元素下加如下元素
<identity impersonate="true" username="" password="" />