关于sql语句中的连接(join)关键字,是较为常用而又不太容易理解的关键字,下面这个例子给出了一个简单的解释 --建表table1,table2:
create table table1(id int,name varchar(10))
create table table2(id int,score int)
insert into table1 select 1,'lee'
insert into table1 select 2,'zhang'
insert into table1 select 4,'wang'
insert into table2 select 1,90
insert into table2 select 2,100
insert into table2 select 3,70
如表
-------------------------------------------------
 table1  | table2  |
-------------------------------------------------
id  name |id  score |
1  lee |1  90 |
2  zhang |2  100 |
4  wang |3  70 |
-------------------------------------------------

以下均在查询分析器中执行

一、外连接
1.概念:包括左向外联接、右向外联接或完整外部联接

2.左连接:left join 或 left outer join
(1)左向外联接的结果集包括 LEFT OUTER 子句中指定的左表的所有行,而不仅仅是联接列所匹配的行。如果左表的某行在右表中没有匹配行,则在相关联的结果集行中右表的所有选择列表列均为空值(null)。
(2)sql语句
select * from table1 left join table2 on table1.id=table2.id
-------------结果-------------
id name id score
------------------------------
1 lee 1 90
2 zhang 2 100
4 wang NULL NULL
------------------------------
注释:包含table1的所有子句,根据指定条件返回table2相应的字段,不符合的以null显示

3.右连接:right join 或 right outer join
(1)右向外联接是左向外联接的反向联接。将返回右表的所有行。如果右表的某行在左表中没有匹配行,则将为左表返回空值。
(2)sql语句
select * from table1 right join table2 on table1.id=table2.id
-------------结果-------------
id name id score
------------------------------
1 lee 1 90
2 zhang 2 100
NULL NULL 3 70
------------------------------
注释:包含table2的所有子句,根据指定条件返回table1相应的字段,不符合的以null显示

4.完整外部联接:full join 或 full outer join
(1)完整外部联接返回左表和右表中的所有行。当某行在另一个表中没有匹配行时,则另一个表的选择列表列包含空值。如果表之间有匹配行,则整个结果集行包含基表的数据值。
(2)sql语句
select * from table1 full join table2 on table1.id=table2.id
-------------结果-------------
id name id score
------------------------------
1 lee 1 90
2 zhang 2 100
4 wang NULL NULL
NULL NULL 3 70
------------------------------
注释:返回左右连接的和(见上左、右连接)

二、内连接
1.概念:内联接是用比较运算符比较要联接列的值的联接

2.内连接:join 或 inner join

3.sql语句
select * from table1 join table2 on table1.id=table2.id
-------------结果-------------
id name id score
------------------------------
1 lee 1 90
2 zhang 2 100
------------------------------
注释:只返回符合条件的table1和table2的列

4.等价(与下列执行效果相同)
A:select a.*,b.* from table1 a,table2 b where a.id=b.id
B:select * from table1 cross join table2 where table1.id=table2.id  (注:cross join后加条件只能用where,不能用on)

三、交叉连接(完全)

1.概念:没有 WHERE 子句的交叉联接将产生联接所涉及的表的笛卡尔积。第一个表的行数乘以第二个表的行数等于笛卡尔积结果集的大小。(table1和table2交叉连接产生3*3=9条记录)

2.交叉连接:cross join (不带条件where...)

3.sql语句
select * from table1 cross join table2
-------------结果-------------
id name id score
------------------------------
1 lee 1 90
2 zhang 1 90
4 wang 1 90
1 lee 2 100
2 zhang 2 100
4 wang 2 100
1 lee 3 70
2 zhang 3 70
4 wang 3 70
------------------------------
注释:返回3*3=9条记录,即笛卡尔积

4.等价(与下列执行效果相同)
A:select * from table1,table2       

转载地址:http://blog.csdn.net/daisy_leaf/archive/2006/10/08/1325494.aspx

posted @ 2007-01-04 15:24 yiki 阅读(6735) 评论(1) 编辑
例表a
aid adate
1 a1
2 a2
3 a3
表b
bid bdate
1 b1
2 b2
4 b4
两个表a,b相连接,要取出id相同的字段
select * from a inner join b on a.aid = b.bid这是仅取出匹配的数据.
此时的取出的是:
1 a1 b1
2 a2 b2
那么left join 指:
select * from a left join b on a.aid = b.bid
首先取出a表中所有数据,然后再加上与a,b匹配的的数据
此时的取出的是:
1 a1 b1
2 a2 b2
3 a3 空字符
同样的也有right join
指的是首先取出b表中所有数据,然后再加上与a,b匹配的的数据
此时的取出的是:
1 a1 b1
2 a2 b2
4 空字符 b4
posted @ 2007-01-04 15:13 yiki 阅读(98) 评论(0) 编辑

ASP.NET 2.0中新增的最佳功能之一是新的成员身份服务,它提供了用于创建和管理用户帐户的易于使用的API。ASP.NET 1.x大规模引入了窗体身份验证,但仍然要求您编写相当数量的代码来执行实际操作中的窗体身份验证。成员身份服务填补了ASP.NET 1.x窗体身份验证服务的不足,并且使实现窗体身份验证变得比以前简单得多。

  成员身份API通过两个新的类公开:Membership和MembershipUser。前者包含了用于创建用户、验证用户以及完成其他工作的静态方法。MembershipUser代表单个用户,它包含了用于检索和更改密码、获取上次登录日期以及完成类似工作的方法和属性。通过这两个新的类,我们可以不用写一行代码,方便得完成对用户的管理。 但是在实际开发过程中,绝对不能满足我们日常开发的需要。经过日常项目的开发和网络上资料的搜索,现将其一一列出:

  一、默认的各类的数据库是使用sql express的,而我们在实际开发中往往使用sql sever 2000或者sql server 2005,这时就需要我们修改数据库的类型。

  微软给我们提供了一个Aspnet_regsql的命令来修改默认数据库。打开 Visual Studio 2005 命令提示,输入aspnet_regsql,按照提示一步一步进行即可。

  此时打开数据库,可以发现多处来了一系列"aspnet_"开头的存储过程,这就是我们使用membership所必需的存储过程。

  此时打开IIS,[属性] →[ASP.NET] →[编辑配置]:

  [常规],连接参数LocalSqlServer按照普通的sql连接字符串格式。

  [身份验证],模式为Forms,管理提供程序的minRequiredNonalphanumericCharacters为0,这时就可以去掉默认变态的必需需要输入字母,数字等组合的密码安全了。此步也可修改密码最低长度和最大长度等等。

  经过此步骤,系统会自动在web.config中配置好了我们所需的规则。很方便,修改web.config以后都可以通过这种图形化工具来了。

  二、由于自带的login控件和membership类,只提供了简单的用户信息录入,不能满足我们项目的需要。例如:我们要用户注册的时候同时输入QQ号码,电话号码,家庭地址。那么默认的是没有办法解决的。我这里给出两种解决方案。我分别用在了不同的项目中。优缺点大家自行判断。

  1、使用profile。此类方法网上教程已经很多。不在出重复叙述,免去赚稿费的嫌疑:)。这里只是给出网上没有的部分说明。
由于membership只能列出来指定组的用户名,而不能列出其他的详细信息,我们实际使用中,往往需要对组中的其它信息进行同时修改。我采用的是自行构造datatable的方法。见代码:

public static DataTable listuser(string userRoles)//列出指定组的用户信息
{
 string[] users = Roles.GetUsersInRole(userRoles);
 //列出指定组下的用户
 DataTable dt = new DataTable();
 dt.Columns.Add("username", System.Type.GetType("System.String"));
 dt.Columns.Add("QQ", System.Type.GetType("System.String"));
 dt.Columns.Add("phone", System.Type.GetType("System.String"));
 dt.Columns.Add("address", System.Type.GetType("System.String"));
 dt.Columns.Add("email", System.Type.GetType("System.String"));
 //以上构造一个数据表
 foreach (string i in users)
 {
  DataRow dr = dt.NewRow();
  MembershipUser mu = Membership.GetUser(i);
  得到用户基本信息
  ProfileCommon p = Profile.GetProfile(i); //得到用户的profile信息
  dr[0] = mu. username;
  dr[1] = p. QQ;
  //profile是强类型,可以很方便的通过感知来添加。
  dr[2] = p. phone;
  dr[3] = p. address;
  dr[4] = mu. email;
  dt.Rows.Add(dr);
  dt.AcceptChanges();
 }
 return dt;
}
public static void deleteuser(string username)/删除指定用户
{
 Membership.DeleteUser(username);
 //系统会自动删除profile下的指定用户的信息
}
public static void updateuser(string username)/更新指定用户
{
 ProfileCommon p = Profile.GetProfile(i);
 //得到用户的profile信息
 p. phone="电话";
 p. address="地址;
 p. QQ="QQ号码";
 p.Save();
 //保存所作修改。
}

  2、自定义一个membershipinfo表格,同membership系统标关联起来。自己编写SQL语句来进行查询,修改等功能。

列出指定组的用户
select * from aspnet_membership inner join aspnet_users on
aspnet_membership.userid=aspnet_users.userid left join memberinfo on aspnet_membership.userid=memberinfo.userid
where aspnet_membership.userid=(select userid from aspnet_usersinroles inner join
aspnet_roles on aspnet_usersinroles.roleid=aspnet_roles.roleid where rolename='admin')
  
  删除、修改等功能比较简单,这里就不作叙述。可以采用membership的createuser方法建立,然后再用sql语句写入memberinfo表。

  对于建立用户,我们可以采取扩展CreateUserWizard控件,或者自行写登陆界面。
1、采取扩展CreateUserWizard控件,我们可以使用它的模版列,此时需要注意的是:用户名,密码,提示问题,提示问题答案,Email,他们的ID一定要分别是username,Password,Question,Answer,Email否则会出错,而且此时验证控件均不能使用。怀疑是IDE的一个Bug。

  如下所示,我们定义好的样式应当是:

<WizardSteps>
<asp:CreateUserWizardStep runat="server"> 自定义代码部分<ContentTemplate>
</ContentTemplate>
</asp:CreateUserWizardStep>
</WizardSteps>
代码部分:
protected void CreateUserWizard1_CreatedUser(object sender, EventArgs e)
{
 //由于系统会自动给们建立基本的信息表,所以我们只需要对profile或者membershipinfo标进行修改即可。   
 Roles.AddUserToRole(CreateUserWizard1.UserName, "shop"); 
 //添加用户到相应的组
 ProfileCommon p = (ProfileCommon)ProfileCommon.Create(CreateUserWizard1.UserName, true);
 p. QQ = ((TextBox)CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("QQ")).Text.Trim();
 p.address= ((TextBox)CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("address")).Text.Trim();   
 p.phone = ((TextBox)CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("phone")).Text.Trim(); p.Save();//保存所作修改
}

  2、采取自己写UI,个人推荐使用这种方法。灵活性比较大,而且可以使用2005强大的验证控件。

  页面部分比较简单略过不再陈述。下面详细介绍一下代码部分。

  此时我们需要使用membership的CreateUser()方法。语法我们就不再介绍。他会根据建立用户的结果返回成一个MembershipCreateStatus枚举类,它详细的包含了所有建立用户不成功的错误信息。我们只需要根据他的值,就可以返回给界面相应的提示,如:用户名已经存在,电子邮件已经存在等等。

  对于自定义用户信息部分我们仍然可以采取profile或者自定义membershipinfo表的方法。

  列出profile建立用户的方法。SQL语句比较简单,略过不写。

protected void Button1_Click(object sender, EventArgs e)
{
 MembershipCreateStatus status;
 MembershipUser newUser = Membership.CreateUser(username.Text.Trim(), Password.Text.Trim(),  Email.Text.Trim(), Question.Text.Trim(), Answer.Text.Trim(), true, out status);
 //使用membership建立用户,并把建立结果返回给MembershipCreateStatus

  if (newUser == null)//没有新用户,则意味着出错。
  {
  GetErrorMessage(status);
  //调用GetErrorMessage函数,返回详细错误信息
  } else {
   Roles.AddUserToRole(newUser.UserName, "jiancai");
   //添加用户到相应组 ProfileCommon
   p = (ProfileCommon)ProfileCommon.Create(newUser.UserName, true);
   p.QQ = QQ.Text.Trim();
   p.address= address.Text.Trim();
   p.phone= phone.Text.Trim();
   p.Save();
  }
 }
public void GetErrorMessage(MembershipCreateStatus status)
{
 switch (status)
  {
   case MembershipCreateStatus.DuplicateUserName: DisplayAlert("当前用户已经存在,请重新选择");
    break;
    //其余各种错误信息,请查看MSDN的MembershipCreateStatus枚举类。
   default: DisplayAlert("注册00000000用户失败,请检查您的用户名,密码等信息");
    break;
  }
}

  个人见解:采取profile的方法,比较方便,由于profile 是强类型,可以通过智能感知功能减少代码的输入量。采取自定义数据表的方法,需要输入大量的sql语句,但是查询速度比较快,性能比较强,由于Roles.GetUsersInRole()方法无法分页读取数据,只能一次性读出来所有数据,而自写SQL 语句可以很方便的根分页结合起来。随着用户量的增多,故不推荐profile方法。
1、采取扩展CreateUserWizard控件,我们可以使用它的模版列,此时需要注意的是:用户名,密码,提示问题,提示问题答案,Email,他们的ID一定要分别是username,Password,Question,Answer,Email否则会出错,而且此时验证控件均不能使用。怀疑是IDE的一个Bug。

  如下所示,我们定义好的样式应当是:

<WizardSteps>
<asp:CreateUserWizardStep runat="server"> 自定义代码部分<ContentTemplate>
</ContentTemplate>
</asp:CreateUserWizardStep>
</WizardSteps>
代码部分:
protected void CreateUserWizard1_CreatedUser(object sender, EventArgs e)
{
 //由于系统会自动给们建立基本的信息表,所以我们只需要对profile或者membershipinfo标进行修改即可。   
 Roles.AddUserToRole(CreateUserWizard1.UserName, "shop"); 
 //添加用户到相应的组
 ProfileCommon p = (ProfileCommon)ProfileCommon.Create(CreateUserWizard1.UserName, true);
 p. QQ = ((TextBox)CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("QQ")).Text.Trim();
 p.address= ((TextBox)CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("address")).Text.Trim();   
 p.phone = ((TextBox)CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("phone")).Text.Trim(); p.Save();//保存所作修改
}

  2、采取自己写UI,个人推荐使用这种方法。灵活性比较大,而且可以使用2005强大的验证控件。

  页面部分比较简单略过不再陈述。下面详细介绍一下代码部分。

  此时我们需要使用membership的CreateUser()方法。语法我们就不再介绍。他会根据建立用户的结果返回成一个MembershipCreateStatus枚举类,它详细的包含了所有建立用户不成功的错误信息。我们只需要根据他的值,就可以返回给界面相应的提示,如:用户名已经存在,电子邮件已经存在等等。

  对于自定义用户信息部分我们仍然可以采取profile或者自定义membershipinfo表的方法。

  列出profile建立用户的方法。SQL语句比较简单,略过不写。

protected void Button1_Click(object sender, EventArgs e)
{
 MembershipCreateStatus status;
 MembershipUser newUser = Membership.CreateUser(username.Text.Trim(), Password.Text.Trim(),  Email.Text.Trim(), Question.Text.Trim(), Answer.Text.Trim(), true, out status);
 //使用membership建立用户,并把建立结果返回给MembershipCreateStatus

  if (newUser == null)//没有新用户,则意味着出错。
  {
  GetErrorMessage(status);
  //调用GetErrorMessage函数,返回详细错误信息
  } else {
   Roles.AddUserToRole(newUser.UserName, "jiancai");
   //添加用户到相应组 ProfileCommon
   p = (ProfileCommon)ProfileCommon.Create(newUser.UserName, true);
   p.QQ = QQ.Text.Trim();
   p.address= address.Text.Trim();
   p.phone= phone.Text.Trim();
   p.Save();
  }
 }
public void GetErrorMessage(MembershipCreateStatus status)
{
 switch (status)
  {
   case MembershipCreateStatus.DuplicateUserName: DisplayAlert("当前用户已经存在,请重新选择");
    break;
    //其余各种错误信息,请查看MSDN的MembershipCreateStatus枚举类。
   default: DisplayAlert("注册00000000用户失败,请检查您的用户名,密码等信息");
    break;
  }
}

  个人见解:采取profile的方法,比较方便,由于profile 是强类型,可以通过智能感知功能减少代码的输入量。采取自定义数据表的方法,需要输入大量的sql语句,但是查询速度比较快,性能比较强,由于Roles.GetUsersInRole()方法无法分页读取数据,只能一次性读出来所有数据,而自写SQL 语句可以很方便的根分页结合起来。随着用户量的增多,故不推荐profile方法。
1、采取扩展CreateUserWizard控件,我们可以使用它的模版列,此时需要注意的是:用户名,密码,提示问题,提示问题答案,Email,他们的ID一定要分别是username,Password,Question,Answer,Email否则会出错,而且此时验证控件均不能使用。怀疑是IDE的一个Bug。

  如下所示,我们定义好的样式应当是:

<WizardSteps>
<asp:CreateUserWizardStep runat="server"> 自定义代码部分<ContentTemplate>
</ContentTemplate>
</asp:CreateUserWizardStep>
</WizardSteps>
代码部分:
protected void CreateUserWizard1_CreatedUser(object sender, EventArgs e)
{
 //由于系统会自动给们建立基本的信息表,所以我们只需要对profile或者membershipinfo标进行修改即可。   
 Roles.AddUserToRole(CreateUserWizard1.UserName, "shop"); 
 //添加用户到相应的组
 ProfileCommon p = (ProfileCommon)ProfileCommon.Create(CreateUserWizard1.UserName, true);
 p. QQ = ((TextBox)CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("QQ")).Text.Trim();
 p.address= ((TextBox)CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("address")).Text.Trim();   
 p.phone = ((TextBox)CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("phone")).Text.Trim(); p.Save();//保存所作修改
}

  2、采取自己写UI,个人推荐使用这种方法。灵活性比较大,而且可以使用2005强大的验证控件。

  页面部分比较简单略过不再陈述。下面详细介绍一下代码部分。

  此时我们需要使用membership的CreateUser()方法。语法我们就不再介绍。他会根据建立用户的结果返回成一个MembershipCreateStatus枚举类,它详细的包含了所有建立用户不成功的错误信息。我们只需要根据他的值,就可以返回给界面相应的提示,如:用户名已经存在,电子邮件已经存在等等。

  对于自定义用户信息部分我们仍然可以采取profile或者自定义membershipinfo表的方法。

  列出profile建立用户的方法。SQL语句比较简单,略过不写。

protected void Button1_Click(object sender, EventArgs e)
{
 MembershipCreateStatus status;
 MembershipUser newUser = Membership.CreateUser(username.Text.Trim(), Password.Text.Trim(),  Email.Text.Trim(), Question.Text.Trim(), Answer.Text.Trim(), true, out status);
 //使用membership建立用户,并把建立结果返回给MembershipCreateStatus

  if (newUser == null)//没有新用户,则意味着出错。
  {
  GetErrorMessage(status);
  //调用GetErrorMessage函数,返回详细错误信息
  } else {
   Roles.AddUserToRole(newUser.UserName, "jiancai");
   //添加用户到相应组 ProfileCommon
   p = (ProfileCommon)ProfileCommon.Create(newUser.UserName, true);
   p.QQ = QQ.Text.Trim();
   p.address= address.Text.Trim();
   p.phone= phone.Text.Trim();
   p.Save();
  }
 }
public void GetErrorMessage(MembershipCreateStatus status)
{
 switch (status)
  {
   case MembershipCreateStatus.DuplicateUserName: DisplayAlert("当前用户已经存在,请重新选择");
    break;
    //其余各种错误信息,请查看MSDN的MembershipCreateStatus枚举类。
   default: DisplayAlert("注册00000000用户失败,请检查您的用户名,密码等信息");
    break;
  }
}

  个人见解:采取profile的方法,比较方便,由于profile 是强类型,可以通过智能感知功能减少代码的输入量。采取自定义数据表的方法,需要输入大量的sql语句,但是查询速度比较快,性能比较强,由于Roles.GetUsersInRole()方法无法分页读取数据,只能一次性读出来所有数据,而自写SQL 语句可以很方便的根分页结合起来。随着用户量的增多,故不推荐profile方法。
三、密码问题。

  个人觉得密码是一个比较头疼的问题。我们在实际开发中总是需要admin组有对用户进行密码修改的权限。Membership提供的修改密码方法只能在已经知道密码提示答案的时候才能修改。而admin组根本不可能知道用户的密码提示答案的。这里有点好笑。难道是中西方文化差异?

  列出个人对membership密码研究的一些心得。

  大家都知道machine.config和web.config,如果两者发生冲突,那么以web.config优先。

  默认状态下,membership是采用SHA1的方法进行加密,然后采取一种机制,与passwordsalt进行再次加密,最后形成数据库中显示的密码。可见随着MD5加密的破解,微软对密码的安全也煞费苦心。

  有的朋友不喜欢默认的SHA1加密形式,我们只需要在web.config中设置以下代码来覆盖machine.config中对密码的设置就好了:

<machineKey
validationKey="AutoGenerate,IsolateApps"
decryptionKey="AutoGenerate,IsolateApps"
decryption="Auto"
validation="MD5/SHA1/Clear" />

  其中:

  Clear - 密码以明文形式存储。用户密码可与此值直接比较,而不需要进行进一步的转换。

  MD5 -使用消息摘要 5 (MD5) 哈希摘要存储密码。为了验证凭据,将使用 MD5 算法对用户密码进行哈希运算并将计算出来的值与存储的值进行比较。使用此值时,从不存储或比较明文密码。此算法的性能比 SHA1 好。

  SHA1 -使用 SHA1 哈希摘要存储密码。为了验证凭据,将使用 SHA1 算法对用户密码进行哈希运算并将计算出来的值与存储的值进行比较。从不存储明文密码。使用该算法可以获得比 MD5 算法高的安全性。

  然而虽然MembershipUser提供了GetPassword 方法,但是这是后只有在加密形式设置成Clear,即密码在数据库中以明码的形式存在,才能得到密码。而ChangePassword必须要提供旧密码或者密码提示答案才可以修改。对用户管理造成了很大的不便。找了许多资料无解。顺便提带一下:微软的membership机制起源于csblog,并进行了一定的修改。

  Csblog对密码的策略:首先对用户输入的密码进行加密(SHA1或者MD5),然后根据系统自动生成的密匙再次对加密后的密码进行DES加密。不过我按照csblog 的方法进行加密的时候,形式相同,密码却不同,目前正在演就中。也欢迎大家共同讨论。

  如果passwordsalt和password固定,那么用户密码肯定一定。所以目前我采取的一个方法就是给用户重新设置成固定密码的功能。

  首先取得已知用户密码的passwordsalt和password,然后替换相应用户的passwordsalt和password字段。这时,用户的密码就已经恢复成已知的密码了。

  不可否认,membership给我们开发中创造了很大的便利,其方便的Roles功能,对于我们进行权限管理的时候提供了很好的解决方案。2005许多功能均进化自csblog这优秀的开源项目,有兴趣者可以研究csblog,以进一步的了解membership的运作机制。限于篇幅,MSDN上边有做介绍的我就不再重复叙述。只介绍MSDN上没有列出的技巧跟我在项目开发中的一些心得。 ASP.NET 2.0为使用窗体身份验证的Web站点提供了重要的安全性优势。通过提供用户配置文件储备库以及对角色的支持,窗体身份验证将走出ASP.NET内行的视野,而得以更广泛地实现。

转载地址:http://www.cnblogs.com/hbreset/

posted @ 2007-01-04 11:45 yiki 阅读(273) 评论(0) 编辑

Asp.net的身份验证有有三种,分别是"Windows | Forms | Passport",其中又以Forms验证用的最多,也最灵活。
Forms 验证方式对基于用户的验证授权提供了很好的支持,可以通过一个登录页面验证用户的身份,将此用户的身份发回到客户端的Cookie,之后此用户再访问这个web应用就会连同这个身份Cookie一起发送到服务端。服务端上的授权设置就可以根据不同目录对不同用户的访问授权进行控制了。

问题来了,在实际是用中我们往往需要的是基于角色,或者说基于用户组的验证和授权。对一个网站来说,一般的验证授权的模式应该是这样的:根据实际需求把用户分成不同的身份,就是角色,或者说是用户组,验证过程不但要验证这个用户本身的身份,还要验证它是属于哪个角色的。而访问授权是根据角色来设置的,某些角色可以访问哪些资源,不可以访问哪些资源等等。要是基于用户来授权访问将会是个很不实际的做法,用户有很多,还可能随时的增减,不可能在配置文件中随时的为不断增加的新用户去增加访问授权的。

下面大概的看一下Forms的过程。

Forms身份验证基本原理:

一 身份验证

要采用Forms身份验证,先要在应用程序根目录中的Web.config中做相应的设置:

<authentication mode="forms">
    <forms name=".ASPXAUTH " loginUrl="/login.aspx" timeout="30" path= "/">
    </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的信息,从而达到基于角色验证的目的。

Forms身份验证基于角色的授权

一 身份验证

在web.config的<authentication>的设置还是一样:

<authentication mode="forms">
    <forms name=".ASPXAUTH " loginUrl="/login.aspx" timeout="30" path= "/">
    </forms>
</authentication>

/login.aspx验证用户合法性页面中,在验证了用户的合法性后,还要有个取得此用户属于哪些role的过程,这个看各个应用的本身如何设计的了,一般是在数据库中会有个use_role表,可以从数据库中获得此用户属于哪些role,在此不深究如何去获取用户对应的role,最后肯定能够获得的此用户对应的所有的role用逗号分割的一个字符串。
在上面的非基于角色的方法中,我们用了FormsAuthentication.RedirectFromLoginPage 方法来完成生成身份验证票,写回客户端,浏览器重定向等一系列的动作。这个方法会用一些确省的设置来完成一系列的动作,在基于角色的验证中我们不能用这一个方法来实现,要分步的做,以便将一些定制的设置加进来:

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) 将身份验证票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
          Context.Response.Cookies.Add (UserCookie) ; //输出Cookie
         Context.Response.Redirect (Context.Request["ReturnUrl"]) ; // 重定向到用户申请的初始页面
     }
    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来控制用户的访问权限了.

出处:http://www.donews.net/robinblood/archive/2005/04/30/358041.aspx
posted @ 2007-01-04 10:07 yiki 阅读(59) 评论(0) 编辑

解释:ASP.NET 2.0 中的窗体身份验证

发布日期: 2006-6-26 | 更新日期: 2006-6-26

适用于:
ASP.NET 2.0 版

摘要:本教程阐释 ASP.NET 2.0 版中窗体身份验证的工作机制;阐释 IIS 和 ASP.NET 身份验证如何协作,以及 FormsAuthenticationModule 类的角色与操作。

*
本页内容
目标 目标
概述 概述
IIS 身份验证 IIS 身份验证
ASP.NET 窗体身份验证 ASP.NET 窗体身份验证
Cookieless 窗体身份验证 Cookieless 窗体身份验证
成员身份和登录控件 成员身份和登录控件
Web 场方案 Web 场方案
其他资源 其他资源
反馈 反馈
技术支持 技术支持
社区和新闻组 社区和新闻组
投稿人与审阅者 投稿人与审阅者

目标

了解 ASP.NET 2.0 版中窗体身份验证的工作机制。

了解窗体身份验证配置如何影响窗体身份验证票的生成。

查看窗体身份验证票中存储了什么内容。

了解 cookieless 窗体身份验证的工作机制。

了解窗体身份验证的 Web 场 (Web farm) 注意事项。

概述

窗体身份验证使用用户登录到站点时创建的身份验证票,然后在整个站点内跟踪该用户。窗体身份验证票通常包含在一个 Cookie 中。然而,ASP.NET 2.0 版支持无 Cookie 窗体身份验证,结果是将票证传入查询字符串中。

如果用户请求一个需要经过身份验证的访问的页,且该用户以前没有登录过该站点,则该用户重定向到一个配置好的登录页。该登录页提示用户提供凭据(通常是用户名和密码)。然后,将这些凭据传递给服务器并针对用户存储(如 SQL Server 数据库)进行验证。在 ASP.NET 2.0 中,用户存储访问可由成员身份提供程序处理。对用户的凭据进行身份验证后,用户重定向到原来请求的页面。

窗体身份验证处理由 FormsAuthenticationModule 类实现,该类是一个参与常规 ASP.NET 页处理循环的 HTTP 模块。本文阐释 ASP.NET 2.0 中窗体身份验证的工作机制。

IIS 身份验证

ASP.NET 身份验证分为两个步骤。首先,Internet 信息服务 (IIS) 对用户进行身份验证,并创建一个 Windows 令牌来表示该用户。IIS 通过查看 IIS 元数据库设置,确定应该对特定应用程序使用的身份验证模式。如果 IIS 配置为使用匿名身份验证,则为 IUSR_MACHINE 帐户生成一个令牌并用它表示匿名用户。然后,IIS 将该令牌传递给 ASP.NET。

其次,ASP.NET 执行自己的身份验证。所使用的身份验证方法由 authentication 元素的 mode 属性指定。以下身份验证配置指定 ASP.NET 使用 FormsAuthenticationModule 类:

<authentication mode="Forms" />

注 由于窗体身份验证不依赖于 IIS 身份验证,因此如果要在 ASP.NET 应用程序中使用窗体身份验证,则应该在 IIS 中为应用程序配置匿名访问。

ASP.NET 窗体身份验证

ASP.NET 窗体身份验证在 IIS 身份验证完成后发生。可以使用 forms 元素配置窗体身份验证。

窗体身份验证配置

以下配置文件片段显示窗体身份验证的默认属性值。

<system.web>
<authentication mode="Forms">
<forms loginUrl="Login.aspx"
protection="All"
timeout="30"
name=".ASPXAUTH"
path="/"
requireSSL="false"
slidingExpiration="true"
defaultUrl="default.aspx"
cookieless="UseDeviceProfile"
enableCrossAppRedirects="false" />
</authentication>
</system.web>

下面是对默认属性值的描述:

loginUrl 指向应用程序的自定义登录页。应该将登录页放在需要安全套接字层 (SSL) 的文件夹中。这有助于确保凭据从浏览器传到 Web 服务器时的完整性。

protection 设置为 All,以指定窗体身份验证票的保密性和完整性。这导致使用 machineKey 元素上指定的算法对身份验证票证进行加密,并且使用同样是 machineKey 元素上指定的哈希算法进行签名。

timeout 用于指定窗体身份验证会话的有限生存期。默认值为 30 分钟。如果颁发持久的窗体身份验证 Cookie,timeout 属性还用于设置持久 Cookie 的生存期。

name 和 path 设置为应用程序的配置文件中定义的值。

requireSSL 设置为 false。该配置意味着身份验证 Cookie 可通过未经 SSL 加密的信道进行传输。如果担心会话窃取,应考虑将 requireSSL 设置为 true。

slidingExpiration 设置为 true 以执行变化的会话生存期。这意味着只要用户在站点上处于活动状态,会话超时就会定期重置。

defaultUrl 设置为应用程序的 Default.aspx 页。

cookieless 设置为 UseDeviceProfile,以指定应用程序对所有支持 Cookie 的浏览器都使用 Cookie。如果不支持 Cookie 的浏览器访问该站点,窗体身份验证在 URL 上打包身份验证票。

enableCrossAppRedirects 设置为 false,以指明窗体身份验证不支持自动处理在应用程序之间传递的查询字符串上的票证以及作为某个窗体 POST 的一部分传递的票证。

授权配置

在 IIS 中,对所有使用窗体身份验证的应用程序启用异步访问。UrlAuthorizationModule 类用于帮助确保只有经过身份验证的用户才能访问页。

可以使用 authorization 元素配置 UrlAuthorizationModule,如以下示例所示。

<system.web>
<authorization>
<deny users="?" />
</authorization>
</system.web>

使用该设置将拒绝所有未经过身份验证的用户访问应用程序中的任何页。如果未经身份验证的用户试图访问某页,窗体身份验证模块将该用户重定向到 forms 元素的 loginUrl 属性指定的登录页。

窗体身份验证控制流

图 1 显示窗体身份验证期间出现的事件顺序。


图 1. 窗体身份验证控制流

用户请求应用程序的虚拟目录下的 Default.aspx 文件。因为 IIS 元数据库中启用了匿名访问,因此 IIS 允许该请求。ASP.NET 确认 authorization 元素包括 <deny users="?" /> 标记。

服务器查找一个身份验证 Cookie。如果找不到该身份验证 Cookie,则用户重定向到配置好的登录页 (Login.aspx),该页由 forms 元素的 LoginUrl 属性。用户通过该窗体提供和提交凭据。有关起始页的信息存放在使用 RETURNURL 作为密钥的查询字符串中。服务器 HTTP 应答如下所示:

302 Found Location:
            http://localhost/FormsAuthTest/login.aspx?RETURNURL=%2fFormAuthTest%2fDefault.aspx
            

浏览器请求 Login.aspx 页,并在查询字符串中包括 RETURNURL 参数。

服务器返回登录页以及 200 OK HTTP 状态代码。

用户在登录页输入凭据,并将该页(包括来自查询字符串的 RETURNURL 参数)发送回服务器。

服务器根据某个存储(如 SQL Server 数据库或 Active Directory 用户存储)验证用户凭据。登录页中的代码创建一个包含为该会话设置的窗体身份验证票的 Cookie。

在 ASP.NET 2.0 中,可以通过成员身份系统执行对用户凭据的验证。Membership 类为此提供了 ValidateUser 方法,如下所示:

if (Membership.ValidateUser(userName.Text, password.Text))
            {
            if (Request.QueryString["ReturnUrl"] != null)
            {
            FormsAuthentication.RedirectFromLoginPage(userName.Text, false);
            }
            else
            {
            FormsAuthentication.SetAuthCookie(userName.Text, false);
            }
            }
            else
            {
            Response.Write("Invalid UserID and Password");
            }
            

注 使用 Login Web 服务器控件时,它自动为您执行以下步骤。下文使用了前面提供的代码。

对于经过身份验证的用户,服务器将浏览器重定向到查询字符串中的 RETURNURL 参数指定的原始 URL。服务器 HTTP 应答如下所示:

302 Found Location:
            http://localhost/TestSample/default.aspx
            

重定向之后,浏览器再次请求 Default.aspx 页。该请求包括身份验证 Cookie。

FormsAuthenticationModule 类检测窗体身份验证 Cookie 并对用户进行身份验证。身份验证成功后,FormsAuthenticationModule 类使用有关经过身份验证的用户的信息填充当前的 User 属性(由 HttpContext 对象公开)。

由于服务器已经验证了身份验证 Cookie,因此它允许访问并返回 Default.aspx 页。

FormsAuthenticationModule

ASP.NET 2.0 在计算机级 Web.config 文件中定义了一组 HTTP 模块,包括大量身份验证模块,如下所示:

<httpModules>
...
<add name="WindowsAuthentication"
type="System.Web.Security.WindowsAuthenticationModule" />
<add name="FormsAuthentication"
type="System.Web.Security.FormsAuthenticationModule" />
<add name="PassportAuthentication"
type="System.Web.Security.PassportAuthenticationModule" />
...
</httpModules>

每个请求只能使用一个身份验证模块。所使用的身份验证模块取决于 authentication 元素(通常位于应用程序的虚拟目录中的 Web.config 文件中)指定了哪种身份验证模式。

当 Web.config 文件中包含以下元素时,激活 FormsAuthenticationModule 类。

<authentication mode="Forms" />

FormsAuthenticationModule 类构造一个 GenericPrincipal 对象并将其存储在 HTTP 上下文中。GenericPrincipal 对象保存对一个 FormsIdentity 实例的引用,该实例代表当前经过身份验证的用户。应该允许窗体身份验证为您管理这些任务。如果应用程序有特定要求(例如,将 User 属性设置为一个实现 IPrincipal 接口的自定义类),则该应用程序应该处理 PostAuthenticate 事件。FormsAuthenticationModule 验证了窗体身份验证 Cookie 并创建了 GenericPrincipal 和 FormsIdentity 对象之后,会发生 PostAuthenticate 事件。在该代码中,可以构造一个包装 FormsIdentity 对象的自定义 IPrincipal 对象,然后将它存储在 HttpContext. User 属性中。

注 如果执行了这一操作,还需要设置 Thread.CurrentPrincipal 属性上的 IPrincipal 引用,以确保 HttpContext 对象和该线程指向相同的身份验证信息。

窗体身份验证 Cookie

调用 FormsAuthentication.SetAuthCookie 或FormsAuthentication.RedirectFromLoginPage 方法时,FormsAuthentication 类自动创建身份验证 Cookie。

典型的窗体身份验证 Cookie 中包括以下属性:

Name。该属性指定 Cookie 的名称。

Value。该属性指定 Cookie 的值。

在典型的窗体身份验证 Cookie 中,该值包含一个经过加密和签名的 FormsAuthenticationTicket 对象的字符串表示形式。该 Cookie 包含以下属性:

Expires。该属性指定 Cookie 的到期日期和时间。仅当代码指示应该颁发一个持久的窗体身份验证 Cookie,窗体身份验证才设置该值。

Domain。该属性指定与 Cookie 关联的域。默认值为 null。

HasKeys。该属性指出 Cookie 是否有子项。

HttpOnly。该属性指定是否可以通过客户端脚本访问该 Cookie。在 ASP.NET 2.0 中,该值始终设置为 true。Internet Explorer 6 Service Pack 1 支持该 Cookie 属性,从而防止客户端脚本从 document.cookie 属性访问该 Cookie。如果尝试从客户端脚本访问该 Cookie,则返回一个空字符串。无论何时用户浏览到当前域中的 Web 站点,该 Cookie 仍然发送至服务器。

注 不支持 HttpOnly Cookie 属性的 Web 浏览器要么忽略该 Cookie,要么忽略该属性,这意味着会话仍然容易受到跨站点脚本的攻击。

Path。该属性指定 Cookie 的虚拟路径。默认值为"/",代表根目录。

Secure。该属性指出 Cookie 是否应该仅通过 HTTPS 连接传输。Secure 属性应设置为 true,以便该 Cookie 可以受 SSL 加密的保护。

Version。该属性指定 Cookie 的版本号。

创建身份验证 Cookie

通过 FormsAuthentication 类创建身份验证 Cookie,如下所示。用户经过验证后,FormsAuthentication 类在内部创建一个 FormsAuthenticationTicket 对象,方法是指定 Cookie 名、Cookie 版本、目录路径、Cookie 颁发日期;Cookie 到期日期、是否应该保留 Cookie,以及用户定义的数据(可选)。

FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1,
"userName",
DateTime.Now,
DateTime.Now.AddMinutes(30), // value of time out property
false, // Value of IsPersistent property
String.Empty,
FormsAuthentication.FormsCookiePath);

接下来,如果 forms 元素的 protection 属性设置为 All 或 Encryption,则窗体身份验证使用 Encrypt 方法对窗体身份验证票进行加密和签名。

string encryptedTicket = FormsAuthentication.Encrypt(ticket);

以下文本显示了当 protection 属性设置为 All 时使用的过程:

创建序列化窗体身份验证票。创建票证的字节数组表示形式。

对窗体身份验证票进行签名。字节数组的消息身份验证代码 (MAC) 的值,由使用 machineKey 元素的 validation 和 validationKey 属性指定的算法和密钥进行计算。默认情况下,使用 SHA1 算法。

对窗体身份验证票进行加密。已经创建的第二个字节数组使用 FormsAuthentication 类的 Encrypt 方法进行加密。该 Encrypt 方法在内部使用由 machineKey 元素上的 decryption 和 decryptionKey 属性指定的算法和密钥。ASP.NET 1.1 版在默认情况下使用 3DES 算法。ASP.NET 2.0 版在默认情况下使用 Rinjdael (AES) 算法。

根据需要创建 HTTP Cookie 或查询字符串。然后,如果窗体身份验证针对 cookieless 身份验证进行了配置,则加密的身份验证票添加到 HttpCookie 对象。使用以下代码创建该 Cookie 对象:

HttpCookie authCookie = new HttpCookie(
            FormsAuthentication.FormsCookieName,
            encryptedTicket);
            

将窗体身份验证Cookie 设置为安全的。如果窗体身份验证票配置为使用 SSL,则 HttpCookie.Secure 属性设置为 true。这表明浏览器仅通过 HTTPS 连接发送 Cookie。

authCookie.Secure = true;
            

设置 HttpOnly 位。在 ASP.NET 2.0 中,始终设置该位。

设置适当的 Cookie 属性。如果需要,设置 Cookie 的 path、domain 和 expires 属性。

将 Cookie 添加到 Cookie 集合。将身份验证 Cookie 添加到要返回给客户端浏览器的 Cookie 集合。

Response.Cookies.Add(authCookie);
            

每次在身份验证之后接收一个后续请求时,FormsAuthenticationModule 类都会从身份验证 Cookie 中检索身份验证票,对其进行解密,计算哈希值,并比较该 MAC 值,以帮助确保该 Cookie 未被篡改。最后,验证该窗体身份验证票中包含的到期时间。

注 ASP.NET 并不依赖于 Cookie 的到期日期,因为该时间很容易伪造。

角色授权

在 ASP.NET 2.0 中,角色授权已经得到简化。对用户进行身份验证或者将角色细节添加到身份验证 Cookie 时,不再需要检索角色信息。.NET Framework 2.0 包括一个角色管理 API,它使您能够创建和删除角色,将用户添加到角色以及从角色删除用户。该角色管理 API 将其数据存储在一个基础数据存储中,它通过针对该数据存储的适当角色提供程序访问该存储。以下角色提供程序为 .NET Framework 2.0 附带,可以与窗体身份验证一起使用:

SQL Server。它是默认的提供程序,将角色信息存储在 SQL Server 数据库。

授权管理器 (AzMan)。该提供程序使用 XML 文件、Active Directory 或 Active Directory 应用程序模式 (ADAM) 中的一个 AzMan 策略存储作为其角色存储。它通常用于 Intranet 或 Extranet 方案中,其中 Windows 身份验证和 Active Directory 用于进行身份验证。

有关如何使用角色管理 API 的详细信息,请参阅 How To: Use Role Manager in ASP.NET 2.0

Cookieless 窗体身份验证

ASP.NET 2.0 支持 cookieless 窗体身份验证。该功能由 forms 元素的 cookieless 属性控制。该属性可以设置为以下四个值之一:

UseCookies。该值强制 FormsAuthenticationModule 类使用 Cookie 传输身份验证票。

UseUri。该值指示 FormsAuthenticationModule 类重写 URL 来传输身份验证票。

UseDeviceProfile。该值指示 FormsAuthenticationModule 类查看浏览器功能。如果浏览器支持 Cookie,则使用 Cookie;否则,重写 URL。

AutoDetect。该值通过一个动态检测机制指示 FormsAuthenticationModule 类检测浏览器是否支持 Cookie。如果检测逻辑表明不支持 Cookie,则重写 URL。

如果应用程序配置为使用 cookieless 窗体身份验证,并且正在使用 FormsAuthentication.RedirectFromLoginPage 方法,则 FormsAuthenticationModule 类自动设置 URL 中的窗体身份验证票。以下代码示例显示了典型 URL 在重写后的外观:

http://localhost/CookielessFormsAuthTest/(F(-k9DcsrIY4CAW81Rbju8KRnJ5o_gOQe0I1E_jNJ
LYm74izyOJK8GWdfoebgePJTEws0Pci7fHgTOUFTJe9jvgA2))/Test.aspx

括号中的 URL 部分包含 Cookie 通常将包含的数据。该数据在请求处理过程中由 ASP.NET 删除。该步骤由 ASP.NET ISAPI 筛选器执行,而不是在 HttpModule 类中执行。如果从一个 .aspx 页读取 Request.Path 属性,您在 URL 中不会看到任何额外的信息。如果重定向请求,URL 将自动重写。

注 难以保证 URL 中包含的身份验证票的安全。当安全性极为重要时,您应该使用 Cookie 存储身份验证票。

成员身份和登录控件

ASP.NET 2.0 引入了成员身份功能和一组登录 Web 服务器控件,它们简化了使用窗体身份验证的应用程序的实现。

成员身份为应用程序用户提供凭据存储和管理。它还提供一个成员身份 API,可以在使用窗体身份验证时简化用户凭据的验证任务。该成员身份功能构建于提供程序模型之上。该模型允许实现和配置指向不同用户存储的不同提供程序。ASP.NET 2.0 包括以下成员关系提供程序:

Active Directory 成员关系提供程序。该提供程序使用 Active Directory 或 Active Directory 应用程序模式 (ADAM) 用户存储。

SQL Server 成员关系提供程序。该提供程序使用 SQL Server 用户存储。

还可以添加对自定义用户存储的支持。例如,可以添加对其他轻量级目录访问协议 (LDAP) 目录或其他现有公共标识存储的支持。为此,创建一个从 MembershipProvider 抽象基类继承的自定义提供程序。

ASP.NET 登录控件自动使用成员身份和窗体身份验证,并封装提示用户输入凭据,验证用户,恢复或替换密码等所需的逻辑。实际上,ASP.NET 登录控件在窗体身份验证和成员身份上提供一个抽象层,并且取代了您使用窗体身份验证时通常必须进行的大多数或全部工作。

有关使用成员身份功能和登录控件的详细信息,请参阅 How To: Use Membership in ASP.NET 2.0

Web 场方案

在 Web 场中,无法确保哪个服务器将处理连续请求。如果用户在一台服务器上经过身份验证,但下一个请求在另一台服务器上进行,则身份验证票将导致验证失败并请求用户重新进行身份验证。

machineKey 元素中的 validationKey 和 decryptionKey 属性用于对窗体身份验证票进行哈希操作和加密。这些属性的默认值为 AutoGenerate.IsolateApps。这些密钥是针对每个应用程序自动生成的,在每台服务器上都不同。因此,在一台计算机上加密的身份验证票无法在 Web 场中的另一台计算机或者同一台 Web 服务器上的另一个应用程序中进行解密和验证。

为了解决该问题, Web 场中所有计算机上的 validationKey 和 decryptionKey 值都必须相同。有关配置 machineKey 元素的详细信息,请参阅 How To: Configure MachineKey in ASP.NET 2.0

其他资源

How To: Protect Forms Authentication in ASP.NET 2.0

How To: Use Forms Authentication with SQL Server in ASP.NET 2.0

How To: Use Forms Authentication with Active Directory in ASP.NET 2.0

How To: Use Forms Authentication with Active Directory in Multiple Domains in ASP.NET 2.0



原文地址:http://www.microsoft.com/china/msdn/library/webservices/asp.net/PAGExplained0002.mspx?mfr=true
posted @ 2007-01-04 10:06 yiki 阅读(85) 评论(0) 编辑
 

问题:
网站有bbs.xxx.com / main.xxx.com / pay.xxx.com 这三个不同的二级域名站,要实现任何一二级域名处登陆,其他各二级域名的站均登陆。
解决办法:
设置cookie的domain属性。
详细介绍:
把保存用户登陆信息的cookie的域设置成一样即可。
usercookie.Domain=".xxx.com"; 要注意,在域名前必须要加一个"."。
这个设置在登陆和退出的时候都需要。
比较普遍的做法自然是用一个专门负责用户登陆的类来管理这些事务。这样其他登陆页面只要调用这个类就可以,一通百通。
而如果你使用asp.net 2.0 和forms验证,则有更简单的办法来完成这个设置。
web.config中添加了对域的支持,只要按照如下的设置即可:
<authentication mode="Forms">
<forms loginUrl="Login.aspx" domain=".xxx.com"  name=".AutoLinkEduSiteCookie"/>
</authentication>
domain:可选的属性。
指定在传出 Forms 身份验证 Cookie 中设置的可选域。此设置的优先级高于 httpCookies 元素中使用的域。
此属性是 .NET Framework 2.0 版中的新属性。默认值为空字符串 ("")。

注意
无论是哪一种办法,cookie的name一定要相同。


转载地址:http://www.knowsky.com/print.asp?id=340123

posted @ 2007-01-04 09:59 yiki 阅读(69) 评论(0) 编辑
asp.net 2.0中,新增了许多新的功能和控件。其中,新增的登陆控件可谓使得WEB应用的设计更加得心应手。什么是登陆控件呢?就是我们平常在WEB应用中经常要用到的用户注册,登陆,忘记密码,登陆后根据权限的不同而显示不同的页面等功能,现在在asp.net 2.0中都已经可以由提供的控件来实现了。ASP.NET 2.0中的登陆控件比较多,封装了大部分WEB应用中要实现系统登陆的系列功能,涉及到很多方面,本文将简单介绍一下它们的主要功能。

  首先,asp.net 2.0中,为了更方便创建和管理用户,以及对 Web 应用程序中的页进行密码保护,引进了新的框架membership。新的框架包含用于处理身份验证和授权的新增功能,能够同时满足 Web 站点管理员和开发人员的需要。Web 站点管理员可以利用新的 Web 站点管理工具来创建新的用户和角色,以及控制对 Web 应用程序中页面的访问。Web 站点管理工具是一组预先编写的 ASP.NET页,不具备编程技巧的用户可以使用它们来配置 Web 应用程序。利用membership API,编程人员可以很方便地使用拖拉控件的方法,再加以少量的代码,就可以完全实现对用户,角色权限等的管理,还可以自定义做扩充。

  其中,membership框架中包含了两个默认的用户验证程序:默认的 AccessMembershipProvider以及 SqlMembershipProvider。如果希望在 Microsoft SQL Server 数据库中存储成员身份信息,则可以将应用程序配置为使用 SqlMembershipProvider,如果要使用access数据库,则使用AccessMembershipProvider。还可以创建自定义成员身份提供程序。例如,如果需要在 XML 文件、或 Oracle 数据库中存储成员身份信息,希望创建自己的成员身份提供程序,则需要实现抽象类MembershipProvider 的所有方法和属性。本文中,将以access数据库为例子做说明,所以使用Accessmembershipprovider。

  我们可以使用asp.net 2.0自带的WEB站点安全配置工具,就可以无需编程,在系统向导的带领下,自动建立一个存储用户角色身份的数据库了。比如,我们现在需要建立一个access的数据库,里面存储用户名,密码,密码提示问题,用户EMAIL等资料,则可以通过web站点安全配置工具来实现。步骤如下:

  1)先使用visual studio express 2005建立一个WEB站点,命名为Dorknozzle,使用VB.NET语言,之后,我们点选Visual studio express中的WebSite菜单中的ASP.NET Configuration,这时会弹出一个新的页面窗口,其中选择Security选项,如下图:


  2) 从上图可以看到,安全设置选项被分为三部分:用户,角色和规则。由于我们是第一次配置,选择“To configure security step by step, use the Security Setup Wizard”,在出现的界面点“NEXT”进入下一步。

  在第二步中,你可以选择验证的类型,比如你的站点是发布在internet还是intranet,再点next进入下一步,系统会提示目前是使用AspnetAccessProvider ,继续点下一步,系统会询问是否创建基于角色的应用,如果应用中要用到角色,就必须将那个单选框打勾,本文不选择该项,继续下一步。

  3) 此时,系统会询问我们输入创建用户的信息,如下图,比如输入用户名,密码,EMAIL等信息。在输入后,点NEXT进入下一步。


  4)系统会允许你对某一用户进行有关规则的详细设置,比如允许某一用户行驶或拒绝使用某角色等。本文将忽略不用,继续选NEXT进下一步,此时,完成了系统的初步配置了。

  此时,AccessMembershipProvider会自动在当前工程的data目录下,创建一个AspNetDB的ACCESS数据文件,打开它,会发现自动生成了相关的表格,比如有aspnet_users表,存放用户的基本信息,aspnet_membership表,存放用户加密的密码和其他信息。

  在进行完上面的步骤后,下面我们开始认识并使用asp.net 2.0中的登陆控件。这些控件在工具箱的Login选项组中,共有Login, LoginView, PasswordRecovery, LoginStatus, LoginName, CreateUserWizard, ChangePassword 共7个,下面逐一介绍:

 Login控件

  拖拉一个Login控件到窗体中,就发现和平时见到的登陆窗口差不多了,在该控件的属性窗口中,比较重要的是DestinationPageUrl 属性,该属性将指定用户在登陆后跳转的页面,而FailureText表示登陆失败后提示的文本,CreateUserText表示创建新用户的提示文本,PasswordRecoveryText表示提示密码的文本,PasswordRecoveryUrl表示实现密码提示恢复功能的链接,此外可以设置其很多属性,比如显示用户名,密码的文本,登陆按钮PasswordRecoveryUrl的文本等,都十分简单,大家可以去试一下,初步设置属性后的登陆框如下图:


  Loginname和LoginStatus控件

  一般的登陆模块,当用户在成功登陆后,会显示用户当前登陆的身份,比如“欢迎XXX用户登陆”的提示,而同时会显示“LOGOUT(退出)”的提示,这时,我们可以通过Loginname和loginstatus控件来实现,在index.aspx页中,加入

<form runat="server">
Welcome <asp:LoginName id="lnUser" runat="server"/>, your login was successful!<br /><br />
<asp:LoginStatus id="lsUSer" runat="server"/>
</form>

  则当用户登陆后,会显示如下:


  LoginView控件

  另一十分有用的控件就是loginview。它会根据用户角色的不同而显示不同的登陆后的内容。比如,做为管理员在登陆后,希望能看到所有部门的人员信息,而只希望某一部门的人员看到只属于该部门的信息,LoginView控件可以做到这点。本文的例子中,希望系统显示给匿名访问者的信息是希望他们注册用户,而对于已经登陆的用户,则显示欢迎他们再次访问的文字。此时要用到Loginview控件的AnonymousTemplate模版和LoggedInTemplate模版。AnonymousTemplate模版用于显示给匿名用户时的提示信息,LoggedInTemplate模版用于显示给已经登陆用户的提示信息,代码如下:

<asp:LoginView id="lvDorknozzle" runat="server">
<LoggedInTemplate>
Welcome <asp:LoginName id="lnUser" runat="server"/>
</LoggedInTemplate>
<AnonymousTemplate>
Welcome to the Dorknozzle site!<br />
Please click on the New User link to register on our site.
</AnonymousTemplate>
</asp:LoginView>

  运行如下图:

 CreateUserWizard控件

  使用该控件,可以很方便地一步步引导用户进行注册。拖拉其到设计窗体中,如下图:


  当然,你可以对其属性进行更多地设置,比如,设置HeaderTemplate(头模版),StartNavigationTemplate(开始注册模版),FinishNavigationTemplate(结束注册模版)等,具体设置可以参考在线帮助。而该控件还允许当注册成功后向用户发邮件,比如:

<asp:CreateUserWizard id="CreateUserWizard1" runat="server">
<MailDefinition
BodyFileName="NewUserEmail.txt"
From="welcome@dorknozzle.com"
Subject="Welcome to the Dorknozzle site!"/>
</asp:CreateUserWizard>

  而发邮件则需要在web.config文件中配置,比如:

<configuration>
<system.web>
<authentication mode="Forms"/>
<smtpMail serverName="Localhost"/>
</system.web>
</configuration>

  PasswordRecovery密码恢复控件

  该控件可以实现简单地密码提示恢复功能,并将密码email给用户,代码如下:

<asp:PasswordRecovery id="prForgotPass" runat="server"> </asp:PasswordRecovery>

  实现时如下图所示:


  用户输入用户名后,则需要输入密码恢复的答案,如下图:


  并在该控件中设置将恢复后的密码EMAIL给用户

<asp:PasswordRecovery id="prForgotPass" runat="server">
<MailDefinition
BodyFileName="forgotpassword.txt"
From="helpdesk@dorknozzle.com"
Subject="Word has it, you forgot your password?"/>
</asp:PasswordRecovery>

  ChangePassword 更改密码控件

  更改密码控件可以很方便地实现密码的更改,包括一个 MailDefinition 属性。如果将值赋给 MailDefinition 属性,则 ChangePassword 控件在密码成功更改时将自动向用户发送一个电子邮件。

  

  直接使用Membership API

  有时候,需要对成员身份进行更高级别(比 Web 站点管理工具或 Login 控件提供的控制级别高)的控制。在这些情况下,可以直接使用 Membership API。

  Membership API 是通过 Membership 类公开的。Membership 类包含的方法使您能够完成以下工作:创建新用户;更改密码;搜索与特定条件匹配的用户等等。在幕后,Login 控件使用这些方法与已配置的成员身份提供程序进行交互。

  下面列出了 Membership 类的一些比较重要的方法:

   CreateUser — 使您能够创建新用户。

   DeleteUser — 使您能够删除现有用户。

   FindUsersByEmail — 使您能够检索与某个特定的电子邮件地址匹配的用户集合。

   FindUsersByName — 使您能够检索与某个特定的用户名匹配的用户集合。

   GeneratePassword — 使您能够生成随机密码。

   GetAllUsers — 使您能够检索成员身份提供程序中存储的所有用户。

   GetNumberOfUsersOnline — 使您能够返回当前正在访问 Web 应用程序的用户数。

   GetUser — 使您能够检索与当前用户相关联的成员身份信息,或者使您能够检索与某个已经提供用户名的用户相关联的成员身份信息。

   GetUsernameByEmail — 使您能够检索具有某个特定电子邮件地址的用户的用户名。

   UpdateUser — 使您能够更新特定用户的信息。

   ValidateUser — 使您能够根据成员身份提供程序对用户进行身份验证。

  最后,我们利用该API,实现稍微复杂的功能。在index.aspx中,添加一个label控件,显示在线的人数,代码如下:

Number of Users Online:
<asp:Label id="lblNumUsersOnline" runat="server"/>

  在PAGE-LOAD事件中,增加代码如下:

Sub Page_Load(s As Object, e As EventArgs) Handles MyBase.Load
lblNumUsersOnline.Text = _
Membership.GetNumberOfUsersOnline().ToString()
End Sub

  则可以显示在线人数。我们并且再添加一个gridview,显示所有用户的在线状况,是否允许某种权限和相关的信息。

<asp:GridView id="gvUsers" runat="server" AutoGenerateColumns="False">
<Columns>
<asp:BoundField HeaderText="Username" DataField="Username" />
<asp:BoundField HeaderText="Is Online?" DataField="IsOnline" />
<asp:BoundField HeaderText="Is Approved?" DataField="IsApproved" />
<asp:BoundField HeaderText="Email" DataField="Email" />
</Columns>
</asp:GridView>

  并将gridview绑定:

gvUsers.DataSource = Membership.GetAllUsers() ;
gvUsers.DataBind();

  最后运行结果如下图:


  总结: 本文以实际例子,介绍了asp.net 2.0中新增加的登陆系列控件的简单用法以及新增加的Membership API的简单使用,更多的请参考MSDN或正式版时的有关介绍。


转载地址:http://dev.csdn.net/Develop/article/28/75533.shtm
posted @ 2007-01-04 09:51 yiki 阅读(68) 评论(0) 编辑

从严格意义上来说,ASP.NET 2.0 的成员资格、角色管理授权和 .NET 角色安全性没有多大关系。只不过,Microsoft 替我们完成了一些原本需要我们自己进行的工作而已。

在这两种新的技术中使用的"提供程序模型"倒是值得我们好好学习一下,因为这个 IoC 概念非常相似。


成员资格

成员资格提供了通用的用户管理功能,诸如注册、登录、找回密码等,加上与之配套的可视化控件,我们“几乎”不用在编写额外的代码就可以工作。实际上真是如此吗?MemebershipUser 属性太少,显然不大适合我们的商业应用;注册和登录控件有缺乏验证码,缺乏安全性;…… 当然我们还可以使用自定义验证和提供程序,不过如此一来还不如自己参考"提供者模型"开发一套呢,毕竟独立的 Passport 才是我们所需要的,更不要说和既有系统进行整合了,总之 ASP.NET 提供的这个新功能,食之无味,弃之可惜。有关成员资格和角色管理的使用不是本文的重点,详情可参考 MSDN 文档。

成员资格由 Membership、MembershipUser、MembershipProvider 组成,Membership 是个静态类,为用户提供了大量用户相关的操作方法,MembershipUser 则是用户实体类,除了用户属性外也有一些用户自身的操作方法,而 MembershipProvider 自然就是提供程序的基础抽象类了,它规范了提供程序的接口。

 单击在新窗口中浏览图片

我们分析一下 Membership 代码(局部代码),看看如何获取真实的目标提供程序对象的。其实过程也很简单,读取配置获取类型信息,然后用反射创建目标提供程序对象实例。

System.System.Web.Security.Membership

复制C#源代码
// 我们从 ValidateUser 方法入手。
public static bool ValidateUser(string username, string password)
{
    // 通过 Provider 属性获取提供程序对象
    return Membership.Provider.ValidateUser(username, password);
}
public static MembershipProvider Provider
{
get
{
        // 应该是通过 Initialize() 方法获取,并赋值给 s_Provider 变量的。
        Membership.Initialize();
return Membership.s_Provider;
}
}
private static void Initialize()
{
    // 从配置文件中读取配置信息
    RuntimeConfig config1 = RuntimeConfig.GetAppConfig();
MembershipSection section1 = config1.Membership;
Membership.s_Providers = new MembershipProviderCollection();
    // 使用 ProvidersHelper.InstantiateProviders 方法从配置信息中读取设置。
    ProvidersHelper.InstantiateProviders(section1.Providers, Membership.s_Providers, typeof(MembershipProvider));
    // 将缺省提供程序对象赋值给 s_Provider 变量。
    Membership.s_Provider = Membership.s_Providers[section1.DefaultProvider];
Membership.s_Initialized = true;
}
public static void ProvidersHelper.InstantiateProviders(ProviderSettingsCollection configProviders, ProviderCollection providers, Type providerType)
{
    // 循环读取配置信息,并创建提供程序对象。
    foreach (ProviderSettings settings1 in configProviders)
{
providers.Add(ProvidersHelper.InstantiateProvider(settings1, providerType));
}
}
public static ProviderBase ProvidersHelper.InstantiateProvider(ProviderSettings providerSettings, Type providerType)
{
ProviderBase base1 = null;
try
{
        // 调用 HttpRuntime.CreatePublicInstance 创建提供程序对象。
        base1 = (ProviderBase) HttpRuntime.CreatePublicInstance(type1);
}
catch (Exception exception1)
{
}
return base1;
}
internal static object HttpRuntime.CreatePublicInstance(Type type)
{
    // 利用反射创建对象
    return Activator.CreateInstance(type);
}

下面我们分析一下 Login 控件的源码,我们会发现其内部不过是自动调用 Membership 和 MembershipProvider 进行操作而已。

System.Web.UI.WebControls.Login

复制C#源代码
private void AttemptLogin()
{
if ((this.Page == null) || this.Page.IsValid)
{
LoginCancelEventArgs args1 = new LoginCancelEventArgs();
this.OnLoggingIn(args1);
if (!args1.Cancel)
{
AuthenticateEventArgs args2 = new AuthenticateEventArgs();
            // 调用核心代码
            this.OnAuthenticate(args2);
            // 看到了什么?标准的身份验证代码。详情可以查看上一篇Blog。
            if (args2.Authenticated)
{
FormsAuthentication.SetAuthCookie(this.UserNameInternal, this.RememberMeSet);
this.OnLoggedIn(EventArgs.Empty);
this.Page.Response.Redirect(this.GetRedirectUrl(), false);
}
else
{
this.OnLoginError(EventArgs.Empty);
if (this.FailureAction == LoginFailureAction.RedirectToLoginPage)
{
FormsAuthentication.RedirectToLoginPage("loginfailure=1");
}
ITextControl control1 = (ITextControl) this.TemplateContainer.FailureTextLabel;
if (control1 != null)
{
control1.Text = this.FailureText;
}
}
}
}
}
protected virtual void OnAuthenticate(AuthenticateEventArgs e)
{
AuthenticateEventHandler handler1 = (AuthenticateEventHandler) base.Events[Login.EventAuthenticate];
if (handler1 != null)
{
        // 用户使用了自定义身份验证服务
        handler1(this, e);
}
else
{
        // 使用成员资格提供程序
        this.AuthenticateUsingMembershipProvider(e);
}
}
private void AuthenticateUsingMembershipProvider(AuthenticateEventArgs e)
{
    // 使用 LoginUtil.GetProvider 获取成员资格提供程序对象,并调用其 ValidateUser 进行身份验证。
    e.Authenticated = LoginUtil.GetProvider(this.MembershipProvider).ValidateUser(this.UserNameInternal, this.PasswordInternal);
}
internal static LoginUtil.MembershipProvider GetProvider(string providerName)
{
if (string.IsNullOrEmpty(providerName))
{
return Membership.Provider;
}
    // 调用 Membership 相关属性,获取提供程序对象。
    MembershipProvider provider1 = Membership.Providers[providerName];
if (provider1 == null)
{
throw new HttpException(SR.GetString("WebControl_CantFindProvider"));
}
return provider1;
}


角色管理授权

在实际开发中,除了用户(User)和角色(Role)以外,我们还需要权限(Permission)这个概念。角色更像是用户组(Users Group),用户可以加入一个或多个用户组,每个用户组又拥有多个权限。有了权限,我们就可以动态赋予用户组不同的权力,比如我们可以临时授予 A 组执行 XX 的权力,三天后我们再取消该权力,显然没有权限的设计会缺乏灵活性。

ASP.NET 2.0 的角色管理授权,由 RoleManagerModule、Roles、RoleProvider、RolePrincipa 共同组成。系统会自动载入 RoleManagerModule 这个 HttpModule,Roles 为用户提供角色相关的操作方法,而 RoleProvider 自然是提供程序的抽象类了。至于 RolePrincipa 干什么用,看看下面的代码自然就明白了。

System.Web.Security.RoleManagerModule

复制C#源代码
private void OnEnter(object source, EventArgs eventArgs)
{
    // ...
    // 注意下面的代码, RoleManagerModule 使用 RolePrincipal 替代了缺省的 GenericPrincipal。
    if (!(context1.User is RolePrincipal))
{
context1.User = new RolePrincipal(context1.User.Identity);
}
}

转载地址:http://www.chenjiliang.com/Article/View.aspx?ArticleID=1428&TypeID=84

posted @ 2007-01-04 09:30 yiki 阅读(796) 评论(1) 编辑