ASP.NET实现匿名访问控制
通过学习我掌握了两种控制方法,实现了对于网站的匿名访问控制,一个是通过IIS实现,另一个是通过ASP.NET实现。个人感觉两种基本上可以实现的功能没有什么差别,不过通过ASP.NET实现的更易于管理和维护。
对于通过IIS的实现都比较清楚,可以对文件夹或单个文件(页面)设置访问权限,IIS提供了“目录安全性”和“文件安全性”的设置方法。并可以通过直接设置不同访问人的权限。其中就提供了匿名访问的控制方法,这里不再细说。
ASP.NET控制方法:
首先介绍例子实现的效果,一个网站登陆以后可以将用户信息保存在一个cookie中,并标识用户是通过身份验证的,在访问其他页面的时候直接对用户的身份是否通过验证进行判断,如果通过即可继续访问页面,如果是一个未通过验证的匿名访问用户(即未登录用户)则将跳转到登陆页面提示用户登录。设置其中一个用户注册页面为可以匿名访问,因为业务逻辑上只有允许注册才能登陆。
在登陆页面还是先对用户的用户名和密码进行验证,至于使用与数据库比对的方法还是到AD中验证等都可以,然后通过以下的方法将当前用户在cookie中标识为通过验证的用户,并跳转到用户跳转到登陆页之前请求的页面。
FormsAuthentication.RedirectFromLoginPage(userName,createPersistentCookie);
FormsAuthentication.RedirectFromLoginPage(UserEmail.Value, PersistCookie.Checked);
在用户直接请求的页面上我们使用如下的方法来验证用户。
Context.User.Identity.IsAuthenticated
Context:获取与该页关联的 System.Web.HttpContext 对象
User:为当前 HTTP 请求获取或设置安全信息
Identity:获取当前用户的标识
IsAuthenticated:获取一个bool值,该值指示是否验证了用户
然后我们在Web.config中进行安全设置。
<authentication mode="Forms">
<forms loginUrl="Login.aspx"></forms>
</authentication>
"Forms" 您为用户提供一个输入凭据的自定义窗体(Web 页),然后在您的应用程序中验证他们的身份。用户凭据标记存储在 Cookie 中。
<authorization>
<deny users="?" />
</authorization>
deny表示禁止,users="?"表示匿名用户
<location path="NewUser.aspx">
<system.web>
<authorization>
<allow users="*" />
</authorization>
</system.web>
</location>
表示新用户注册页面NewUser.aspx,可以允许任何人访问。
这样就实现了对于匿名用户的访问控制。
对于机密目录,我们可以参照以下的说明:
我们设置机密目录(也就是"安全目录",特定的使用者如管理员才有权限访问的目录)。首先看看你的Web应用程序根目录下是否有 Web.config 这个文件,如果没有就创建一个。你也可以在你的子目录中创建 Web.config 文件,当然,这个 Web.config 文件是有限制的(一些参数它不可以设置)。要实现安全认证,在 Web应用程序根目录下的 Web.config 文件中找到 <system.web> 节点下的
程序代码
<authentication mode="Windows" />,把它修改为
<authentication mode="Forms">
<forms name="AMUHOUSE.ASPXAUTH"
loginUrl="Login.aspx"
protection="All"
path="./" />
</authentication>
<authorization>
<allow users="*"/>
</authorization>
上面的 name="AMUHOUSE.ASPXAUTH" 中,AMUHOUSE.ASPXAUTH 这个名称是任意的。要控制用户或者用户组的权限,我们可以有两种方法,一是配置在应用程序根目录下的 Web.config 文件,二是在机密目录下创建一个独立的 Web.config 文件。(后者也许会比较好。)如果是前者,这个Web.config 就应该包含有下面的内容(或者类似的内容):
程序代码
<configuration>
<system.web>
<authentication mode="Forms">
<forms name=" AMUHOUSE.ASPXAUTH"
loginUrl="login.aspx"
protection="All"
path="/"/>
</authentication>
<authorization>
<allow users="*"/>
</authorization>
</system.web>
<location path="./Admin">
<system.web>
<authorization>
<!-- 注意!下面几行的顺序和大小写是非常重要的! -->
<allow roles="Administrator"/>
<deny users="*"/>
</authorization>
</system.web>
</location>
<location path="./User">
<system.web>
<authorization>
<!-- 注意!下面几行的顺序和大小写是非常重要的! -->
<allow roles="User"/>
<deny users="*"/>
</authorization>
</system.web>
</location>
</configuration>
为了使Web应用程序的目录之前不互相依赖,可以比较方便的改名或者移动,可以选择在每一个安全子目录下配置单独的 Web.config 文件。它只需要配置 <authorization/>节点,如下:
程序代码
<configuration>
<system.web>
<authorization>
<!-- 注意!下面几行的顺序和大小写是非常重要的! -->
<allow roles="Administrator"/>
<deny users="*"/>
</authorization>
</system.web>
</configuration>
需要再次提醒的是,上面的角色 roles 是大小写敏感的,为了方便,你也可以把上面修改为:
<allow roles="Administrator,administrator" />
如果你想允许或者禁止多个角色对这个目录的访问,可以用逗号隔开,如:
<allow roles="Administrator,Member,User" />
<deny users="*" />
至此,我们已经为网站配置了基于角色的安全认证机制了。你可以先编译你的程序,然后尝试访问一个机密目录,例如 http://localhost/RolebasedAuth/Admin ,这时候你就会被转向到用户登录页面。如果你登录成功,并且你的角色对这个目录有访问权限,你就重新回到这个目录下。可能会有用户(或入侵者)企图进入机密目录,我们可以使用一个 Session 来存储用户登录的次数,超过一定次数就不让用户登录,并且显示"系统拒绝了你的登录请求!"。
下面,我们讨论如何根据用户角色让Web控件显示不同内容。
有时候根据用户的角色来显示内容比较好,因为你可能不想为那么多不同的角色(用户群组)制作一大堆有许多重复内容的页面。这样的网站,各种用户帐户可以并存,付费的用户帐户能够访问附加的付费内容。另一个例子是一个页面将显示一个 "进入后台管理" 按钮链接到后台管理页面如果当前用户是 "Administrator"(高级管理员)角色。我们现在就实现这个页面。
我们上面用到的 GenericPrincipal 类实现了 IPincipal 接口,这个接口有一个方法名叫做 IsInRole(),它的参数是一个字符串,这个字符串就是要验证的用户角色。如果我们要显示内容给角色是 "Administrator"的已登录用户,我们可以在 Page_Load 中添加下面代码:
程序代码
if (User.IsInRole("Administrator"))
AdminLink.Visible = true;
整个的页面代码如下(为了简便,把后台代码也写在aspx页面):
程序代码
<html>
<head>
<title>欢迎您!</title>
<script runat="server">
protected void Page_Load(Object sender, EventArgs e)
{
if (User.IsInRole("Administrator"))
AdminLink.Visible = true;
else
AdminLink.Visible = false;
}
</script>
</head>
<body>
<h2>欢迎!</h2>
<p>欢迎来到阿木小屋 http://amuhouse.com/ ^_^</p>
<asp:HyperLink id="AdminLink" runat="server"
Text="管理首页" NavigateUrl="./Admin"/>
</body>
</html>
这样,链接到 Admin 目录的HyperLink 控件只会显示给角色是 Administrator 的用户。你也可以根据为未登录用户提供一个链接到登录页面,如:
程序代码
protected void Page_Load(object sender, System.EventArgs e)
{
if (User.IsInRole("Administrator"))
{
AdminLink.Text = "管理员请进";
AdminLink.NavigateUrl="./Admin";
}
else if(User.IsInRole("User"))
{
AdminLink.Text = "注册用户请进";
AdminLink.NavigateUrl="./User";
}
else
{
AdminLink.Text = "请登录";
AdminLink.NavigateUrl="Login.aspx?ReturnUrl=" + Request.Path;
}
}
这里,我们通过设置叫做ReturnUrl的 QueryString 变量,可以使用户登录成功后返回到当前的这个页面.
下面是一个简单例子:
Default.aspx:
<%@ Import Namespace="System.Web.Security " %>
<html>
<script language="C#" runat=server>
void Page_Load(Object Src, EventArgs E ) {
Welcome.Text = "Hello, " + User.Identity.Name;
}
void Signout_Click(Object sender, EventArgs E) {
FormsAuthentication.SignOut();
Response.Redirect("login.aspx");
}
</script>
<body>
<h3><font face="宋体">使用 Cookie 身份验证</font></h3>
<form runat=server>
<h3><asp:label id="Welcome" runat=server/></h3>
<asp:button text="注销" OnClick="Signout_Click" runat=server/>
</form>
</body>
</html>
<%@ Import Namespace="System.Web.Security " %>
<html>
<script language="C#" runat=server>
void Login_Click(Object sender, EventArgs E) {
if (((UserEmail.Value == "jdoe@somewhere.com") && (UserPass.Value == "password")) || ((UserEmail.Value == "mary@somewhere.com") && (UserPass.Value == "password"))) {
FormsAuthentication.RedirectFromLoginPage(UserEmail.Value, PersistCookie.Checked);
}
else {
Msg.Text = "凭据无效:请再试一次";
}
}
</script>
<body>
<form runat=server>
<h3><font face="宋体">登录页</font></h3>
<table>
<tr>
<td>电子邮件:</td>
<td><input id="UserEmail" type="text" runat=server/></td>
<td><ASP:RequiredFieldValidator ControlToValidate="UserEmail" Display="Static" ErrorMessage="*" runat=server/></td>
</tr>
<tr>
<td>密码:</td>
<td><input id="UserPass" type=password runat=server/></td>
<td><ASP:RequiredFieldValidator ControlToValidate="UserPass" Display="Static" ErrorMessage="*" runat=server/></td>
</tr>
<tr>
<td>持久的 Cookie:</td>
<td><ASP:CheckBox id=PersistCookie runat="server" /> </td>
<td></td>
</tr>
</table>
<asp:button text="登录" OnClick="Login_Click" runat=server/>
<p>
<asp:Label id="Msg" ForeColor="red" Font-Name="Verdana" Font-Size="10" runat=server />
</form>
</body>
</html>
<configuration>
<system.web>
<authentication mode="Forms">
<forms name=".ASPXUSERDEMO" loginUrl="login.aspx" protection="All" timeout="60" />
</authentication>
<authorization>
<deny users="jdoe@somewhere.com" />
<deny users="?" />
</authorization>
<globalization requestEncoding="UTF-8" responseEncoding="UTF-8" />
</system.web>
</configuration>
我看了有很多朋友都在尝试写出带有登陆这样功能的网站,其方法几乎都是验证用户的登陆合法,然后发送一个表示验证的Cookie,或者在Session中保存信息以便于追踪接下来的访问授权,其实,这些细节化的操作,.NET都提供了一种非常有效的解决办法,能使你从繁琐的安全验证上解脱出来,而且,尽管你可能很小心地定义那些页面不能被没有权限的人访问,然而还有可能出现一些无法被检查出来的漏洞让他们跳过安全验证
好,废话少说,本文将介绍如下内容:
1、关于登陆验证和授权
2、使用Forms验证模式
3、授权资源的访问
4、基于角色的授权
1、关于登陆验证和授权
很多网站都有登陆对话框,让事先已经注册的用户验证,以便为他们提供个性化的服务等。可以把这个过程看作是两件事情的发生:验证和授权!登陆的作用是验证请求登陆的用户是否合法,而授权则是验证合法的用户在请求资源时,根据他们的权限决定是访问还是拒绝。
以上这种网站本身提供对话框的作法在.NET中被称之为Forms验证模式,接下来将会讲述这种验证模式。在以前ASP陈序员或者其他程序员,要想保存合法用户的验证,在以后的访问授权中使用,不得不使用写Cookie或者将信息保存在Session中的方法,而在需要授权的页面加载前添加一堆繁琐的代码来验证制定的用户是否具有访问权限否则的话就不能显示页面的内容,最恼火的是在授权页面上添加这些代码让人觉得重复和繁琐,而且可能不是最安全的,有一些比较隐蔽的方式可能会轻易绕过这种验证,因此程序员将来要做的很多事情就是再修改代码已堵住在运行过程中才发现的漏洞。在.NET的System.Web.Security中提供了一些网站安全方面的解决方案,尽管验证用户合法和授权的基本思路没有变化,但是授权的工作几乎已经交给.NET框架了,我们些代码之需要自己验证用户合法,并且告诉框架这个用户合法即可。
2、使用Forms验证模式
要使用启用Forms验证模式,请在网站根目录下的web.config文件中添加如下配置:(注意区分大小写)
<configuration>
<system.web>
<authentication mode="Forms" />
</system.web>
</configuration>
这将告诉.NET,你的网站使用Forms验证模式,.NET将不参与验证用户的工作,而是将这个工作交给你完成,你必须自己编写一些代码来验证用户合法,并且报告给.NET用户是合法的。.NET将会发送一个验证Cookie到用户,随后的访问中,.NET以此Cookie为依据,来执行授权的操作。
例如我们在login.ASPx界面中放置两个接受输入的文本框txtUserName和txtPassword,在数据库中,保存了用户名UserName和密码UserPassword,使用btnLogin按钮的Click事件来验证用户:
private void btnLogin_Click(object sender, EventArgs e)
{
string sql = "SELECT userid FROM Users WHERE UserName = '" + txtUserName.Text.Replace("'","_") + "' AND UserPassword = '" + System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(txtPassword.Text, "md5") + "'";
//使用上面类似的SQL语句向数据库执行查询,如果用户是合法的,将会返回数据。
if (...) //根据条件判定用户是合法的
{
//下面的语句告诉.NET发送一个验证Cookie给用户:
System.Web.Security.FormsAuthentication.SetAuthCookie(userid, false)
Response.Redirect("afterlogin.ASPx"); //定位到登陆后页面
}
else
{
//用户不合法,提示错误信息
}
}
以上代码中,
txtUserName.Text.Replace("'","_")将用户输入的文本中单引号替换为下划线,以防止SQL注入攻击。
System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(txtPassword.Text, "md5")方法将txtPassword.Text转换为MD5散列值,注意,在用户注册的时候,同样使用此方法将其输入的注册密码转换为散列值存储在数据库中,这里将用户输入的散列值进行对比以决定是否合法用户。任何时候不要将敏感的文本信息以明文方式存放在数据库中。通过MD5加密,即便此密文被截获,攻击者仍无法获得真实的密码。
当确认用户验证是合法的,则调用System.Web.Security.FormsAuthentication.SetAuthCookie(userid, false)方法,发送验证Cookie,此方法传递两个参数,一个是代表用户的标示,一般来说,在接下来确认用户唯一身份的就是从数据库中获得的userid。第二个参数告诉.NET是否写入持续的Cookie,如果为true,则Cookie将被持续,下次用户再次访问时,Cookie仍存在(相当于记住用户,可以提供这样的复选框让用户来决定是否持续Cookie)。发送了Cookie后,即可调用跳转语句跳转到指定地方。
另外还有一个方法:Web.Security.FormsAuthentication.RedirectFromLoginPage(string UserName, bool);将发送Cookie,并且根据传递的ReturnUrl参数来跳转到指定的页面(相当于将上面的两个步骤合为一步)。因此login.ASPx隐含可以传递ReturnUrl,如果没有这个参数,这个方法将用户跳转到Default.ASPx页。
3、授权资源的访问
一旦验证了用户合法,接下来要做的事就是对于用户请求的资源,授权他们是否能够访问。重新回到web.config文件中,在网站的任何目录中都可以使用web.config,他们的设置是传递继承的。
例如在users目录中存放的均是当用户登录后才能访问的页面,则在这个目录中创建一个web.config文件,内容如下:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.web>
<!-- 授权
此节设置应用程序的授权策略。可以允许或拒绝不同的用户或角色访问
应用程序资源。通配符: "*" 表示任何人,"?" 表示匿名
(未经身份验证的)用户。
-->
<authorization>
<deny users="?" />
</authorization>
</system.web>
</configuration>
上述内容中deny users="?" 将告诉.NET,此目录拒绝匿名用户的访问,也就是没有验证的用户。当用户试图请求此目录中的资源,将会被重新定向到login.ASPx页面,要求登陆。没有登陆的情况下是无法访问的。
上述仅对目录进行定义,程序员不用在页面上添加任何代码,即可完整地实现了授权方案。
当然,这种仅针对目录的授权配置可能有时候又会缺乏灵活,因此,.NET也提供location配置节,可以对指定的资源定义授权:
<configuration>
<location path="userabc.aspx">
<system.web>
<authorization>
<allow users="a,b,c" />
</authorization>
</system.web>
</location>
</configuration>
其中path是资源相对路径。
如果这还不够灵活的话,.NET也提供了在代码中使用的方法,ASP.NET页全局隐含了一个只读的User对象,通过获取User.Identity.IsAuthenticated属性,可探知用户是否验证(即是否登陆),User.Identity.Name属性可以获得用户的Name,即在验证时的SetAuthCookie方法中传递的userid。
4、基于角色的授权
上面我们讲述的用户验证,只可能有两种情况,要么用户通过验证,可以授权访问资源,要么用户没有通过验证,不能访问需要授权的资源。但是即便是验证通过的用户,可能他们所持用的权限还需要再进一步区分。例如普通用户和管理员同样是需要验证通过的,但是普通用户显然不能够访问管理页面,而管理员可以。面对这种情况,.NET可以使用基于角色的授权模型。
其基本原理是,一旦用户验证合法,他们就被分配角色,用户可以使一个或者若干和角色,而资源的授权面向角色,这样,针对不同的角色,就可以授予不同的权限,没有某种角色类型的用户试图访问需要这种角色的资源将会被拒绝。
当网站开始接受用户请求时,就伴随着验证,将激发Application_AuthenticateRequest事件,在Global.asax文件中写代码以响应此事件。角色的分配工作就需要再这里进行。
public void Application_AuthenticateRequest(object sender, EventArgs e)
{
if (this.Request.IsAuthenticated)
{
//这里简化了操作,可以从数据库中获得角色信息用以构造rolesStrArr数组。作为示例,我们为除了a之外的用户分配了管理员角色
string[] rolesStrArr;
if (this.Context.User.Identity.Name == "a")
{
rolesStrArr = new string[]{"普通用户"};
}
else
{
rolesStrArr = new string[]{"普通用户","管理员"};
}
this.Context.User = new System.Security.Principal.GenericPrincipal(this.User.Identity, rolesStrArr);
}
}
以上代码清晰明了,因此不再赘述。虽然在全局性有User对象,但是只有Context上下文中的User对象是可以写入的,我们调用System.Security.Principal.GenericPrincipal方法,在原有User对象的基础上为其加入角色。角色列表示一个字符串数组。
一旦用户被授予访问角色之后,在web.config中就可以配置针对不同角色的访问。例如在管理员admin目录内
<configuration>
<location path="userabc.aspx">
<system.web>
<authorization>
<allow roles="管理员" />
<deny users="*" />
</authorization>
</system.web>
</location>
</configuration>
上述配置只允许管理员角色才能被授权。资源默认是任何人都访问的,所以要在下面再添加<deny users="*" />表示对任何用户拒绝。
注意,无论对角色或者对用户指定资源的访问,如果对于多个角色或者读个资源,他们之间使用半角逗号隔开。同样,也可以使用上面讲到的方法,对指定的资源进行配置而不是对整个目录。
全局的User对象提供了一个方法IsInRole(string RoleName)方法用来在代码中检测用户是否拥有某种角色。如果他拥有这种角色,将返回true。
后记
.NET提供了完整的安全方面的解决方案,相对于ASP,这是激动人心的一个新特性。只是很多人可能并不能够熟练地运用,而且最痛心的是,很多书籍上甚至并没有这方面的任何描述,甚至连概念都没有。这就让人很怀疑编者的水平了。
首先,还是要在不断的实践过程中去了解和体会.NET,其实最好的老师应当是MSDN,到论坛来发帖的用户,我都尽量建议去查阅MSDN的资料,MSDN除了教给你怎么写代码,其实他教给你的还有非常优秀的思想和整体概念。只要学会使用,没有这些书也可以。从写第一行代码到现在,除了一本启蒙书,其他的资源都是MSDN或者网上找的,还有就是在每次做项目中的心得。尽管现在看来,启蒙书中也没有非常全面地讲述这些东西。
好了,希望大家看到会有所收获。限于我的水平,错误难免,欢迎指正!