成员和角色管理器提供程序--现在asp.net 2.0包含了内建的成员和角色管理服务。由于这些服务都是提供程序驱动的(provider-driven),你可以轻易地变更它,或者用自定义实现来代替它。
登录控件--新的登录控件为站点的基于认证和授权的ui(例如登录窗体、创建用户窗体、密码取回、已登录用户或角色的定制ui)提供了基本模块。这些控件利用asp.net 2.0中的内建的成员和角色服务与站点所定义的用户和角色信息交互操作。
大多数web应用程序的一个重要的部分是辨别用户并控制资源的访问权。检测请求的实体(entity)身份的操作就是认证(authentication)。通常,为了进行认证,用户必须提供凭证,例如帐号/密码。一旦认证过的身份是有效的,就必须检测该身份是否能够访问指定的资源,这个过程就是授权(authorization)。asp.net与iis一起为应用程序提供认证和授权服务。
com对象的一个重要特性就是,它能够控制那些运行com对象代码的身份。当com对象用请求的实体身份运行代码的时候,就称为模仿(impersonation)。asp.net框架组件应用程序可以选择模仿请求。
有些应用程序还希望根据请求的身份或者根据请求的身份所属的角色来动态地定制内容。asp.net框架组件应用程序可以动态地检测当前请求的身份是否属于某种角色。例如,应用程序可能希望检测当前用户是否属于管理员角色,以便为管理员有条件地生成内容。
asp.net 2.0的成员特性使你创建和管理用户更加容易了。成员特性一般与另外一个叫做角色管理器的新特性一起运作。角色管理器为创建角色和给角色指定用户提供了下层结构。当成员、角色管理器特性和窗体认证一起工作的时候,asp.net 2.0就可以为创建、认证和授权用户提供端对端的支持。
成员和角色管理器都是用基于提供程序的模型设计的。提供程序从特性所暴露的类和业务逻辑中提取特性的物理数据存储。成员和角色管理器特性都带有microsoft sql server提供程序。成员特性还带有一个用于处理活动目录和活动目录应用程序模式(adam)的提供程序。角色管理器特性带有一个能利用windows server 2003授权管理特性的提供程序。你可以建立自定义的提供程序,并配置它与成员和角色管理器特性一起使用。使用自定义提供程序的时候,利用成员和角色管理器特性的页面仍然会继续工作,毫无改变。
登录控件是一组自定义的服务器控件,它为认证和授权事务提供了公用的用户界面。登录控件利用了成员、角色管理器和窗体认证特性中的功能。
认证和授权
asp.net与iis一起支持使用基本的、digest和windows认证。asp.net支持微软passport认证服务,它支持单点登录服务和用户配置服务。asp.net还支持一种使用基于窗体认证的强大的服务。基于窗体的认证使用cookie来认证用户,并允许应用程序执行自己的凭证验证过程。
我们要认识到,asp.net认证服务是受到iis提供的认证服务制约的。例如,为了在iis应用程序中使用基本认证,你就必须使用internet服务管理工具来配置应用程序以使用基本认证。
asp.net提供了两种授权服务:
· 检查acl(访问控制列表)或资源权限,看某个认证过的用户是否能够访问该资源。
· url授权,它批准一个身份使用一定的web空间。
为了演示它们的差别,我们来看一个例子,假设某个应用程序允许匿名用户使用iusr_mymachine帐号访问。当某个asp.net页面(例如"/default.aspx")的请求通过认证之后,就会依据该文件(例如"c:/inetpub/wwwroot/default.aspx")的acl进行检查,看iusr_mymachine帐号是否有权限读取这个文件。如果有权限,就对访问进行授权。如果web内容位于ntfs卷中,并且已经配置了虚拟目录使用windows认证,文件的授权就会自动地执行。
对于url授权来说,会依据asp.net应用程序的配置数据来进行检查。如果允许访问被请求的url,请求就获得授权了。在例子中,asp.net检查匿名用户是否有访问/default.aspx的权限(也就是说,检查过程是依据url本身的,没有依据url最终解析成的文件)。
这种差别看起来很细微,但是它让应用程序能够使用类似基于窗体的认证或passport认证,在这些认证模式中用户不需要与计算机或域帐号相对应。它还允许你进行虚拟资源的授权(在资源下方并没有物理文件)。例如,应用程序可以把所有对.stk文件的请求映射给一个处理程序,它根据查询字符串中的变量来进行证券报价。在这种情况下,没有物理的.stk文件可供acl检查,因此使用url授权来控制虚拟资源的访问权。
文件授权通常依赖iis提供的通过认证的帐号来执行。如果允许匿名访问,它就是配置的匿名帐号。否则,就是nt帐号。它的工作方式跟asp是一样的。
在资源管理器属性页面的"安全"选项卡中可以设置文件或目录的acl(访问控制列表)。url授权被配置为asp.net框架组件应用程序的一部分,在"授权用户和角色"部分有完整的讲解。
为了激活asp.net的认证服务,你必须在应用程序的配置文件中配置<authentication>元素。这个元素可以包含下表列举的任何值。
值 | 描述 |
none | 没有激活的asp.net认证服务。请注意iis认证服务仍然存在。 |
windows | asp.net认证服务给当前请求附加上windowsprincipal (system.security.principal.windowsprincipal),以保证根据nt用户或组进行授权。 |
forms | asp.net认证服务管理cookie并把未认证的用户重定向到登录页面。它通常在iis允许匿名访问应用程序的时候使用。 |
passport | asp.net认证服务提供了passport sdk (你必须安装)的一个方便的包装。 |
例如,下面的配置文件允许应用程序使用基于窗体(cookie)的认证:
<configuration>
<system.web>
<authentication mode="forms"/>
</system.web>
</configuration>
使用登录控件
下面的例子演示了在应用程序中如何使用登录控件。
创建和登录用户
在例子中我们会看到站点的主页,它包含了一个loginstatus控件,该控件提示用户登录站点。这个页面上的loginstatus控件检查用户当前是否通过了认证,并向用户显示一个登录链接。用户点击这个链接就可以看到默认的login.aspx页面,在web.config中已经把这个页面配置为窗体认证。login控件显示在login.aspx页面上(请注意,在默认的登录页面上登录控件的visiblewhenloggedin属性会被忽略)。在例子中,登录控件设置了额外的属性,显示了"创建用户"链接,点击这个链接会访问另外一个页面,那个页面使用了createuserwizard控件。在默认情况下,createuserwizard控件包含两个步骤,在第一步中用户输入必要的信息,当他们点击"创建用户"按钮的时候,控件把这些信息传递给成员api。如果成员api不能建立该用户,在控件中会显示适当的错误信息;如果用户创建成功,控件就载入向导的第二步。在例子中,continuedestinationpageurl属性被设置为在用户创建成功之后返回主页。在默认情况下,当用户被成功创建之后,createuserwizard会认证并登录用户。当用户返回到主页的时候,他们会注意到loginstatus被删除了,他们已经通过了认证,并显示了一个登出链接。点击登录链接会引起用户认证票据(ticket)被清除,并显示登录链接。这时用户可以点击登录链接,由于他们已经创建了用户帐号,所以可以在login.aspx上输入用户名和密码来登录网站。你可能注意到login控件显示了一个"记住帐号(remember me)"检查框。选中这个框并成功登录之后,会向用户的计算机上写入一个cookie,该cookie默认的存续期是50年。你可以通过把login控件的displayrememberme和remembermeset属性设置为false来禁用这个选项。查看示例的代码,你可以发现这项事务并没有任何代码,只设置了少许的几个属性。这些控件的样式属性都是站点应用的样式表设置的。
login.aspx
<%@ page language="vb" masterpagefile="~/site.master"%>
<asp:content id="content1" contentplaceholderid="mainbody" runat="server">
<asp:login id="login1" runat="server" createuserurl="createuser.aspx" createusertext="create a new account" />
</asp:content>
createuser.aspx
<%@ page language="vb" masterpagefile="~/site.master"%>
<asp:content id="content1" contentplaceholderid="mainbody" runat="server">
<asp:createuserwizard id="createuserwizard1" runat="server" continuedestinationpageurl="home.aspx"/><br />
<a href="home.aspx">return to default home page</a><br />
<a href="homeloginview.aspx">return to loginview home page</a><br />
<a href="homechangepassword.aspx">return to changepassword home page</a><br />
</asp:content>
向认证用户显示不同的内容
下面的例子演示了使用loginview控件为认证过的用户和匿名用户显示不同的内容。尽管例子中没有显示什么,但是loginview控件支持基于用户角色来显示不同内容。loginview控件中的anonymoustemplate模板包含了一个登录控件,loggedintemplate模板包含了loginname控件。loginname控件利用格式化字符串属性来显示欢迎和用户姓名。请使用上一个例子中创建的帐号或重新创建一个帐号来登录站点,并点击页面上方的登出链接。
<%@ page language="vb" masterpagefile="~/site.master"%>
<asp:content id="content1" contentplaceholderid="mainbody" runat="server">
<asp:loginview id="loginview1" runat="server">
<loggedintemplate>
<h1>
<asp:loginname id="loginname1" runat="server" formatstring="welcome {0}" />
</h1>
</loggedintemplate>
<anonymoustemplate>
<h1>welcome to login controls</h1>
<asp:login id="login1" runat="server" createuserurl="createuser.aspx" createusertext="create a new account" />
</anonymoustemplate>
</asp:loginview>
</asp:content>
修改密码
在默认情况下,changepassword控件要求用户通过了站点的认证才能更改他们的密码。但是,在下面的例子中,我们把displayusername属性设置为真,其结果是用户在改变自己的密码之前,可以由changepassword控件进行认证,或者通过站点认证的用户输入不同的帐号来改变密码。例子还链接到了创建用户页面,使你能够创建有效的用户并测试示例。
<%@ page language="vb" masterpagefile="~/site.master"%>
<asp:content id="content1" contentplaceholderid="mainbody" runat="server">
<asp:changepassword id="changepassword1" runat="server" createuserurl="createuser.aspx" createusertext="create a new account" canceldestinationpageurl="homechangepassword.aspx" displayusername="true" continuedestinationpageurl="homechangepassword.aspx"/>
</asp:content>
使用成员和角色管理器api
成员
成员特性是围绕两个核心类构建的:membership和membershipuser。membership类提供创建用户(membershipuser类处理)的方法,以及通用的管理用户的方法。用membership类建立的用户是通过asp.net应用程序认证的身份。
membership类执行的通用事务包括:
· 创建新的membershipuser
· 当用户试图登录的时候验证用户名-密码组合。接下来你可以使用窗体认证来生成一个cookie,表明用户登录了站点。
· 检索membershipuser实例
· 更新membershipuser实例
· 根据不同的条件搜索用户
· 获取当前在线的通过认证的用户
· 在不需要用户的时候从系统中删除它
一旦你获取了membershipuser实例,就可以直接使用membershipuser类执行下面的事务:
· 访问应用程序中的membershipuser类的属性
· 检索用户的密码(只有把成员特性配置为允许密码检索才可以使用)
· 改变或重置用户的密码
· 改变用户的密码提问和答案(如果成员特性被配置为在检索或更新密码之前,提示用户密码问题和答案)
· 解锁那些因为密码错误或密码答案错误而被锁定的用户
角色管理器
角色管理器的核心类是roles类。roles为创建角色和把用户指定给角色提供方法。它也提供了用于管理角色信息的方法。
使用roles类可以执行的通用事务包括:
· 创建新角色
· 删除已有的角色
· 把用户指定给角色
· 从角色中删除用户
· 检测某个用户是否获得了特定角色的授权
· 搜索特定角色中的用户,检索角色中的所有用户
· 获取特定用户的角色信息
角色管理器特性也包含了httpmodule。这个模块负责检索用户分配的角色并把这些信息存储在roleprincipal内,而它存在于页面的httpcontext中。httpcontext中存在roleprincipal使你能够利用<authorization>元素来保护页面和目录。依据roleprincipal中存储的角色信息,用户只能获得站点内特定页面和目录的访问权。
示例
下面的例子演示了在应用程序中如何使用成员 api。
创建新用户
下面的例子演示了如何建立新的membershipuser。示例使用了membership.createuser重载,它返回一个状态参数。其它的重载也可以使用,他们会抛出异常而不是返回状态代码。请注意,在默认情况下,成员特性要求密码至少有7个字符长度,并且密码至少包含一个非数字字符。
<script runat="server">
sub btncreate_click(byval sender as object, byval e as system.eventargs)
dim username as string = txtuserid.text
'这个值式加密的或散列过,不会显示
dim password as string = txtpassword.text
dim email as string = txtemail.text
dim passwordquestion as string = ddlpasswordquestion.selectedvalue
'这个值式加密的或散列过,不会显示
dim passwordanswer as string = txtpasswordanswer.text
dim result as membershipcreatestatus
membership.createuser(username, password, email, passwordquestion, passwordanswer, true, result)
lblresults.visible = true
select case result
case membershipcreatestatus.success
txtuserid.text = nothing
txtpassword.text = nothing
txtemail.text = nothing
ddlpasswordquestion.selectedindex = -1
txtpasswordanswer.text = nothing
lblresults.text = "user successfully created!"
case membershipcreatestatus.invalidusername
lblresults.text = "the username format was invalid. please enter a different username."
case membershipcreatestatus.invalidpassword
lblresults.text = "the password was invalid: a password cannot be an empty string and must also meet the pasword strength requirements of the configured provider. please enter a new password."
case membershipcreatestatus.invalidemail
lblresults.text = "the email format was invalid. please enter a different username."
case membershipcreatestatus.invalidquestion
lblresults.text = "the password question format was invalid. please enter a different question."
case membershipcreatestatus.invalidanswer
lblresults.text = "the password answer format was invalid. please enter a different answer."
case membershipcreatestatus.duplicateusername
lblresults.text = "the username is already in use. please enter a new username."
case membershipcreatestatus.duplicateemail
lblresults.text = "the email address is already in use. please enter a different email address."
case else
lblresults.text = "an error occurred while creating the user."
end select
end sub
</script>
用户登录和访问用户属性
下面的例子演示了用户使用membership.validateuser方法登录。它还演示了在登录用户的时候如何同时使用窗体认证和成员特性。在上面的例子中创建用户之后,请在登录页面上输入凭证。一旦你登录了,你会被重定向到一个页面,该页面利用membership.getuser来检索与登录用户相对应的membershipuser实例。同时请注意,这个页面还显示了目录上设置的用户属性,这些内容只有通过认证的用户才能访问。点击页面底部的登出链接可以退出站点。
<script runat="server">
protected memuser as membershipuser
sub page_load(byval sender as object, byval e as system.eventargs)
memuser = membership.getuser()
end sub
sub linklogout_click(byval sender as object, byval e as system.eventargs)
formsauthentication.signout()
roles.deletecookie()
formsauthentication.redirecttologinpage()
end sub
</script>
user name/id: <% = server.htmlencode(memuser.username) %>
email:<% = server.htmlencode(memuser.email) %>
更新用户属性
请用前面建立的用户凭证登录。页面会用asp.net 2.0中新的detailsview控件显示用户属性。detailsview控件与一个数据源控件通讯。在例子中,objectdatasource控件检索membershipuser实例的内容。你可以点击页面底部的"编辑"链接使detailsview进入编辑模式。membershipuser的电子邮件和注释都可以修改。点击"更新"链接可以把新值保存到数据库。请注意,在代码中页面实现了itemupdating事件,该事件是由objectdatasource引发的。这样做是必要的,membershipuser类没有参数化构造函数,它要求使用objectdatasource的双向数据绑定。点击登出链接可以退出。
sub detailsview1_itemupdating(byval sender as object, byval e as detailsviewupdateeventargs)
'必须手动处理更新操作,因为membershipuser 没有参数化的构造函数
dim memuser as membershipuser = membership.getuser()
memuser.email = cstr(e.newvalues(0))
memuser.comment = cstr(e.newvalues(1))
try
membership.updateuser(memuser)
e.cancel = true
detailsview1.changemode(detailsviewmode.readonly)
catch ex as exception
response.write("<div>the following error occurred:<font color='red'> " + ex.message + "</font></div>")
e.cancel = true
end try
end sub
帐号锁定
membership特性自动地跟踪用户重试密码的次数。在检索密码或重置密码的时候,它也跟踪密码重试的次数。下面的例子演示了自动的帐号锁定能力,以及如何取消帐号锁定。首先使用前面的"建立新用户"示例创建一个新帐号。接着,点击下方的按钮运行"帐号登出"示例。登录页面显示了显示了为了锁定帐号需要重试的失败次数。在登录页面上,使用你建立的第一个帐号并输入错误的密码。请注意,在重试的失败次数到了之后,如果你使用了正确的密码,也不能登录了--这是因为在重试失败的次数到了一定的数量之后,membership特性自动地锁定的帐号。为了解除该帐号的锁定,请使用你建立的第二个帐号登录。显示的页面与前面的显示用户属性的例子很相似。但是,这个页面允许你在页面底部输入任意的用户名称。请输入被锁定的帐号并回车。detailsview控件会刷新并显示该用户的信息。请注意,标识锁定状态的检查框islockedout是选中的。lastlockoutdate也被更新了,它显示了用户被锁定的日期。点击页面底部的"解锁"按钮来解除当前显示的用户的锁。它调用了membershipuser实例的unlockuser方法,解除了用户的锁。在解除用户的锁之后,islockedout检查框被清除了,lastlockoutdate属性也被重置了。点击页面底部的登出链接。现在尝试用第一个帐号登录。现在可以再次成功登录了。
sub btnunlockuser_click(byval sender as object, byval e as system.eventargs)
dim memuser as membershipuser = membership.getuser(txtusername.text)
if (not memuser is nothing and memuser.islockedout = true)
memuser.unlockuser()
end if
'刷新被选中用户的信息
detailsview1.databind()
end sub
删除用户
你可以使用membership.deleteuser方法删除用户。下面的例子演示了如何使用窗体认证删除当前登录的用户并让该用户登出。
<script runat="server">
sub btndeletecurrentuser_click(byval sender as object, byval e as system.eventargs)
if (membership.deleteuser(user.identity.name)) then
formsauthentication.signout()
roles.deletecookie()
response.redirect("~/creatingusers.aspx")
else
lblresult.visible = true
lblresult.text = "the membership user was not deleted."
end if
end sub
</script>
管理角色
下面的例子演示了认证用户如何使用角色管理器特性。所有的示例页面都拒绝匿名用户访问。在默认情况下,asp.net中是没有激活角色管理器特性的。但是,下面的例子中使用的web.config显式地激活了角色管理器特性。
添加和删除角色
下面的例子演示了如何使用roles.createrole和roles.deleterole方法建立和删除角色。在你建立角色或删除已有角色之后,页面使用roles.getallroles方法显示系统中的所有可用角色。roles.getallroles的返回值可以轻易地绑定到任何支持数据绑定的控件。你至少需要建立一个叫做"administrators"的角色。
在你建立和删除角色的时候,请注意角色管理器特性不允许你建立重复的角色。同时还要注意,在默认情况下,角色管理器不允许你删除填充过的角色。
sub btncreaterole_click(byval sender as object, byval e as system.eventargs)
dim rolename as string = txtcreaterole.text
try
roles.createrole(rolename)
lblresults.text = nothing
lblresults.visible = false
txtcreaterole.text = nothing
catch ex as exception
lblresults.text = "could not create the role: " + server.htmlencode(ex.message)
lblresults.visible = true
end try
end sub
sub btndeleterole_click(byval sender as object, byval e as system.eventargs)
if (lbxavailableroles.selectedindex <> -1) then
try
roles.deleterole(lbxavailableroles.selectedvalue)
lblresults.text = nothing
lblresults.visible = false
catch ex as exception
lblresults.text = "could not delete the role: " + server.htmlencode(ex.message)
lblresults.visible = true
end try
end if
end sub
向角色中添加用户和从角色中删除用户
下面的例子使用了前面例子中建立的角色,它演示了如何向角色添加用户和从角色中删除用户。使用roles.addusertorole方法向角色中添加用户,使用roles.removeuserfromrole方法从角色中删除用户。在给角色添加用户之前,先检查该用户是否已经是该角色的成员。这种检查是必要的,因为如果你试图给角色多次添加同一个用户,角色管理器会抛出异常。在前面的例子中,角色信息和角色的成员都显示在数据绑定控件中。用户所属的角色列表通过roles.getrolesforuser方法获取。要运行下面的例子,就要确保把你自己加入"administrators"角色。
sub btnaddusertorole_click(byval sender as object, byval e as system.eventargs)
if (lbxavailableroles.selectedindex <> -1) then
dim selectedrole as string = lbxavailableroles.selectedvalue
if not roles.isuserinrole(selectedrole) then
try
roles.addusertorole(user.identity.name, selectedrole)
refreshcurrentroleslistbox()
catch ex as exception
lblresults.text = "could not add the user to the role: " + server.htmlencode(ex.message)
lblresults.visible = true
end try
else
lbxavailableroles.selectedindex = -1
end if
end if
end sub
sub btndeleteuserfromrole_click(byval sender as object, byval e as system.eventargs)
dim selectedrole as string = lbxuserroles.selectedvalue
if (lbxuserroles.selectedindex <> -1) then
try
roles.removeuserfromrole(user.identity.name, selectedrole)
refreshcurrentroleslistbox()
catch ex as exception
lblresults.text = "could not remove the user from the role: " + server.htmlencode(ex.message)
lblresults.visible = true
end try
end if
end sub
用角色管理器对页面进行授权访问
这个例子的web.config文件包含了<authorization>元素,它限制了示例只能让"administrators"角色的成员访问。请确保你已经建立了"administrators"角色并把自己添加到了这个角色中。一旦你称为"administrators"角色的成员,就可以访问示例页面了。asp.net提供了一个角色管理器httpmodule,它自动地把roleprincipal附加到当前请求的httpcontext上。如果你是"administrators"角色的成员,当url授权过程根据roleprincipal执行isinrole检查(url授权过程调用roleprincipal.isinrole)的时候,该访问检查会返回true,你就可以访问页面了。请注意,你可以通过调用page.user并把结果转换roleprincipal来引用页面中的roleprincipal。
<location path="administrators_role">
<system.web>
<authorization>
<allow roles="administrators" />
<deny users="*"/>
</authorization>
</system.web>
</location>
编程检查授权
由于角色管理器特性把roleprincipal附加到httpcontext上,你也可以编写代码根据roleprincipal执行访问检查。你先建立两个角色"regular users"和"power users",把自己添加到这两个角色中。当你运行示例的时候,页面使用多种技术执行isinrole检查。有些访问检查使用了user.isinrole。它说明了使用正常的page.user语法的时候,roleprincipal也是可用的。这个页面还演示了如何把page.user转换为roleprincipal引用,接着直接在roleprincipal上调用isinrole。
<asp:label id="label1" runat="server" text=<%# user.isinrole("administrators") %> />
<asp:label id="label2" runat="server" text=<%# roles.isuserinrole("regular users") %> />
<asp:label id="label3" runat="server" text=<%# (ctype(user,roleprincipal)).isinrole("power users") %> />