说明:两个月前我刚学 ASP.NET, 在 codeproject.com 看到题目叫 Role-based Security with Forms Authentication 的文章,觉得很有帮助。当时就想翻译成中文。不过直接翻译实在没意思,这两天我参照 Heath Stewart的这篇文章,并且根据自己的理解,把它按照自己的想法和表达方式写成中文。附带上自己为这篇文章做的一个演示的web应用程序。
如果有理解错误的地方,欢迎来信指出或发表评论。
概要:
ASP.NET 提供了基于角色(即 Roles)的认证机制,然而它对角色的支持是不完全的。本文试图通过一些例子来说明如何实现和使用这种基于角色的认证机制。
简介:
ASP.NET 中窗体认证是一个功能非常强大的特性,只需要很少的代码就可以实现一个简单的平台无关的安全认证系统。
但是,如果你需要一个更复杂更有效的认证机制,那么你就要把众多用户分成用户群组,以利用它的灵活性。Windows 集成认证提供了这种认证机制,但它使用的是 NTLM,即Windows NT LAN Manager,因而它不是跨平台的。现在越来越多的人使用 Linux 系统,而 Mozilla Forefox 浏览器用户也越来越多,我们肯定不能把这些人拒之门外,因此我们寻求另外的认证机制。有两个选择:一是为网站划分多个区域,提供多个登录页面,强迫用户一个一个的去注册和登录;二是把用户分组,并且限制特定用户组对某页面或者某区域访问的权限。后者当然是更好的选择。通过分配角色给各个用户,我们能够实现这种功能。
微软为.NET平台留下了窗体认证中基于角色的认证机制,但是我们必须自己去实现它。本文力求覆盖窗体认证中基于角色的认证机制的一些基本的东西,比如它的概念,它的实现,如何在Web应用程序中应用等。
必要准备:
我们首先要建立一个数据库,一个Web应用项目,几个不同安全级别的机密目录,以及几个ASP.NET页面。当然你也可以在你现有的Web应用项目中添加这些。
1、创建数据库
首先要选择你需要使用的数据库管理系统 DBMS。本文使用 SQL Server 2000。
在实际应用项目的数据库中,一般都会有用户数据表 Users,它可能包括用户唯一标记:UserID,用户名:UserName,密码:Password,用户的邮件地址:Email,用户所在城市:City,用户登录次数 LoginCount 等。可以通过创建一个 UserInRoles 数据表(一般可以包括两个字段,用户名:UserName,用户角色:UserRoles)来实现为用户分配角色。
为了简单,我只创建一个 Users 数据表,它有3个字段,用户名 UserName,密码 Password,用户角色 UserRoles。创建表之前,你要选择数据库,或者创建一个新的数据库。要创建一个新的命名为WebSolution的数据库 ,只需要简单的SQL语句:
Create DATABASE WebSolution
GO
要选择一个叫msdb的数据库,可以使用SQL语句:
USE msdb
GO
接下来,我们创建刚才提到的 Users 数据表,SQL 脚本如下:
Create TABLE Users
(
UserName nvarchar(100) CONSTRAINT PK_UserName PRIMARY KEY,
Password nvarchar(150),
UserRoles nvarchar(100)
)
可以为这个表创建索引 Credentials,SQL语句如下:
Create INDEX Credentials ON Users
(
UserName,
Password
)
是否创建索引是可选的,由你自己决定。索引的好处和坏处请参考相关资料。
然后我们为这个Users数据库添加数据。角色名称由你自己自由选择,但是最好用有意义的名称,比如"Administrator"(顶级管理员),"Manager"(管理员),"Member"(加盟成员),"User"(普通用户)等。例如:
UserName|Password|Roles
"willmove"|"pwd123"|"Administrator,User"
"amuhouse"|"pwd123"|"User"
其SQL语句是:
--注意 '45CB41B32DCFB917CCD8614F1536D6DA' 是 'pwd123' 使用 md5 加密后的字符串
Insert INTO Users(UserName,Password,UserRoles) VALUES ('willmove','45CB41B32DCFB917CCD8614F1536D6DA','Administrator,User')
GO
Insert INTO Users(UserName,Password,UserRoles) VALUES ('amuhouse','45CB41B32DCFB917CCD8614F1536D6DA','User')
GO
要注意的是角色 Roles 是大小写敏感的,这是因为在 Web.config 文件中是大小写敏感的。现在我们为实现这个安全认证机制创建几个必要的页面。
首先是用户登录页面 Login.aspx
如果还没有创建Web应用程序,那就现在创建一个。当然你也可以在一个已有的Web应用程序中创建这个页面。这里我假设已经创建了一个名称为 RolebasedAuth的Web应用程序(即 Visual Studio .Net 中的Project)。我把这个Login.aspx放在它的根目录下,也就是通过 http://localhost/RolebasedAuth/Login.aspx 可以访问。
这个Login.aspx放在哪里是无所谓的,但是它必须是公众有权限访问的。
在应用程序根路径下,我们创建两个机密的子目录,分别是 Admin 和 User。
接下来,我们创建一个支持角色认证的窗体认证登录系统。因为微软没有提供简单的实现机制,我们要自己花些时间去创建认证票据。它需要存贮少量信息,当然,有些名称必须和 Web.config 中配置的一样,要不ASP.NET 就会认为你的认证票据是无效的,从而强制转向到登录页面。我们在 VS.NET 中为 Login.aspx 添加两个TextBox控件,取名 UserNameTextBox, PasswordTextBox,再添加一个Button,取名 LoginButton,点击它进入后台代码。在 LoginButton_Click 方法中添加需要的代码。如下:
private void LoginButton_Click(object sender, System.EventArgs e)
{
// 初始化 FormsAuthentication
// 注意它是在 System.Web.Security 命名空间
// 因此要在代码开始添加 using System.Web.Security;
FormsAuthentication.Initialize ();
// 创建数据库连接和数据库操作命令对象
// 注意它是在 System.Data.SqlClient 命名空间
// 因此要在代码开始处添加 using System.Data.SqlClient;
SqlConnection conn =
new SqlConnection("Data Source=sun-willmove;integrated security=SSPI;Initial Catalog=WebSolution;");
SqlCommand cmd = conn.CreateCommand();
cmd.CommandText = "Select UserRoles FROM Users Where UserName=@username " +
"AND Password=@password";
// 填充各个参数
cmd.Parameters.Add("@username", SqlDbType.NVarChar, 100).Value =
UserNameTextBox.Text;
cmd.Parameters.Add("@password", SqlDbType.NVarChar, 150).Value =
FormsAuthentication.HashPasswordForStoringInConfigFile(
PasswordTextBox.Text, "md5"); // 或者 "sha1"
// 执行数据库操作命令
conn.Open();
SqlDataReader reader = cmd.ExecuteReader();
if (reader.Read())
{
// 为了实现认证,创建一个新的票据
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
1, // 票据版本号
UserNameTextBox.Text, // 票据持有者
DateTime.Now, //分配票据的时间
DateTime.Now.AddMinutes(30), // 失效时间
true, // 需要用户的 cookie
reader.GetString(0), // 用户数据,这里其实就是用户的角色
FormsAuthentication.FormsCookiePath);//cookie有效路径
//使用机器码machine key加密cookie,为了安全传送
string hash = FormsAuthentication.Encrypt(ticket);
HttpCookie cookie = new HttpCookie(
F