本章首先从一个实用的角度来介绍ASP.NET提供的安全措施,涉及的内容有身份验证、用户授权和角色管理等。之后我们还会介绍ASP.NET 2.0新增的成员资格、角色管理器以及登录控件,甚至不需要编写任何代码就能实现一套基于角色的用户管理系统。安全是一个非常大的概念,作为本章的补充,在第18章中我们会从另外一个角度(安全意识)来介绍Asp.net应用程序的安全。
15.1 ASP.NET应用程序安全概述
15.1.1 安全相关的名词
本章我们会涉及到以下一些与安全相关的名词,理解这些概念是学习其他知识的根本。
· 身份验证。许多Web应用程序的一个重要部分是能够识别用户和控制对资源的访问。确定请求实体的标识的行为称为身份验证。通常,用户必须提供凭据(如名称/密码对)才能进行身份验证。
· 授权。一旦经过身份验证的标识可用,就必须确定该标识是否可以访问给定的资源。此过程称为授权。ASP.NET 与 IIS 一起使用为应用程序提供身份验证和授权服务。
· 模拟。在默认情况下,ASP.NET使用本地系统账号的权限执行(而不是请求的用户),使用模拟后,ASP.NET应用程序可以用发出请求的用户的Windows标识(用户账户)执行。
· 基于角色的安全性。对于一个庞大的用户系统,我们不可能为每一个用户都单独授权,如果把用户划分成不同的角色,然后对角色进行授权,那么这个工作会简单很多。
15.1.2 ASP.NET安全结构
ASP.NET中安全系统之间的关系如图15-1所示。
图15-1 ASP.NET安全结构
如图15-1所示,所有Web客户端都通过Internet信息服务(IIS)与ASP.NET应用程序通信。IIS根据需要对请求进行身份验证,然后找到请求的资源(如ASP.NET应用程序)。如果客户端已被授权,则资源可用。当运行ASP.NET应用程序时,它可以使用内置的ASP.NET安全功能。另外,ASP.NET应用程序还可以使用.NET框架的安全功能。
15.1.3 身份验证方式
ASP.NET与IIS一起使用以支持身份验证,并使用基本、摘要式和Windows身份验证。ASP.NET还为要使用基于窗体的身份验证的应用程序提供可靠的服务。基于窗体的身份验证使用Cookie鉴别用户的身份,并允许应用程序执行自己的凭据验证。
若要启用ASP.NET身份验证服务,必须在Web.config中配置<authentication>元素。
<configuration>
<system.web>
<authentication mode="Forms"/>
</system.web>
</configuration>
其中mode属性可以设置为以下项。
· None。不使用任何ASP.NET身份验证服务,但是IIS身份验证服务仍可以存在。
· Windows。基于Windows的身份验证,适合内网系统的身份验证方式。ASP.NET身份验证服务将WindowsPrincipal附加到当前请求以启用对NT用户或组的授权。
· Forms。基于表单(Cookie)的身份验证,适合外网系统的身份验证方式。ASP.NET 身份验证服务管理Cookie,并将未经身份验证的用户重定向到登录页。
· Passport。微软提供的集中式身份验证服务。由于需要依赖微软的网站(用户登录会转到微软Passport站点然后再跳转回原网站)而且需要付费,所以一般不适合小型网站使用。在第21章,我们也会介绍一个集中式单点登录的方案。
15.2节和15.3节将重点介绍基于Windows和表单的身份验证。
15.2 用户账户模拟
15.2.1 一个模拟请求用户的例子
和以前的ASP不同,ASP.NET默认不模拟发出请求的用户。因此,可能发生以下情况。
· 由于ASP.NET系统账户默认权限不够高,不能执行一些对权限要求比较高的操作(比如磁盘访问、活动目录操作等)。
· ASP.NET系统账户权限设置得过高,匿名用户都能执行“危险”操作。
我们可以通过开启模拟让ASP.NET以请求的账号来执行。一起来做一个试验。
1.新建一个ASPX页面,在Page_Load中我们输出请求标识和身份验证类型。(需要using System.Security.Principal;)。
protected void Page_Load(object sender, EventArgs e)
{
WindowsIdentity userIdentity = WindowsIdentity.GetCurrent();
WindowsPrincipal userPrincipal = new WindowsPrincipal(userIdentity);
Response.Write(string.Format("当前用户标识:{0}<br/>" , userPrincipal.
Identity.Name));
Response.Write(string.Format("身份验证类型:{0}<br/>" , userPrincipal.
Identity.AuthenticationType));
}
在页面上放置一个按钮控件和一个GridView控件。
<asp:Button ID="btn_GetFileList" runat="server" OnClick="btn_GetFileList_Click"
Text="获取文件列表" />
<asp:GridView ID="GridView1" runat="server">
</asp:GridView>
按钮控件的单击事件处理方法如下(需要using System.IO;):
protected void btn_GetFileList_Click(object sender, EventArgs e)
{
DirectoryInfo di = new DirectoryInfo(@"d:"test");
GridView1.DataSource = di.GetFiles();
GridView1.DataBind();
}
可以看到,单击按钮后我们读取d盘的test目录的文件列表并绑定到GridView上进行呈现。
新建一个Web.config文件,配置如下:
<?xml version="1.0"?>
<configuration>
<system.web>
<identity impersonate="true"/> <!--开启模拟-->
</system.web>
</configuration>
2.配置IIS,把程序所在的目录配置成一个虚拟目录,并且设置这个虚拟目录为关闭匿名访问,开启Windows集成验证,如图15-2所示。
注意:如果应用程序的虚拟目录上启用了匿名访问,则会模拟IUSR_MACHINENAME账户。
3.运行程序,单击“获取文件列表”按钮,如图15-3所示。
我们看到,当前模拟的用户为请求的用户(按Ctrl+Alt+Del组合键也可以看到当前登录的用户信息),由于当前请求的用户为本地的管理员(拥有操作本机d:"test的权限),GridView显示了目录下的所有文件信息。
4.右键单击“我的电脑”,选择“管理”,在本地用户和组下的用户文件夹中找到ASPNET用户,单击属性,如图15-4所示,我们可以看到ASPNET账户隶属于Users这个用户组。
注意:本章所有的例子都是以Windows XP操作系统(IIS 5.1)为例,对于IIS 6.0而言,ASP.NET的默认运行于NetworkService系统账户。
图15-2 配置虚拟目录
图15-3 以模拟方式运行
图15-4 ASPNET账户隶属于Users用户组
那么再来看一下d:"test文件夹的权限,如图15-5所示。
可以看到,Users用户组不具有对这个文件夹的任何权限。那么,如果我们关闭模拟,ASP.NET页面以ASPNET系统账户来执行的话应该就没有权限访问test文件夹了。设置<identity impersonate="false"/>, 然后重新运行页面,如图15-6所示。
图15-5 文件夹的权限 图15-6 关闭账户模拟
单击“获取文件列表”按钮后出现如图15-7所示的异常。
图15-7 ASPNET账户对文件夹的访问被拒绝
如图15-8所示,给test文件夹添加ASPNET系统账户并赋予读取和运行权限。
图15-8 赋予ASPNET系统账户对文件夹的操作权限
重新运行页面后能正常获取文件列表了,如图15-9所示。
图15-9 模拟请求用户
15.2.2 模拟某一个用户
如果你的Web服务器上有多个ASP.NET应用程序,或者你的ASP.NET应用程序需要更高的权限以执行特殊的操作,那么可以为程序设置一个单独的账户。
<?xml version="1.0"?>
<configuration>
<system.web>
<identity impersonate="true"
userName="域"机器名"
password="密码"
/>
</system.web>
</configuration>
在这里,你可以用明文设置用户名和密码,对此就不做过多解释了。需要特别注意的是,你设置的账户需要对以下目录拥有权限:
· ASP.NET临时文件夹。这是ASP.NET动态编译的位置,需要有读写权限。
· 全局程序集缓存(%Windir%"assembly)。这是全局程序集缓存,需要有读取权限。
注意:如果正在运行Windows Server 2003,其中的IIS 6.0配置为运行在辅助进程隔离模式下(默认情况),则可通过将ASP.NET应用程序配置为在自定义应用程序池(在特定的域标识下运行)中运行,然后使用指定的域标识访问资源而无需使用模拟。
15.2.3 使用编程方式进行临时模拟
有的时候可能希望暂时模拟经过身份验证的调用方,可以使用代码进行临时模拟。
1.把先前Page_Load里面的代码封装成一个私有方法并删除Page_Load中的代码。
private void GetIdentityInfo()
{
WindowsIdentity userIdentity = WindowsIdentity.GetCurrent();
WindowsPrincipal userPrincipal = new WindowsPrincipal(userIdentity);
Response.Write(string.Format("系统用户标识:{0}<br/>", userPrincipal.
Identity.Name));
Response.Write(string.Format("身份验证类型:{0}<br/>", userPrincipal.
Identity.AuthenticationType));
}
2.修改“获取文件列表”按钮的单击事件处理方法。
protected void btn_GetFileList_Click(object sender, EventArgs e)
{
GetIdentityInfo();
WindowsIdentity userIdentity = (WindowsIdentity)User.Identity;
WindowsPrincipal userPrincipal = new WindowsPrincipal(userIdentity);
WindowsImpersonationContext ctx = null;
try
{
Response.Write("模拟开始<br/>");
ctx = userIdentity.Impersonate();
GetIdentityInfo();
DirectoryInfo di = new DirectoryInfo(@"d:"test");
GridView1.DataSource = di.GetFiles();
GridView1.DataBind();
}
catch
{
}
finally
{
Response.Write("模拟结束<br/>");
if (ctx != null)
ctx.Undo();
GetIdentityInfo();
}
}
我们看到,输出了三次当前用户标识,分别是开始模拟以前,模拟以后和恢复不模拟以后。
3.重新运行程序之前首先修改Web.config文件禁止模拟,然后禁止ASPNET账户对d:"test文件夹的读取权限。运行结果如图15-10所示。
我们看到,虽然在Web.config中禁止了模拟,并且ASPNET账户对文件夹没有读取权限。但是通过编程方式我们临时模拟请求用户成功执行了进行文件夹操作。
图15-10 临时模拟
15.3 基于Windows的身份验证
15.3.1 概述
如果你正在开发一个公司内网的ASP.NET应用程序,就可以使用基于Windows的身份验证控制对敏感信息的访问。我们可以使用现有的Windows用户账号和用户组来进行授权,比如对Managers用户组授权员工工作绩效评估文档的目录的访问。
对于公共的系统来说,Windows集成验证是不适合的,因为我们不太可能为每一个来访的用户都添加一个Windows账户。
基于Windows的身份验证,整个过程如图15-11所示。
图15-11说明下列事件序列。
1.来自网络客户端的请求进入IIS。
2.IIS使用基本、简要或Windows集成的安全(NTLM或Kerberos)对客户端进行身份验证。
3.如果客户端通过了身份验证,则IIS将已通过身份验证的请求传递给ASP.NET。
4.ASP.NET应用程序使用从IIS传递来的访问标记模拟发出请求的客户端,并依赖于NTFS文件权限来授予对资源的访问权。ASP.NET应用程序只需验证是否在ASP.NET配置文件中将模拟设置为true ,不需要ASP.NET安全码。如果未启用模拟,则应用程序将使用ASP.NET进程标识运行。对于Microsoft Windows 2000 Server和Windows XP Professional,默认标识是在安装ASP.NET时自动创建的名为ASPNET的本地账户。对于Microsoft Windows Server 2003,默认标识是用于IIS应用程序的应用程序池标识(默认情况下为Network Service账户)。
5.如果允许访问,则ASP.NET应用程序通过IIS返回请求的资源。
在了解了这些后,让我们来学习如何使用基于Windows的身份验证。
15.3.2 配置IIS安全
在使用基于Windows的身份验证之前,首先需要配置IIS。如图15-12所示。
图15-11 基于Windows的身份验证过程 图15-12 在IIS中配置身份验证方法
我们可以看到,IIS 5.1支持以下几种身份验证方式(需要禁止匿名访问):
· 基本身份验证。与所有的浏览器和防火墙兼容,但是用户名和密码在网络上是以明文发送的。
· 集成Windows身份验证。不与所有的防火墙和代理服务器兼容,与IE浏览器兼容。用户名和密码不以明文发送。
· 摘要式身份验证。与所有的防火墙和代理服务器兼容,与IE浏览器兼容。用户名和密码不以明文进行发送,但是会保存在域服务器的明文文件中。
通常有两种方式禁止匿名用户访问文件或目录。
1.在IIS中配置禁止对某个目录的匿名访问。
2.配置NTFS权限,禁止匿名用户或者Guests用户组访问文件夹。这样,即使启用了匿名访问,匿名用户最终还是没有权限访问文件或者目录。
15.3.3 配置Windows安全
在15.2节中我们已经知道如何设置NTFS文件夹的权限。Windows系统包含几个默认的用户组。
· Administrators组。具有最大最多的权限。
· Everyone组。自动包含当前域中经过身份验证的每一个用户。
· Guests组。IIS所关联的匿名账户默认隶属于Guests组,如图15-13所示。
因此,要小心设置Guests用户组的权限。
NTFS的权限控制采用继承机制,默认情况下d:"test目录会继承d:的配置。因此,如果你尝试修改从父项继承的权限(比如修改d:"test文件夹自动从d:继承的权限),会得到如图15-14所示的系统 提示。
图15-13 匿名用户账户 图15-14 操作继承权限的提示
可以单击“高级”选项,然后取消勾选的“从父项继承权限”,如图15-15所示。
图15-15 高级安全设置
系统会提示怎么处理已经继承的权限,如图15-16所示。
图15-16 选择处理已经继承权限的方式
我们可以选择复制,应用于父项的权限会全部复制到这个文件夹上,这样,这个文件夹就拥有了独立的权限。然后就可以对这些权限进行修改。
15.3.4 配置Windows身份验证
默认情况下,所有的ASP.NET应用程序使用基于Windows的身份验证方式,如果你修改machine.config中的验证方式,那么可以通过单独配置某个ASP.NET应用程序根目录下的Web.config来应用Windows身份验证。
<configuration>
<system.web>
<authentication mode="Windows"/>
</system.web>
</configuration>
15.3.5 配置Windows授权
在启用Windows身份验证之后可以通过配置Web.config文件来向特定的用户和组授予特定目录或文件的权限。
配置如下文件对匿名用户的访问进行拒绝。
<?xml version="1.0"?>
<configuration>
<system.web>
<authorization>
<deny users="?"/>
</authorization>
</system.web>
</configuration>
Users代表一个或者多个用户(多个用户之间使用逗号隔开)。我们使用问号“?”来表示所有未经过身份验证的用户(匿名用户),使用星号“*”来表示所有用户。
需要注意,授权配置在<authorization>节点下,身份验证方式配置在<authentication>节点下,这两个英语单词比较容易混淆。
<deny>节点表示拒绝,还可以使用<allow>节点来表示允许,请看如下配置。
<?xml version="1.0"?>
<configuration>
<system.web>
<authorization>
<allow users="Magicgrids"yzhu"/>
<deny users="*"/>
</authorization>
</system.web>
</configuration>
这样就表示拒绝所有用户,只允许Magicgrids域下的yzhu用户访问文件或者目录。你可能会奇怪,拒绝所有用户和允许一个用户之间有矛盾。其实,授权部分使用的是第一匹配算法,允许的配置在拒绝前出现,ASP.NET看到允许后就直接放行用户而不会“追究”之后的拒绝。
在这里还需要注意以下几点。
1.如果你配置NTFS文件夹允许Everyone用户组(代表所有已经通过身份验证的用户)访问的话,那么就等于是进行了<allow users="*"/>的配置。
2.所有对于用户或者用户组的授权都需要是域名"用户名的格式。如果是本机用户,就在域名处写上计算机名或者一个“.”(点号)。
按照笔者的配置,你可能会打不开页面,因为你登录计算机的身份并不是Magicgrids域下的yzhu用户,因此出现如图15-17所法的对话框。
在15.4节中,我们会介绍更多有关授权的配置。
15.3.6 实现自定义角色
有的时候,我们可能不能直接使用Windows的用户组来授权用户,那么,我们可以使用编程方式自定义角色。创建一个全局应用程序类Global.asax。
<%@ Application Language="C#" %>
<script runat="server">
void WindowsAuthentication_OnAuthenticate(Object Source,
WindowsAuthenticationEventArgs e)
{
System.Collections.Generic.Dictionary<string, string[]> myRoles = new System.
Collections.Generic.Dictionary<string, string[]>();
myRoles.Add(@"magicgrids"yzhu", new string[] { "myGroup" });
if(myRoles.ContainsKey(e.Identity.Name.ToLower()))
e.User = new System.Security.Principal.GenericPrincipal(e.Identity,
myRoles[e.Identity.Name.ToLower()]);
}
</script>
在代码中,我们定义了一个字典来存储用户和与它关联的用户组。在这里,我们把magicgrids"yzhu这个用户关联到了自定义的用户组myGroup中。然后对web.config文件做如下修改:
<?xml version="1.0"?>
<configuration>
<system.web>
<authorization>
<allow roles="myGroup"/>
<deny users="*"/>
</authorization>
</system.web>
</configuration>
重新编译程序后页面还能正常打开,说明自定义用户组成功。在应用的时候,你可以把用户和用户组数据存放在数据库中,然后在全局应用程序类中加载自定义用户组,在Web.config中授权用户组。
15.3.7 获取用户信息
如果你觉得通过Web.config来进行授权(其实,它是一种URL授权,也就是说只能授权某文件或者某文件夹的访问而不能细化到每一个操作)还不是很方便,你可以直接读取当前访问页面的(域)用户信息来使用编程方式手动授权。
读取用户标识名和判断用户是否在某个用户组中的代码如下:
Response.Write(User.Identity.Name + "<br/>");
Response.Write(User.IsInRole("myGroup") + "<br/>");
你可以通过判断用户名来获取用户身份,然后根据用户身份给予不同的反馈。你也可以通过判断用户是否在一个用户组内进行相应的反馈。
15.4 基于表单的身份验证
15.4.1 概述
如果我们需要为网站创建一套自定义的用户注册系统,那么基于表单的身份验证(下称表单验证)是非常合适的。因此,我们可以使用任何的载体来存储用户名和密码(比如配置文件或者数据库)。表单验证通过cookie来判断用户票据,如果一个没有通过身份验证的用户请求一个页面,那么他会被自动转到登录页面,用户登录后又会转到原来的请求页面。
表单验证不负责提供完整的登录验证、用户注册等操作,它仅仅对用户进行身份验证,将未经身份验证的用户重定向到登录页,并执行所有必要的 Cookie 管理。
基于表单的身份验证,其整个过程如图15-18所示。
15.4.2 启用表单身份验证
要启用表单验证就必须完成以下步骤。
1.配置根目录Web.config文件的<authentication>节点,来启用表单验证。
2.配置必要目录Web.config文件的<authorization>节点,为指定目录关闭匿名访问权限。
3.创建一个登录页面,允许用户输入用户名和密码。
图15-18 基于表单的身份验证过程
一个典型的配置如下:
<configuration>
<system.web>
<authentication mode="Forms"/>
<authorization>
<deny users="?"/>
</authorization>
</system.web>
</configuration>
这样就设置了整个应用程序不允许匿名用户访问,未登录用户访问任何页面都会被转到登录页面(默认为Login.aspx)要求登录。
15.4.3 配置表单身份验证
在知道了如何启用表单身份验证后,让我们再来研究一下表单验证的一些配置选项。Web.config的<authentication>节点有一个可选的<forms>元素,它支持以下一些属性(现在的属性都设置为默认值)。
<authentication mode="Forms">
<forms
name=".ASPXAUTH"
loginUrl="login.aspx"
defaultUrl="default.aspx"
protection="All"
timeout="30"
path="/"
requireSSL="false"
slidingExpiration="true"
enableCrossAppRedirects="false"
cookieless="UseDeviceProfile"
domain="">
</forms>
</authentication>
每个属性各自的作用如下表所示。
属性 | 说明 |
name | 指定要用于身份验证的HTTP Cookie。如果正在一台服务器上运行多个应用程序,并且每个应用程序都需要唯一的Cookie,则必须在每个应用程序的Web.config文件中配置Cookie名称。 默认值为“.ASPXAUTH” |
loginUrl | 指定如果找不到任何有效的身份验证Cookie,将请求重定向到用于登录的URL。 默认值为login.aspx |
defaultUrl | 定义在身份验证之后用于重定向的默认URL。 默认值为“default.aspx” |
protection | 指定Cookie使用的加密类型(如果有)。 此属性可以为下列值之一: All。指定应用程序同时使用数据验证和加密方法来保护Cookie。该选项使用已配置的数据验证算法,该算法基于machineKey元素。如果三重DES(3DES)可用并且密钥足够长(48位或更长),则使用三重DES进行加密。默认值(建议值)为All。 Encryption。指定对于将Cookie仅用于个性化并且具有较低的安全要求的站点,同时禁用加密和验证。请不要以此方式使用Cookie;但是,通过这种方法在.NET Framework中启用个性化占用的资源最少。 None。指定使用3DES或DES对Cookie进行加密,但不对Cookie执行数据验证。采用这种方式的Cookie可能受到精选的纯文本的攻击。 Validation。指定验证方案验证已加密的Cookie的内容在转换中是否未被更改。Cookie是使用Cookie验证创建的,方法是将验证密钥与Cookie数据相连接,然后计算消息身份验证代码(MAC),最后将MAC追加到传出Cookie。 默认值为All |
timeout | 指定Cookie过期前逝去的时间(以整数分钟为单位)。如果SlidingExpiration属性为True,则timeout属性是滑动值,会在接收到上一个请求之后的指定时间(以分钟为单位)后过期。为防止危及性能并避免向开启Cookie警告的用户发出多个浏览器警告,当指定的时间逝去大半时将更新 Cookie。这可能导致精确性受损。持久性Cookie不超时。 默认值为30(分钟) |
path | 为应用程序发出的Cookie指定路径 默认值是斜杠(/),这是因为大多数浏览器是区分大小写的,如果路径大小写不匹配,浏览器不会送回Cookie |
requireSSL | 指定是否需要 SSL 连接来传输身份验证 Cookie。 此属性可以为下列值之一: True。指定必须使用SSL连接来保护用户凭据。如果为True,则ASP.NET为身份验证Cookie设置 Secure属性,并且除非连接使用SSL,否则兼容的浏览器不会返回Cookie。 False。指定不要求使用 SSL 连接来传输 Cookie。默认值为 False。 默认值为False |
续表
属性 | 说明 |
Sliding- | 指定是否启用弹性过期时间。可调过期将Cookie的当前身份验证时间重置为在单个会话期间收到每个请求时过期。 此属性可以为下列值之一: True。指定启用弹性过期时间。在单个会话期间,身份验证Cookie被刷新,并且每个后续请求的到期时间被重置。 False。指定不启用可调过期,并指定Cookie在最初发出之后,经过一段设定的时间间隔后过期。 默认值为False |
enableCross- | 表明是否将通过身份验证的用户重定向到其他 Web 应用程序中的URL。 此属性可以为下列值之一: True。指定将能够通过身份验证的用户重定向到其他 Web 应用程序中的URL。 False。指定将不能通过身份验证的用户重定向到其他Web应用程序中的URL。 默认值为False。 |
cookieless | 定义是否使用Cookie以及Cookie的行为。 此属性可以为下列值之一: UseCookies。指定无论在什么设备上都始终使用Cookie。 UseUri。指定从不使用 Cookie。 AutoDetect。如果设备配置文件支持Cookie,则指定使用Cookie;否则不使用Cookie。对于已知支持Cookie 的桌面浏览器,将使用探测机制来尝试在启用Cookie时使用Cookie。如果设备不支持Cookie,将不使用探测机制。 UseDeviceProfile。如果浏览器支持Cookie,则指定使用Cookie;否则不使用Cookie。对于支持Cookie的设备,不尝试通过探测来确定是否已启用Cookie支持。 默认值为UseDeviceProfile |
domain | 指定在传出Forms身份验证Cookie中设置的可选域。此设置的优先级高于httpCookies元素中使用的域。 默认值为空字符串("") |
15.4.4 配置表单授权——一个限制匿名下载的例子
在介绍基于Windows的身份认证的时候,我们简单介绍了一下配置授权用户和角色的方法。其实,ASP.NET用于控制对URL资源的客户端访问。它对于用于生成请求的HTTP方法(GET或POST)是可配置的,并且可配置为允许或拒绝访问用户组或角色组。下面的配置演示向名为someone的用户和名为Admins的角色授予访问权。而所有其他用户的访问被拒绝。
<authorization>
<allow users="someone" />
<allow roles="Admins" />
<deny users="*" />
</authorization>
允许的授权指令元素为allow或deny。每个allow或deny元素都必须包含users或roles属性。通过提供一个逗号分配的列表,可在单个元素中指定多个用户或角色。
在第14章中,我们介绍了如何通过自定义HttpHandler来实现防图片盗链和匿名下载。其实,我们也可以通过使用ASP.NET授权机制来实现,以rar文件为例。
首先在网站下创建一个Download文件夹,然后在文件夹下放置一个11.rar文件,在文件夹下创建一个Web.config文件,用于配置授权。
<configuration>
<system.web>
<authorization>
<deny users="?" />
</authorization>
</system.web>
</configuration>
上述配置限制了匿名用户对这个文件夹中资源的访问。但是要让ASP.NET引擎“接管”对资源的授权,还需要为某个扩展名的文件添加ASP.NET映射。使用第14章介绍的方法,对网站的虚拟目录添加rar文件到ASP.NET引擎的映射,如图15-19所示。
图15-19 添加扩展名映射
现在,你输入http://localhost/Chapter15/Download/11.rar来尝试下载文件,ASP.NET会自动转到登录页面要求你登录。
15.4.5 登录与注销
那么,怎么通知表单验证用户已经登录,要求表单验证写入用户凭据,又怎么通知表单验证擦除用户凭据呢?
我们首先配置Web.config文件,启用表单验证,并且设置登录页面和默认首页的地址。
<?xml version="1.0"?>
<configuration>
<system.web>
<authentication mode="Forms">
<forms
name=".ASPXAUTH"
loginUrl="MyLogin.aspx"
defaultUrl="Download/Download.aspx">
</forms>
</authentication>
</system.web>
</configuration>
创建一个MyLogin.aspx,在页面上放置一个按钮控件和一个多选框控件。
<asp:CheckBox ID="cb_RememberMe" runat="server" Text="记住我?" />
<asp:Button ID="btn_Login" runat="server" Text="登录" />
双击登录按钮,单击事件处理方法如下:
protected void btn_Login_Click(object sender, EventArgs e)
{
FormsAuthentication.RedirectFromLoginPage("test", cb_RememberMe.Checked);
}
登录操作仅仅只需要一行代码。在这里,我们“通知”表单验证,名为test的用户已经通过身份验证,可以写入票据了。第二个bool型的参数指示是否要持久保留票据(用户以后访问这个页面不需要再次登录)。
为了简单,在这里我们没有根据用户的用户名和密码来判断用户是否是合法用户,RedirectFrom- LoginPage()方法做的仅仅是保存用户票据并把用户重定向到来源页面。
现在,在前一节中创建的Download目录下再创建一个Download.aspx页面。在这样页面上,我们添加一个链接用于下载文件,添加一个按钮用于退出操作。
<a href="11.rar">下载</a><br />
<asp:Button ID="btn_Logout" runat="server" Text="注销" />
注销按钮的单击事件处理方法如下:
protected void btn_Logout_Click(object sender, EventArgs e)
{
// 删除用户票据
FormsAuthentication.SignOut();
// 重定向到登录页面
FormsAuthentication.RedirectToLoginPage();
}
表单验证“接管”了票据的写入和擦除,我们没有对Cookie进行任何编码就实现了用户身份票据在Cookie中的保存和删除。
现在就可以开始测试了。
1.打开MyLogin.aspx进行登录,登录后页面直接转到默认的首页Download目录下的Download. aspx(根据defaultUrl属性的设置)。
2.直接打开通过身份验证用户才能访问的Download/Download.aspx,页面自动重定向到MyLogin. aspx(根据loginUrl属性的设置),要求用户登录,登录后还是自动回到Download.aspx。由于我们在前一节中已经添加了对rar文件的扩展名映射,因此未验证用户直接访问11.rar文件也会转到MyLogin. aspx。
3.测试永久持久票据。在登录的时候选中“记住我”复选框,如图15-20所示。
单击登录后页面转到下载页面,如图15-21所示。
图15-20 记住我功能 图15-21 下载页面
关闭浏览器后直接访问下载页面,发现页面能正常打开,说明用户票据确实被持久保存了。单击“注销”按钮,页面又回到了MyLogin.aspx。
15.4.6 使用Web.config进行身份验证
对于内部使用的小型系统,我们可能根本不需要对外开放注册等功能,而且用户的角色也相对单一,这时我们可以直接使用Web.config来保存用户名和密码,并且直接使用表单验证来验证。如下修改Web.config文件的<forms>部分:
<forms
name=".ASPXAUTH"
loginUrl="MyLogin.aspx"
defaultUrl="Download/Download.aspx">
<credentials passwordFormat="Clear">
<user name="test" password="test"/>
</credentials>
</forms>
在这里,我们用明文存储了用户名和密码(都是test),然后修改MyLogin.aspx登录按钮的单击事件处理方法。
protected void btn_Login_Click(object sender, EventArgs e)
{
if (FormsAuthentication.Authenticate("test", "test"))
FormsAuthentication.RedirectFromLoginPage("test", cb_RememberMe.Checked);
else
Response.Write("用户名或者密码错误");
}
通过Authenticate()方法就能直接对照配置文件中的凭据来验证用户名和密码。如果你觉得明文保存密码不合适,可以修改Web.config文件,选择“MD5”或者“SHA1”加密算法。你可能会问,怎么样知道一个密码对应的加密后密码是什么呢?不用担心,可以使用HashPasswordForStoringIn- ConfigFile()方法。修改MyLogin.aspx的Page_Load事件处理方法。
protected void Page_Load(object sender, EventArgs e)
{
Response.Write(FormsAuthentication.HashPasswordForStoringInConfigFile
("test","SHA1"));
}
上述代码输出了test这个密码经过SHA1加密后的密码,然后把这个密码放入Web.config中即可。
<credentials passwordFormat=:SHA1">
<user name="test" password="A94A8FE5CCB19BA61C4C0873D391E987982FBBD3"/>
</credentials>
15.4.7 获取用户信息
最后,我们来研究如何获取经过表单身份验证后的用户信息和用户凭据信息。在Download目录下创建一个UserInfo.aspx,如下修改Page_Load事件处理方法:
protected void Page_Load(object sender, EventArgs e)
{
if (User.Identity.IsAuthenticated)
{
FormsIdentity identity = User.Identity as FormsIdentity;
FormsAuthenticationTicket ticket = identity.Ticket;
Response.Write(string.Format("用户名:{0}<br/>", identity.Name));
Response.Write(string.Format("创建时间:{0}<br/>", ticket.IssueDate));
Response.Write(string.Format("过期时间:{0}<br/>", ticket.Expiration));
Response.Write(string.Format("是否持久:{0}<br/>", ticket.IsPersistent));
}
else
FormsAuthentication.RedirectToLoginPage();
}
在这里,我们输出了通过身份验证的用户名,以及身份票据的创建时间、过期时间和持久性,在MyLogin.aspx页面登录后(选择“记住我”)访问UserInfo.aspx页面的输出结果如图15-22所示。