概要:
ASP.NET 提供了基于角色(即Roles)的认证机制,然而它对角色的支持是不完全的。本文试图通过一些例子来说明如何实现和使用这种基于角色的认证机制。
简介:
ASP.NET 中窗体认证是一个功能非常强大的特性,只需要很少的代码就可以实现一个简单的平台无关的安全认证系统。
但是,如果你需要一个更复杂更有效的认证机制,那么你就要把众多用户分成用户群组,以利用它的灵活性。Windows 集成认证提供了这种认证机制,但它使用的是 NTLM,即Windows NT LAN Manager,因而它不是跨平台的。现在越来越多的人使用 Linux 系统,而 Mozilla Forefox 浏览器用户也越来越多,我们肯定不能把这些人拒之门外,因此我们寻求另外的认证机制。有两个选择:一是为网站划分多个区域,提供多个登录页面,强迫用户一个一个的去注册和登录;二是把用户分组,并且限制特定用户组对某页面或者某区域访问的权限。后者当然是更好的选择。通过分配角色给各个用户,我们能够实现这种功能。 微软为.NET平台留下了窗体认证中基于角色的认证机制。
必要准备:
首先要建立一个数据库,一个Web应用项目,几个不同安全级别的机密目录,以及几个ASP.NET页面。
1、创建数据库
在实际应用项目的数据库中,一般都会有用户数据表 Users,可能包括用户唯一标记:UserID,用户名:UserName,密码:Password,用户的邮件地址:Email,用户所在城市:City,用户登录次数 LoginCount 等。
可以通过创建一个 UserInRoles 数据表(一般可以包括两个字段,用户名:UserName,用户角色:UserRoles)来实现为用户分配角色。
为了简单,我只创建一个 Users 数据表,它有3个字段,用户名 UserName,密码 Password,用户角色UserRoles。建库和表的语句不详细介绍,SQL语句如下:
Create DATABASE WebSolution
GO
Create TABLE Users
(
UserName nvarchar(100) CONSTRAINT PK_UserName PRIMARY KEY,
Password nvarchar(150),
UserRoles nvarchar(100)
)
然后我们为这个Users数据库添加数据。角色名称由你自己自由选择,但是最好用有意义的名称,比如Administrator(顶级管理员),Manager(管理员),Member(加盟成员),User(普通用户)等。其SQL语句是:
Insert INTO Users(UserName,Password,UserRoles) VALUES
('willmove','45CB41B32DCFB917CCD8614F1536D6DA','Administrator,User')
GO
Insert INTO Users(UserName,Password,UserRoles) VALUES
('amuhouse','45CB41B32DCFB917CCD8614F1536D6DA','User')
GO
45CB41B32DCFB917CCD8614F1536D6DA 是 pwd123 使用 md5 加密后的字符串
要注意的是角色 Roles 是大小写敏感的,这是因为在 Web.config 文件中是大小写敏感的。现在我们为实现这个安全认证机制创建几个必要的页面。
以下是前后台代码
aspx代码
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Login.aspx.cs" Inherits="WebSecurity_Login" %>
http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
http://www.w3.org/1999/xhtml" >
用户名 | |
密码 | |
.cs代码
protected void LoginButton_Click(object sender, EventArgs e)
{
#region 查找用户
//查找用户
string conn = "server = .; database = WebSolution; uid = sa; pwd = 123456Aa;";
string hashPassword = FormsAuthentication.HashPasswordForStoringInConfigFile(PasswordTextBox.Text, "md5"); // 或者 sha1
string sqlSentence = string.Format("Select UserRoles FROM Users Where UserName = '{0}' AND Password = '{1}'"
, this.UserNameTextBox.Text.Trim()
, hashPassword);
DataSet ds = DBConnector.GetInstance(conn).GetDataSet(sqlSentence);
#endregion
// 初始化 FormsAuthentication
FormsAuthentication.Initialize();
if (ds.Tables[0].Rows.Count != 0)
{
// 为了实现认证,创建一个新的票据
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
1, // 票据版本号
this.UserNameTextBox.Text.Trim(), // 票据持有者
DateTime.Now, // 分配票据的时间
DateTime.Now.AddMinutes(30), // 失效时间
true, // 需要用户的 cookie
ds.Tables[0].Rows[0]["UserRoles"].ToString(), // 用户数据,这里是用户的角色
FormsAuthentication.FormsCookiePath);
//使用机器码machine key加密cookie,为了安全传送
string hash = FormsAuthentication.Encrypt(ticket);
HttpCookie cookie = new HttpCookie(
FormsAuthentication.FormsCookieName, // 认证cookie的名称
hash); // 加密之后的cookie
/*这里不设置cookie时间,保证ie关闭的时候删除cookie*/
////将cookie的失效时间设置为和票据tikets的失效时间一致
//if (ticket.IsPersistent)
// cookie.Expires = ticket.Expiration;
//添加cookie到页面请求响应中
Response.Cookies.Add(cookie);
// 将用户转向到之前请求的页面,如果之前没有请求任何页面,就转向到首页
string returnUrl = Request.QueryString["ReturnUrl"];
if (returnUrl == null)
returnUrl = "Default.aspx";
// 不要调用FormsAuthentication.RedirectFromLoginPage 方法,
// 因为它会把刚才添加的票据(cookie)替换掉
Response.Redirect(returnUrl);
}
else
{
ErrorLabel.Text = "用户名或者密码错误,请重试!";
ErrorLabel.Visible = true;
}
}
你会注意到上面我们对密码的处理:将它哈希加密。哈希加密是一种单向算法,生成唯一的字符数组。因此即使是改变密码中一个字母的大小写,都会生成完全不同的哈希列。我们把这些加密的密码存储在数据库中,这样更安全。在实际应用中,你可能想为用户找回忘记的密码。但是哈希散列是不可逆的,所以你就不可能恢复原来的密码。但是你可以更改用户的密码,并且把这个更改后的密码告诉他。如果一个网站能够给你旧密码,那么你的用户数据是不安全的!
如果没有使用SSL,你的密码在网络中也是以明文传输的。传输过程中可能会被窃取。在服务器端加密密码只能保证密码存储的安全。SSL相关的资料可以在 http://www.versign.com/ 或 http://www.thewte.com/ 中找到。
下一步,我们需要修改 Global.asax 文件。如果你的Web应用程序没有这个文件,请右键单击Web应用项目,选择 添加->添加新项...->Global Application Class。在 Global.asax 或者Global.asax.cs 中,找到叫做 Application_AuthenticationRequest 的方法(函数)。先要确认已经包含或者使用了System.Security.Principal 以及 System.Web.Security 命名空间,然后修改它,修改后的代码:
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
if (HttpContext.Current.User != null)
{
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
if (HttpContext.Current.User.Identity is FormsIdentity)
{
FormsIdentity id = (FormsIdentity)HttpContext.Current.User.Identity;
FormsAuthenticationTicket ticket = id.Ticket;
取存储在票据中的用户数据,在这里其实就是用户的角色
string userData = ticket.UserData;
string[] roles = userData.Split(,);
HttpContext.Current.User = new GenericPrincipal(id, roles);
}
}
}
}
认证票据(用户名和密码)是没有作为cookie的一部分来存储的,而且也不可以,因为用户可以修改他们的cookie。事实上,FormsAuthentication是用你的机器码 (machine key,通常在machine.config 中)来加密票据(FormsAuthenticationTicket)的。我们使用 UserData 存储用户角色,并且生成一个新的凭证。一旦凭证已经创建,它会被添加到当前上下文中(即 HttpContext),这样就可以用它来取回用户角色了。
接下来,我们设置机密目录(也就是安全目录,特定的使用者如管理员才有权限访问的目录)。首先看看你的Web应用程序根目录下是否有Web.config 这个文件,如果没有就创建一个。你也可以在你的子目录中创建 Web.config 文件,当然,这个Web.config 文件是有限制的(一些参数它不可以设置)。要实现安全认证,在 Web应用程序根目录下的 Web.config 文件中找到 <system.web>节点下的
,把它修改为
loginUrl=Login.aspx
protection=All
path=./ />
上面的 name=AMUHOUSE.ASPXAUTH 中,AMUHOUSE.ASPXAUTH 这个名称是任意的。要控制用户或者用户组的权限,我们可以有两种方法,一是配置在应用程序根目录下的 Web.config 文件,二是在机密目录下创建一个独立的 Web.config 文件。(后者也许会比较好。)如果是前者,这Web.config 就应该包含有下面的内容(或者类似的内容):
loginUrl=login.aspx
protection=All
path=//>
为了使Web应用程序的目录之前不互相依赖,可以比较方便的改名或者移动,可以选择在每一个安全子目录下配置单独的Web.config 文件。它只需要配置 节点,如下:
需要再次提醒的是,上面的角色 roles 是大小写敏感的,为了方便,你也可以把上面修改为:
如果你想允许或者禁止多个角色对这个目录的访问,可以用逗号隔开,如:
至此,我们已经为网站配置了基于角色的安全认证机制了。你可以先编译你的程序,然后尝试访问一个机密目录,例如 http://localhost/RolebasedAuth/Admin ,这时候你就会被转向到用户登录页面。如果你登录成功,并且你的角色对这个目录有访问权限,你就重新回到这个目录下。可能会有用户(或入侵者)企图进入机密目录,我们可以使用一个Session 来存储用户登录的次数,超过一定次数就不让用户登录,并且显示系统拒绝了你的登录请求!。
下面,我们讨论如何根据用户角色让Web控件显示不同内容。
有时候根据用户的角色来显示内容比较好,因为你可能不想为那么多不同的角色(用户群组)制作一大堆有许多重复内容的页面。这样的网站,各种用户帐户可以并存,付费的用户帐户能够访问附加的付费内容。另一个例子是一个页面将显示一个 进入后台管理 按钮链接到后台管理页面如果当前用户是 Administrator(高级管理员)角色。我们现在就实现这个页面。
我们上面用到的 GenericPrincipal 类实现了 IPincipal 接口,这个接口有一个方法名叫做 IsInRole(),它的参数是一个字符串,这个字符串就是要验证的用户角色。如果我们要显示内容给角色是Administrator的已登录用户,我们可以在 Page_Load 中添加下面代码: 程序代码
if (User.IsInRole(Administrator))
AdminLink.Visible = true;
小结:
本文用于帮助你理解基于角色安全机制的重要性、实用性,并且也用 ASP.NET 实现了基于角色的安全机制。它并不是一个很难实现的机制,不过它可能需要一些相关知识如什么是用户凭证,如何认证用户身份,以及如何审定授权用户。