2.2 配置安全授权
授权是指识别用户是否可以访问特定资源的过程。可以通过向Web配置文件添加authorization节点来控制授权。
不管应用程序启用任何类型的鉴别类型,都会使用相同的方法进行授权。换句话说,即使是在使用Forms、Windows和.NET Passport鉴别时,也都使用相同的方法来为其进行授权。
一般来说,我们会将所有需要密码保护的页面放在单独的文件夹中。如果向该文件夹中添加一个Web配置文件,那么该Web配置文件的设置将会应用到该文件夹以及其子文件夹中的所有页面上。
例如,如果将代码清单2-10中的Web配置文件添加到该文件夹中,那么任何未获得授权的用户访问该文件夹中的任何页面,都会被拒绝。
代码清单2-10 Web.Config
<?xml version="1.0"?>
<configuration>
<system.web>
<authorization>
<deny users="?"/>
</authorization>
</system.web>
</configuration>
如果将代码清单2-10中的文件添加到该文件夹中,那么未授权的用户将不能访问该文件夹下的任何页面。当启用了Forms鉴别后,未授权用户的访问将会自动地重定向到Login页面。
代码清单2-11中的Web配置文件包含了一个authorization节点,同时该节点定义了一个简单的授权规则。匿名用户不能访问配置文件。符号?表示匿名(未鉴别)用户。
下面两个特殊的符号可以用于users属性:
q ?——用于表示未鉴别用户;
q *——用于表示所有用户(未鉴别和已鉴别)。
属性deny可以设置为指定的用户名或者以逗号为分割符的用户名列表。例如,代码清单2-11中的authorization节点允许名为Jane的用户访问,同时禁止其他任何人访问(甚至是已鉴别用户)。
代码清单2-11 SecretFile/Web.Config
<?xml version="1.0"?>
<configuration>
<system.web>
<authorization>
<allow users="Jane" />
<deny users="*" />
</authorization>
</system.web>
</configuration>
授权规则的设置顺序非常重要。ASP.NET Framework使用短路匹配算法。如果交换代码清单2-11中allow和deny属性规则的定义次序,那么没有任何人,Jane也不例外,可以允许访问该配置文件所在文件夹下的页面。
注解 要防止匿名用户访问应用程序中的任何页面,可以通过向该应用程序根目录的Web配置中添加authorization节点。不过在这样的情况下,是需要允许匿名用户访问Login页面的(否则在使用Forms鉴别时,将没有任何用户可以登录该应用程度)。
|
Visual Web 如果你喜欢,还可以通过站点管理工具来配置授权规则。使用该工具可以在表单界面下对不同的文件夹进行授权配置。通过开启该站点配置工具,需要通过选取菜单选项WebSite(站点)→ASP.NET Configuration(ASP.NET配置)。
2.2.1 角色授权
在创建鉴别规则时,还可以通过用户角色进行鉴别。例如,代码清单2-12中的Web配置文件将防止除管理员组外的任何用户访问其文件夹下的任何页面。
代码清单2-12 Web.Config
<?xml version="1.0"?>
<configuration>
<system.web>
<authorization>
<allow roles="Administrator"/>
<deny users="*"/>
</authorization>
</system.web>
</configuration>
当应用程序启用了Forms鉴别后,role属性所表示的是自定义角色。在2.4节中,将介绍如何配置和创建自定的角色。如果应用程序启用了Windows鉴别,那么role属性所表示的是微软Windows组。
2.2.2 根据位置授权访问文件
在默认情况下,授权规则会应用到该文件夹以及其子文件夹中的所有页面上。然而,也可以在authorization节点中使用location节点选项。使用location节点可以将授权规则应用到文件夹或特定路径的页面上。
例如,假设你只需要使用密码来保护一个,仅仅只是一个文件夹中的页面。在那样的情况下,就可以使用location节点来指定单个文件的路径。代码清单2-13中的Web配置文件使用密码来保护了一个名为Secret.aspx的页面。
代码清单2-13 Web.Config
<?xml version="1.0"?>
<configuration>
<system.web>
<authentication mode="Forms" />
</system.web>
<location path="Secret.aspx">
<system.web>
<authorization>
<deny users="?"/>
</authorization>
</system.web>
</location>
</configuration>
location节点也可以将配置信息应用到特定的子文件夹中。例如,代码清单2-14中的Web配置文件通过密码来保护了名为SecretFiles的文件夹。
代码清单2-14 Web.Config
<?xml version="1.0"?>
<configuration>
<system.web>
<authentication mode="Forms" />
</system.web>
<location path="SecretFiles">
<system.web>
<authorization>
<deny users="?"/>
</authorization>
</system.web>
</location>
</configuration>
2.2.3 对图片以及其他文件类型进行访问授权
我们之前所讨论的授权规则只能应用到已关联到ASP.NET Framework中的文件类型上。Visual Web Developer Web服务器端将所有的文件类型关联到了ASP.NET Framework中。另一方面,IIS只将特定的文件类型关联到了ASP.NET Framework中。
如果应用程序使用IIS,并且将一图片文件添加到该应用程序受密码报复的文件夹中,这时用户对该图片文件的访问请求并不会被阻止。在默认情况下,鉴别规则只被应用到了诸如ASP.NET页面这样的文件类型上。而对于图片、微软Word文档和传统的ASP页面等文件类型都被ASP.NET Framework忽略了。
如果你需要使用密码来保护特定类型的静态文件,例如图片或微软Word文档等,那么需要将这些类型文件的扩展名关联到ASP.NET ISAPI扩展中。
例如,遵照下列这些步骤将启用对.gif类型图片文件的授权管理:
(1) 通过点击Start(开始)→Control Panel(控制面板)→Administrator Tools(管理工具)→IIS(Internet信息服务),打开IIS管理器。
(2) 打开特定Web站点或虚拟目录的属性设置窗口。
(3) 通过选取Directory(目录)选项卡并点击Configuration(配置)按钮,开启Application Configuration(应用程序配置)对话框。
(4) 选取并点击Mapping(映射)选项卡(见图2-3)。
(5) 点击Add(添加)按钮以开启添加/编辑(Add/Edit)应用程序扩展名映射对话框。
(6) 在Executable(可执行)表单项中,输入ASP.NET ISAPI动态链接库的路径(也可以通过编辑.aspx扩展名映射,来复制并粘贴该路径)。
(7) 在Extension(扩展名)表单项中填入.gif。
当完成了以上步骤后,对.gif图片文件的请求将会传给ASP.NET Framework。同时也就可以对.gif类型文件进行统一的鉴别和授权。
遵照以上步骤及操作顺序,可以对其他类型静态文件设置密码保护功能,例如微软Word文档、Excel电子表格和视频文件等。
2.2.4 对传统ASP页面进行访问授权
虽然在同一个应用程序中可以混合使用ASP.NET页面和传统ASP页面。然而,普通的ASP.NET页面和传统的ASP页面运行在彼此独立的程序域中。特别需要注意的是,ASP.NET的鉴别和授权都不能应用到传统的ASP页面上。
如果你使用IIS6(该版本IIS位于Windows Server 2003中),那么可以将传统ASP页面映射到ASP.NET Framework中。在那样的设置下,ASP.NET授权规则将应用到传统ASP页面上。
IIS6支持一个名为通配符应用程序映射(wildcard application mapping)的功能。所以可以通过通配符来截获对传统ASP页面的请求,并将该请求交由ASP.NET Framework进行处理。通过鉴别和授权检验后,ASP.NET Framework再将该请求转发给传统ASP模块进行处理。
要启用ASP.NET的通配符映射,需要执行以下步骤:
(1) 点击Start(开始)→Control Panel(控制面板)→Administrator Tools(管理工具)→IIS(Internet信息服务),打开IIS管理器。
(2) 打开指定目录或虚拟目录的属性窗口。
(3) 选中Directory(目录)选项卡并点击Configuration(配置)按钮,以开启应用程序配置对话框。
(4) 选中Mapping(映射)选项卡。
(5) 点击映射选项卡底部的Insert(插入)按钮,以开启Add/Edit(添加/删除)应用程序扩展名映射对话框(见图2-4)。
(6) 在扩展名输入框中输入ASP.NET ISAPI动态链接库的路径(该路径也可以从.aspx扩展名的映射设置上进行复制,并粘贴到该输入框中)。
在完成了以上步骤之后,那么对所有文件(不仅仅是传统ASP文件)的访问请求,都会映射到ASP.NET Framework上。这样一来,就可以使用与保护ASP.NET页面的相同方法来通过ASP.NET授权规则保护传统ASP页面。该应用中的授权规则同时也对诸如图像文件、微软Word文档和其他类型文件生效。
2.2 配置安全授权
授权是指识别用户是否可以访问特定资源的过程。可以通过向Web配置文件添加authorization节点来控制授权。
不管应用程序启用任何类型的鉴别类型,都会使用相同的方法进行授权。换句话说,即使是在使用Forms、Windows和.NET Passport鉴别时,也都使用相同的方法来为其进行授权。
一般来说,我们会将所有需要密码保护的页面放在单独的文件夹中。如果向该文件夹中添加一个Web配置文件,那么该Web配置文件的设置将会应用到该文件夹以及其子文件夹中的所有页面上。
例如,如果将代码清单2-10中的Web配置文件添加到该文件夹中,那么任何未获得授权的用户访问该文件夹中的任何页面,都会被拒绝。
代码清单2-10 Web.Config
<?xml version="1.0"?>
<configuration>
<system.web>
<authorization>
<deny users="?"/>
</authorization>
</system.web>
</configuration>
如果将代码清单2-10中的文件添加到该文件夹中,那么未授权的用户将不能访问该文件夹下的任何页面。当启用了Forms鉴别后,未授权用户的访问将会自动地重定向到Login页面。
代码清单2-11中的Web配置文件包含了一个authorization节点,同时该节点定义了一个简单的授权规则。匿名用户不能访问配置文件。符号?表示匿名(未鉴别)用户。
下面两个特殊的符号可以用于users属性:
q ?——用于表示未鉴别用户;
q *——用于表示所有用户(未鉴别和已鉴别)。
属性deny可以设置为指定的用户名或者以逗号为分割符的用户名列表。例如,代码清单2-11中的authorization节点允许名为Jane的用户访问,同时禁止其他任何人访问(甚至是已鉴别用户)。
代码清单2-11 SecretFile/Web.Config
<?xml version="1.0"?>
<configuration>
<system.web>
<authorization>
<allow users="Jane" />
<deny users="*" />
</authorization>
</system.web>
</configuration>
授权规则的设置顺序非常重要。ASP.NET Framework使用短路匹配算法。如果交换代码清单2-11中allow和deny属性规则的定义次序,那么没有任何人,Jane也不例外,可以允许访问该配置文件所在文件夹下的页面。
注解 要防止匿名用户访问应用程序中的任何页面,可以通过向该应用程序根目录的Web配置中添加authorization节点。不过在这样的情况下,是需要允许匿名用户访问Login页面的(否则在使用Forms鉴别时,将没有任何用户可以登录该应用程度)。
|
Visual Web 如果你喜欢,还可以通过站点管理工具来配置授权规则。使用该工具可以在表单界面下对不同的文件夹进行授权配置。通过开启该站点配置工具,需要通过选取菜单选项WebSite(站点)→ASP.NET Configuration(ASP.NET配置)。
2.2.1 角色授权
在创建鉴别规则时,还可以通过用户角色进行鉴别。例如,代码清单2-12中的Web配置文件将防止除管理员组外的任何用户访问其文件夹下的任何页面。
代码清单2-12 Web.Config
<?xml version="1.0"?>
<configuration>
<system.web>
<authorization>
<allow roles="Administrator"/>
<deny users="*"/>
</authorization>
</system.web>
</configuration>
当应用程序启用了Forms鉴别后,role属性所表示的是自定义角色。在2.4节中,将介绍如何配置和创建自定的角色。如果应用程序启用了Windows鉴别,那么role属性所表示的是微软Windows组。
2.2.2 根据位置授权访问文件
在默认情况下,授权规则会应用到该文件夹以及其子文件夹中的所有页面上。然而,也可以在authorization节点中使用location节点选项。使用location节点可以将授权规则应用到文件夹或特定路径的页面上。
例如,假设你只需要使用密码来保护一个,仅仅只是一个文件夹中的页面。在那样的情况下,就可以使用location节点来指定单个文件的路径。代码清单2-13中的Web配置文件使用密码来保护了一个名为Secret.aspx的页面。
代码清单2-13 Web.Config
<?xml version="1.0"?>
<configuration>
<system.web>
<authentication mode="Forms" />
</system.web>
<location path="Secret.aspx">
<system.web>
<authorization>
<deny users="?"/>
</authorization>
</system.web>
</location>
</configuration>
location节点也可以将配置信息应用到特定的子文件夹中。例如,代码清单2-14中的Web配置文件通过密码来保护了名为SecretFiles的文件夹。
代码清单2-14 Web.Config
<?xml version="1.0"?>
<configuration>
<system.web>
<authentication mode="Forms" />
</system.web>
<location path="SecretFiles">
<system.web>
<authorization>
<deny users="?"/>
</authorization>
</system.web>
</location>
</configuration>
2.2.3 对图片以及其他文件类型进行访问授权
我们之前所讨论的授权规则只能应用到已关联到ASP.NET Framework中的文件类型上。Visual Web Developer Web服务器端将所有的文件类型关联到了ASP.NET Framework中。另一方面,IIS只将特定的文件类型关联到了ASP.NET Framework中。
如果应用程序使用IIS,并且将一图片文件添加到该应用程序受密码报复的文件夹中,这时用户对该图片文件的访问请求并不会被阻止。在默认情况下,鉴别规则只被应用到了诸如ASP.NET页面这样的文件类型上。而对于图片、微软Word文档和传统的ASP页面等文件类型都被ASP.NET Framework忽略了。
如果你需要使用密码来保护特定类型的静态文件,例如图片或微软Word文档等,那么需要将这些类型文件的扩展名关联到ASP.NET ISAPI扩展中。
例如,遵照下列这些步骤将启用对.gif类型图片文件的授权管理:
(1) 通过点击Start(开始)→Control Panel(控制面板)→Administrator Tools(管理工具)→IIS(Internet信息服务),打开IIS管理器。
(2) 打开特定Web站点或虚拟目录的属性设置窗口。
(3) 通过选取Directory(目录)选项卡并点击Configuration(配置)按钮,开启Application Configuration(应用程序配置)对话框。
(4) 选取并点击Mapping(映射)选项卡(见图2-3)。
(5) 点击Add(添加)按钮以开启添加/编辑(Add/Edit)应用程序扩展名映射对话框。
(6) 在Executable(可执行)表单项中,输入ASP.NET ISAPI动态链接库的路径(也可以通过编辑.aspx扩展名映射,来复制并粘贴该路径)。
(7) 在Extension(扩展名)表单项中填入.gif。
当完成了以上步骤后,对.gif图片文件的请求将会传给ASP.NET Framework。同时也就可以对.gif类型文件进行统一的鉴别和授权。
遵照以上步骤及操作顺序,可以对其他类型静态文件设置密码保护功能,例如微软Word文档、Excel电子表格和视频文件等。
2.2.4 对传统ASP页面进行访问授权
虽然在同一个应用程序中可以混合使用ASP.NET页面和传统ASP页面。然而,普通的ASP.NET页面和传统的ASP页面运行在彼此独立的程序域中。特别需要注意的是,ASP.NET的鉴别和授权都不能应用到传统的ASP页面上。
如果你使用IIS6(该版本IIS位于Windows Server 2003中),那么可以将传统ASP页面映射到ASP.NET Framework中。在那样的设置下,ASP.NET授权规则将应用到传统ASP页面上。
IIS6支持一个名为通配符应用程序映射(wildcard application mapping)的功能。所以可以通过通配符来截获对传统ASP页面的请求,并将该请求交由ASP.NET Framework进行处理。通过鉴别和授权检验后,ASP.NET Framework再将该请求转发给传统ASP模块进行处理。
要启用ASP.NET的通配符映射,需要执行以下步骤:
(1) 点击Start(开始)→Control Panel(控制面板)→Administrator Tools(管理工具)→IIS(Internet信息服务),打开IIS管理器。
(2) 打开指定目录或虚拟目录的属性窗口。
(3) 选中Directory(目录)选项卡并点击Configuration(配置)按钮,以开启应用程序配置对话框。
(4) 选中Mapping(映射)选项卡。
(5) 点击映射选项卡底部的Insert(插入)按钮,以开启Add/Edit(添加/删除)应用程序扩展名映射对话框(见图2-4)。
(6) 在扩展名输入框中输入ASP.NET ISAPI动态链接库的路径(该路径也可以从.aspx扩展名的映射设置上进行复制,并粘贴到该输入框中)。
在完成了以上步骤之后,那么对所有文件(不仅仅是传统ASP文件)的访问请求,都会映射到ASP.NET Framework上。这样一来,就可以使用与保护ASP.NET页面的相同方法来通过ASP.NET授权规则保护传统ASP页面。该应用中的授权规则同时也对诸如图像文件、微软Word文档和其他类型文件生效。
本章内容
q 使用浏览器cookie
q 使用会话状态
q 使用用户配置文件
q 小结
那些刚刚进行Web编程的开发人员,总是难以理解维护应用程序状态的问题。万维网最基础的协议HTTP协议是一个无状态协议,也就是说,从Web服务器端的角度来看,每一个访问请求都是一个新的用户。HTTP协议不提供任何方法来决定两个访问请求是否是同一个用户发出的。
然而,维护状态对任何Web应用程序来说都是非常重要的。购物车是一个经典的例子。如果希望在多个页面请求之间关联一个购物车和某个用户,就需要一些维护状态的方法。
这一章关注三种包含在ASP.NET 2.0 Framework中的、在多个页面请求间关联数据和特定的用户的方法。在第一部分,你将了解到如何创建和处理浏览器cookie。浏览器cookie用于关联一些文本到网站的每一个用户。
接着,你将了解如何使用会话状态(session state)。会话状态用于关联任意类型的对象到任何用户。例如,可以存储购物车对象到会话状态中。
你将了解到如何使用不依赖cookie的会话状态,这样,即使浏览器禁用了cookie,你也能使用会话状态。你还将了解到如何通过开启进程外会话状态,从而让会话状态更健壮(robust)。
最后,我们介绍ASP.NET 2.0 Framework引入的一个新特性:profile对象。profile对象提供一个创建强类型和持久化方式的会话状态的方法。
你将了解到定义用户配置文件的不同方法,以及如何在一个组件内使用profile对象。最后,你将了解如何实现一个自定义profile提供程序。
3.1 使用浏览器cookie
cookie是由第一个版本的Netscape浏览器引入。Netscape的开发人员发明了cookie来解决当时折磨互联网的一个问题,那就是没办法赚钱,因为没办法创建一个购物车。
注解 可以从http://home.netscape.com/newsref/std/cookie_spec.html阅读Netscape的原始cookie定义。
先讲讲cookie是如何工作的。当Web服务器端创建一个cookie时,一个附加的HTTP首部在浏览器显示页面时被发送到浏览器。HTTP首部类似如下形式:
Set-Cookie: message=Hello
Set-Cookie首部使得浏览器创建一个名为message的cookie,包含值Hello。
在浏览器创建cookie后,它从相同的应用程序请求页面时,都将像下面这样发送这个HTTP首部:
Cookie:message=Hello
cookie头包含所有的Web服务器端设置的cookie。每次浏览器向Web服务器端请求页面时,cookie都会发送回服务器端。
注意,一个cookie就是一段文本。cookie只能用于存储字符串值。
实际上,我们可以存储两种类型的cookie:会话cookie和持久化cookie。一个会话cookie只存在于内存中。当用户关闭浏览器时,会话cookie就永远消失了。
而持久化cookie可以存在几个月甚至几年。当创建一个持久化cookie时,cookie被浏览器长久存在用户的电脑上。以IE为例,cookie以一组文本文件的形式保存在下面的文件夹:
/Documents and Settings/[user]/Cookies
另一方面,对于Mozilla Firefox浏览器则将cookie保存在下面的文件:
/Documents and Settings/[user]/Application Data/Mozilla/Firefox/Profiles/➥[random folder
name]/Cookies.txt
因为不同的浏览器将cookie存于不同的位置,cookie是浏览器独立的。使用IE浏览器请求页面并创建的cookie,不存在于Firefox或Opera浏览器中。
此外,应注意IE和Firefox浏览器都以明文形式存储cookie。我们不应该将诸如社会保险号,或信用卡号码之类的敏感信息存于cookie中。
注解 cookie这个名称从哪里来?根据Netscape最初的cookie文档,cookie并不是因为什么特别的原因被选中的。然而,这个名称最有可能继承自UNIX世界,类似“magic cookie”,在程序间传递的一种不透明的令牌。
3.1.1 cookie的安全性限制
cookie会涉及安全方面的问题。当创建一个持久化cookie时,我们会修改访问者电脑上的文件。有些人就是一天到晚梦想着对你的电脑做些坏事。为了避免电脑遭到袭击,浏览器对cookie有一些强制的安全限制。
首先,所有的cookie是域名独立的。Amazon网站设置的cookie,巴诺书店网站访问不到。浏览器创建一个cookie时,会记录关联到cookie的域名,不会将其发送到另一个域名。
注解 一个包含在Web页面的图片可能来自和页面不同的另一个域名。当浏览器请求一个图片时,cookie可能被另一个域名设置。诸如DoubleClick这样的公司,就利用这样的后门在多个不同的页面间跟踪并显示广告统计信息。这种类型的cookie就称为第三方cookie。
另一个浏览器存储cookie的重要限制是其大小的限制。一个域名存储的cookie总大小不能超过4 096byte。这个大小限制,包含所有的cookie名称和值在内。
注解 IE5.0以上版本支持一个名叫userData行为的功能。userData行为允许存储远大于cookie的数据(对局域网10 240KB,对因特网站点1 024KB)。要了解更多关于userData行为的信息,请访问微软MSDN网站(msdn.microsoft.com)。
最后,大多数浏览器都限制可以被设置的cookie数量,一个域名不超过20个cookie(不包括IE浏览器)。超过这个数目,旧的cookie会被自动删除。
注解 美国白宫管理和财政办公室阻止所有的联邦网站创建持久化cookie,除非“强制需要”。见http://www.whitehouse.gov/omb/memoranda/m00-13.html。
美国国家安全应急中心网站(www.nsa.gov)最近就遇到了创建持久化cookie的问题。他们在收到来自隐私倡议的阻止警告后停止了使用持久化cookie。
个人认为这样的cookie偏执策略有点疯狂,不过我们还是要注意它。
因为所有关联到cookie的安全性方面的因素,所有的现代浏览器给用户提供了禁用cookie的选项。这意味着,除非在构建局域网应用程序时我们控制每个人的浏览器,其他时候的默认情况下,不应该企图依赖cookie。我们应尽量只在存储非关键信息时使用cookie。
据说,ASP.NET Framework的许多部分依赖cookie。例如,Web部件、表单验证、会话状态和匿名用户配置文件默认情况下都依赖cookie。如果你使用这些功能中的任何一个,就必须使用cookie。
并且,许多网站也依赖cookie。Yahoo!和MSDN网站的许多网页如果没有开启cookie就访问不了。换句话说,需要访问者开启cookie才能访问我们的网站,并不完全是不合理的要求。
3.1.2 创建cookie
可以通过给Response.Cookies集合添加cookie来创建新的cookie。Response.Cookies集合包含所有Web服务器端发送到Web浏览器的cookie。
例如,代码清单3-1所示的页面将创建一个新的名叫Message的cookie。该页面包含一个输入Message这个cookie的值的表单(见图3-1)。
图3-1 创建cookie
代码清单3-1 SetCookie.aspx
<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W 3C //DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
protected void btnAdd_Click(object sender, EventArgs e)
{
Response.Cookies["message"].Value = txtCookieValue.Text;
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
<title>Set Cookie</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:Label
id="lblCookieValue"
Text="Cookie Value:"
AssociatedControlID="txtCookieValue"
Runat="server" />
<asp:TextBox
id="txtCookieValue"
Runat="server" />
<asp:Button
id="btnAdd"
Text="Add Value"
OnClick="btnAdd_Click"
Runat="server" />
</div>
</form>
</body>
</html>
注意,cookie的名称是大小写敏感的。设置一个名叫message的cookie和设置一个名叫Message的cookie是不同的。
如果希望修改代码清单3-1所示的页面创建的cookie,可以打开页面,并给Message这个cookie输入一个新的值。当Web服务器端将它发送到浏览器时,修改的cookie值就会被设置到浏览器。
代码清单3-1所示的页面创建的是一个会话cookie(session cookie)。当关闭Web浏览器时,该cookie就消失了。如果希望创建持久化cookie(persistent cookie),则需要为cookie指定一个过期时间。
代码清单3-2所示的页面创建了一个持久化cookie。
代码清单3-2 SetPersistentCookie.aspx
<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W 3C //DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
void Page_Load()
{
// Get current value of cookie
int counter = 0;
if (Request.Cookies["counter"] != null)
counter = Int32.Parse(Request.Cookies["counter"].Value);
// Increment counter
counter++;
// Add persistent cookie to browser
Response.Cookies["counter"].Value = counter.ToString();
Response.Cookies["counter"].Expires = DateTime.Now.AddYears(2);
// Display value of counter cookie
lblCounter.Text = counter.ToString();
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
<title>Set Persistent Cookie</title>
</head>
<body>
<form id="form1" runat="server">
<div>
You have visited this page
<asp:Label
id="lblCounter"
Runat="server" />
times!
</div>
</form>
</body>
</html>
代码清单3-2所示的页面记录页面被请求的次数。一个名叫counter的持久化cookie被用来记录页面被请求的次数。注意,counter的过期时间被设为2年以后。当为某个cookie设置了过期时间后,cookie就被保存为持久化cookie了。
3.1.3 读取cookie
可以使用Response.Cookies集合创建和修改cookie,也可以使用Request.Cookies集合读取cookie值。
例如,代码清单3-3所示的页面读取message cookie的值。
代码清单3-3 GetCookie.aspx
<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W 3C //DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
void Page_Load()
{
if (Request.Cookies["message"] != null)
lblCookieValue.Text = Request.Cookies["message"].Value;
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
<title>Get Cookie</title>
</head>
<body>
<form id="form1" runat="server">
<div>
The value of the message cookie is:
<asp:Label
id="lblCookieValue"
Runat="server" />
</div>
</form>
</body>
</html>
在代码清单3-3中,IsNothing()函数用来在读取cookie值之前检查cookie是否存在。如果不包含该检查,就有可能获得一个空引用异常。同样,不要忘记cookie的名称是大小写敏感的。
代码清单3-4所示的页面列出包含在Request.Cookies集合中的所有cookie(见图3-2)。
图3-2 显示所有cookie列表
代码清单3-4 GetAllCookies.aspx
<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W 3C //DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
void Page_Load()
{
ArrayList colCookies = new ArrayList();
for (int i = 0; i < Request.Cookies.Count; i++)
colCookies.Add(Request.Cookies[i]);
grdCookies.DataSource = colCookies;
grdCookies.DataBind();
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
<title>Get All Cookies</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:GridView
id="grdCookies"
Runat="server"/>
</div>
</form>
</body>
</html>
注意,从Request.Cookies集合迭代获得的有意义的信息只有HasKey,Name和Value属性。其他列都显示了不正确的信息。例如,Expires列总是显示最小日期。浏览器不与页面请求交互这些额外的属性,所以也就获取不了这些属性的值。
使用Request.Cookies集合时,非常重要的一点是,理解For...Each循环返回的值和For...Next循环返回的值不同。如果用For...Each循环迭代Request.Cookies集合,返回的是cookies的名称。如果用For...Next循环迭代该集合,返回的是HeepCookie类(下一节介绍)的示例。
3.1.4 设置cookie属性
HttpCookie类代表cookie。当创建或读取一个cookie时,可以使用该类的下面这些属性:
q Domain——用于设置关联到cookie的域名,默认值是当前域名;
q Expires——用于通过给定一个过期时间创建一个持久化cookie;
q HasKeys——用于指定该cookie是否是一个多值cookie(见本章稍后的 3.1.6 节);
q HttpOnly——用于避免cookie被JavaScript访问;
q Name——用户指定cookie的名称;
q Path——用于指定关联到cookie的路径。默认值为/;
q Secure——用于指定cookie需要通过安全Socket层(SSL)连接传递;
q Value——允许读/写cookie的值;
q Values——当使用多值cookie时,用于读/写特定的值(见本章稍后的 3.1.6 节)。
这些属性中的一部分需要更多的解释。例如,你可能发现Domain属性有些奇怪,因为你修改不了关联到cookie的domain。
Domain属性对于组织子域名时会非常有用。如果需要设置cookie可以被Sales.MyCompany.com,Managers.MyCompany.com和Support.MyCompany.com访问,则需要设置Domain属性值为.MyCompany.com(注意开头的部分),而不能使用该属性关联cookie到一个完全不同的域名。
HttpOnly属性用户设置一个cookie是否可以通过JavaScript访问。该属性只对IE6(SP1)级以上版本有效。引入该属性是为了防止跨站点脚本攻击。
Path属性用于限定cookie到一个特定的路径。例如,如果在相同的域名部署多个应用程序,而不希望应用程序共享相同的cookie,则需要设置Path属性避免一个应用程序读取另一个应用程序的cookie。
Path属性听起来很有用。不幸的是,你不应该使用它。IE对于路径是大小写敏感的。如果用户在地址栏输入一个不同大小写的路径,则cookie不会被发送。换句话说,下面两个路径并不匹配:
http://localhost/original/GetAllCookies.aspx
http://localhost/ORIGINAL/GetAllCookies.aspx
3.1.5 删除cookie
删除一个cookie的方法并不直观。要删除一个存在的cookie,必须设置其过期时间为一个过去的时间。
代码清单3-5所示的页面演示了如何删除一个单值cookie。页面包含一个用于输入cookie名称的表单。当提交表单时,指定名称的cookie就被删除了。
代码清单3-5 DeleteCookie.aspx
<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W 3C //DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
protected void btnDelete_Click(object sender, EventArgs e)
{
Response.Cookies[txtCookieName.Text].Expires = DateTime.Now.AddDays(-1);
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
<title>Delete Cookie</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:Label
id="lblCookieName"
Text="Cookie Name:"
AssociatedControlID="txtCookieName"
Runat="server" />
<asp:TextBox
id="txtCookieName"
Runat="server" />
<asp:Button
id="btnDelete"
Text="Delete Cookie"
OnClick="btnDelete_Click"
Runat="server" />
</div>
</form>
</body>
</html>
当需要删除cookie时设置的特定的日期只要是一个过去的值,无所谓实际的值是多少。在代码清单3-5中,过期时间被设为一天前。
代码清单3-6所示的页面从浏览器删除当前域名(和路径)下的所有cookie。
代码清单3-6 DeleteAllCookies.aspx
<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W 3C //DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
void Page_Load()
{
string[] cookies = Request.Cookies.AllKeys;
foreach (string cookie in cookies)
{
BulletedList1.Items.Add("Deleting " + cookie);
Response.Cookies[cookie].Expires = DateTime.Now.AddDays(-1);
}
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
<title>Delete All Cookies</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<h1>Delete All Cookies</h1>
<asp:BulletedList
id="BulletedList1"
EnableViewState="false"
Runat="server" />
</div>
</form>
</body>
</html>
代码清单3-6所示的页面从Request.Cookies集合中循环遍历并删除所有的cookie。
3.1.6 使用多值cookie
根据cookie规范,对单个域名,浏览器不能存储超过20个cookie。可以通过创建多值cookie来超越该限制。多值cookie是一个包含子键的单一cookie。可以根据需要创建任意数量的子键。
例如,代码清单3-7所示的页面创建了一个名叫preferences的多值cookie。preferences cookie用于存储first name(名)、last name(姓)和favorite color(喜爱的色彩)(见图3-3)。
图3-3 创建多值cookie
代码清单3-7 SetCookieValues.aspx
<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W 3C //DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
void btnSubmit_Click(Object s, EventArgs e)
{
Response.Cookies["preferences"]["firstName"] = txtFirstName.Text;
Response.Cookies["preferences"]["lastName"] = txtLastName.Text;
Response.Cookies["preferences"]["favoriteColor"] = txtFavoriteColor.Text;
Response.Cookies["preferences"].Expires = DateTime.MaxValue;
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
<title>Set Cookie Values</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:Label
id="lblFirstName"
Text="First Name:"
AssociatedControlID="txtFirstName"
Runat="server" />
<br />
<asp:TextBox
id="txtFirstName"
Runat="server" />
<br /><br />
<asp:Label
id="lblLastName"
Text="Last Name:"
AssociatedControlID="txtFirstName"
Runat="server" />
<br />
<asp:TextBox
id="txtLastName"
Runat="server" />
<br /><br />
<asp:Label
id="lblFavoriteColor"
Text="Favorite Color:"
AssociatedControlID="txtFavoriteColor"
Runat="server" />
<br />
<asp:TextBox
id="txtFavoriteColor"
Runat="server" />
<br /><br />
<asp:Button
id="btnSubmit"
Text="Submit"
OnClick="btnSubmit_Click"
Runat="server" />
</div>
</form>
</body>
</html>
当提交代码清单3-7所示的页面时,下面的HTTP首部被发送到浏览器:
Set-Cookie: preferences=firstName=Steve&lastName=Walther&favoriteColor=green;
expires=Fri, 31-Dec-9999 23:59:59 GMT; path=/
代码清单3-8所示的页面从preferences cookie读取值。
代码清单3-8 GetCookieValues.aspx
<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W 3C //DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
void Page_Load()
{
if (Request.Cookies["preferences"] != null)
{
lblFirstName.Text = Request.Cookies["preferences"]["firstName"];
lblLastName.Text = Request.Cookies["preferences"]["lastName"];
lblFavoriteColor.Text = Request.Cookies["preferences"]["favoriteColor"];
}
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
<title>Get Cookie Values</title>
</head>
<body>
<form id="form1" runat="server">
<div>
First Name:
<asp:Label
id="lblFirstName"
Runat="server" />
<br />
Last Name:
<asp:Label
id="lblLastName"
Runat="server" />
<br />
Favorite Color:
<asp:Label
id="lblFavoriteColor"
Runat="server" />
</div>
</form>
</body>
</html>
可以使用HttpCookie.HasKey属性判断一个cookie是一个普通cookie还是一个多值cookie。
3.2 使用会话状态
你不可能真的用cookie来保存购物车。cookie太小也太简单了。要突破cookie的限制,ASP.NET Framework支持一个名叫Session状态的功能。
和cookie一样,保存在Session状态中的项的作用范围是特定的用户。可以使用Session状态在多个页面请求间存储用户设置信息或其他用户相关的数据。
和cookie不一样的是,Session状态没有大小限制。如果有极端的需求,甚至可以在Session状态中存储上G的数据。
并且,和cookie不一样,Session状态可以保存更复杂的对象,而不像cookie只能存储简单的字符串文本。可以在Session状态中存储任意的对象。例如,可以在Session状态存储一个DataSet或者一个自定义购物车对象。
可以使用Session对象为Session状态添加项。例如,代码清单3-9所示的页面添加一个名叫message的新项到Session状态,包含值Hello World!。
代码清单3-9 SessionSet.aspx
<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W 3C //DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
void Page_Load()
{
Session["message"] = "Hello World!";
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
<title>Session Set</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<h1>Session item added!</h1>
</div>
</form>
</body>
</html>
在代码清单3-9的Page_Load事件处理中,一个新的项被添加到Session对象中。可以像使用HashTable集合那样使用Session对象。
代码清单3-10所示的页面演示了怎样读取保存在Session状态中的项的值。
代码清单3-10 SessionGet.aspx
<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W 3C //DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
void Page_Load()
{
lblMessage.Text = Session["message"].ToString();
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
<title>Session Get</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:Label
id="lblMessage"
Runat="server" />
</div>
</form>
</body>
</html>
当使用Session状态时,一个名叫ASP.NET_SessionId的cookie会自动添加到浏览器。这个cookie包含了一个唯一标识符。它可以在页面切换时跟踪用户。
当添加对象到Session对象时,该对象将存储在Web服务器端上而不是Web浏览器上。ASP.NET_ SessionId用于正确地关联数据和用户。
默认情况下,如果cookie被禁用,Session状态也不能工作了。你不会得到任何错误,但是,添加到Session状态的项目在请求后续页面时不能被访问到(后面你会了解到如何启用不依赖于cookie的Session状态)。
注意 要小心不要过度滥用Session状态。因为会为每个请求页面的用户单独创建一份所有添加到Session状态中项的副本。如果放置一个包含400条记录的DataSet到Session状态中,并且有500个用户在请求这个页面,内存中就会有500份DataSet的副本。
默认情况下,ASP.NET Framework假设如果用户超过20分钟不请求任何页面,则认为此用户离开了网站。此时,该用户保存在Session状态中的数据会被丢弃。
3.2.1 在Session状态中保存数据库数据
可以使用Session状态来创建用户相关的缓存。例如,可以为一个用户载入数据,然后允许用户排序或过滤该数据。
代码清单3-11所示的页面载入一个DataView到Session状态中。用户可以使用GridView控件排序DataView的内容(见图3-4)。
代码清单3-11 SessionDataView.aspx
<%@ Page Language="C#" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Web.Configuration" %>
<!DOCTYPE html PUBLIC "-//W 3C //DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
DataView dvMovies;
/// <summary>
/// Load the Movies
/// </summary>
void Page_Load()
{
dvMovies = (DataView)Session["Movies"];
if (dvMovies == null)
{
string conString = WebConfigurationManager.ConnectionStrings["Movies"].ConnectionString;
SqlDataAdapter dad = new SqlDataAdapter("SELECT Id,Title,Director FROM Movies", conString);
DataTable dtblMovies = new DataTable();
dad.Fill(dtblMovies);
dvMovies = new DataView(dtblMovies);
Session["Movies"] = dvMovies;
}
}
/// <summary>
/// Sort the Movies
/// </summary>
protected void grdMovies_Sorting(object sender, GridViewSortEventArgs e)
{
dvMovies.Sort = e.SortExpression;
}
/// <summary>
/// Render the Movies
/// </summary>
void Page_PreRender()
{
grdMovies.DataSource = dvMovies;
grdMovies.DataBind();
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
<title>Session DataView</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:GridView
id="grdMovies"
AllowSorting="true"
EnableViewState="false"
OnSorting="grdMovies_Sorting"
Runat="server" />
<br />
<asp:LinkButton
id="lnkReload"
Text="Reload Page"
Runat="server" />
</div>
</form>
</body>
</html>
图3-4 排序存储在Session状态中的DataView
在代码清单3-11中,DataView对象存储在Session状态中。当排序GridView控件时,DataView也被排序。
代码清单3-11所示的页面包含一个用来重新载入页面的链接。注意,GridView显示的记录的排序顺序有记忆性,即使在从该页面访问另一页面再返回时还是能保持。
3.2.2 使用Session对象
用户操作Session状态的最主要的应用程序编程接口是HttpSessionState类。该对象被Page.Session,Context.Session,UserControl.Session,WebService.Session和Application. Session属性暴露。也就是说,基本上可以在任何地方访问Session状态。
HttpSessionState支持下面这些属性:
q CookieMode——用来指定是否启用不依赖cookie的Session状态功能。可能的值包括AutoDetect、UseCookies、UseDiviceProfile和UseUri;
q Count——用来获得Session状态中包含的项的数量;
q IsCookieless——用来指定是否启用不依赖cookie的Session状态功能;
q IsNewSession——用来检测当前请求是否创建了新的用户会话;
q IsReadOnly——用来检测该Session状态是否是只读的;
q Keys——用来获取保存在Session状态中的项目的名称列表;
q Mode——用来指定当前的Session状态的存储处理程序。可能的值包括Custom、InProc、Off、SqlServer和StateServer;
q SessionID——用来获得唯一的会话标识符;
q Timeout——用来指定Web服务器端假设用户已经离开并取消Session状态的过期分钟数。最大值为525 600(1年)。
HttpSessionState对象还包含了如下方法:
q Abandon——用来终止一个用户会话;
q Clear——用来清除Session状态中的所有项目;
q Remove——用来从Session状态中删除特定的项。
Abandon()方法允许以编程的方式终止一个用户会话。例如,你可能希望当用户登出系统时自动清除所有的用户会话状态信息。
3.2.3 提交会话事件
Global.asax中包含两个可以处理的关联Session状态的事件:Session Start和Session End事件。
Session Start事件在一个新会话开始时被触发。可以利用该事件从数据库载入用户信息。例如,可以利用Session Start事件载入用户购物车。
Session End事件在会话终止时被触发。会话会因用户不活动而过期或者被显式地使用Session. Abandon()方法而终止。可以利用Session End事件,例如,在希望自动保存用户的购物车到数据库表中时。
代码清单3-12所示的Global.asax文件演示了如何处理Session Start和Session End事件。
代码清单3-12 Global.asax
<%@ Application Language="C#" %>
<script runat="server">
void Application_Start(object sender, EventArgs e)
{
Application["SessionCount"] = 0;
}
void Session_Start(object sender, EventArgs e)
{
Application.Lock();
int count = (int)Application["SessionCount"];
Application["SessionCount"] = count + 1;
Application.UnLock();
}
void Session_End(object sender, EventArgs e)
{
Application.Lock();
int count = (int)Application["SessionCount"];
Application["SessionCount"] = count - 1;
Application.UnLock();
}
//public void Profile_OnMigrateAnonymous(object sender, ProfileMigrateEventArgs args)
//{
// // Get anonymous profile
// ProfileCommon anonProfile = Profile.GetProfile(args.AnonymousID);
// // Copy anonymous properties to authenticated
// foreach (SettingsProperty prop in ProfileBase.Properties)
// Profile[prop.Name] = anonProfile[prop.Name];
// // Kill the anonymous profile
// ProfileManager.DeleteProfile(args.AnonymousID);
// AnonymousIdentificationModule.ClearAnonymousIdentifier();
//}
//public void Profile_ProfileAutoSaving(object s, ProfileAutoSaveEventArgs e)
//{
// if (Profile.ShoppingCart.HasChanged)
// e.ContinueWithProfileAutoSave = true;
// else
// e.ContinueWithProfileAutoSave = false;
//}
</script>
在代码清单3-12中,Global.asax文件用来跟踪活动会话的数量。任何时候一个新的会话开始时,Session Start事件将被触发,SessionCount变量增加1。当会话终止时,Session End事件被触发,SessionCount变量减1。
SessionCount变量被保存在Application状态中。Application状态包含的项被应用程序的所有用户共享。注意,在修改Application对象时锁住了它。必须对Application对象加锁和解锁,因为在同一时间,有可能多个用户都访问Application状态中相同的项。
注解 应用程序状态在ASP.NET中很少被使用。大多数情况下,应该使用Cache对象代替Application状态,因为Cache对象被设计成能够自动地管理内存。
代码清单3-13所示的页面用一个Label控件显示活动会话的数量(见图3-5)。
图3-5 显示用户会话统计
代码清单3-13 ShowSessionCount.aspx
<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W 3C //DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
void Page_Load()
{
lblSessionCount.Text = Application["SessionCount"].ToString();
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
<title>Show Session Count</title>
</head>
<body>
<form id="form1" runat="server">
<div>
Total Application Sessions:
<asp:Label
id="lblSessionCount"
Runat="server" />
</div>
</form>
</body>
</html>
注意 并不是所有的会话存储处理程序都会触发Session End事件。InProc会话处理程序(默认的会话处理程序)会触发,但是StateServer或SQLServer状态处理程序就不会触发。
3.2.4 会话失效控制
默认情况下,ASP.NET Framework假设,过20分钟用户还不请求页面,则认为他已离开应用程序。在某些情形下,我们可能会修改默认的过期值。
假如我们创建一个大学申请入学网站,网站包含一个让申请者输入一篇很长的文章的表单。在这种情形下,我们可能不希望用户会话在20分钟后就过期。
增加会话的过期时间的坏处是,应用程序会消耗更多内存。会话的过期时间越长,潜在地就有可能消耗更多的服务器端内存。
可以在Web配置文件中设置会话过期时间,或者也可以以编程的方式设置会话的过期时间。例如,代码清单3-14所示的Web配置文件将会话过期时间设为了60(1小时)。
代码清单3-14 Web.config
<?xml version="1.0"?>
<configuration>
<system.web>
<sessionState timeout="60" />
</system.web>
</configuration>
也可以以编程的方式设置Session对象的Timeout属性来修改会话过期时间。例如,下面的语句修改Session过期时间从20分钟到60分钟。
Session.Timeout = 60;
执行以上的语句后,用户会话的过期时间值就被修改了。当用户访问其他页面时,该值也一样有效。
3.2.5 使用Cookieless的会话状态
默认情况下,Session状态依赖cookie。ASP.NET Framework利用ASP.NET_SessionId这个cookie来识别跨页面请求的用户,这样,正确的数据就能关联到正确的用户。如果用户在浏览器中禁用了cookie,Session状态就不能工作了。
如果希望在cookie被禁用时Session状态还是能工作,就应该使用无cookie的会话。当启用无cookie的会话时,用户的会话ID添加到页面的URL中。
下面是一个启用无cookie的会话时的页面URL的样子:
http://localhost:4945/Original/(S(5pnh11553sszre45oevthxnn))/SomePage.aspx
URL中的奇怪的代码就是当前用户的会话ID。它的值和从Session.SessionID属性获得的值是一样的。
可以修改Web配置文件的sessionState元素,启用无cookie的会话。sessionState元素包含一个cookieless属性接受下面的值:
q AutoDetect——当浏览器启用cookie时,会话ID保存在cookie中,否则,会话ID添加到URL;
q UseCookies—会话ID总是被存于cookie(默认值);
q UseDeviceProfile——当浏览器支持cookie时,会话ID存于cookie,否则,会话ID添加到URL;
q UseUri——会话ID总是添加到URL。
当设置cookieless的值为UseDeviceProfile时,ASP.NET Framework通过位于下面文件夹中的一组文件查询浏览器是否支持cookie:
/WINDOWS/Microsoft.NET/Framework/[version]/CONFIG/Browsers
根据这些文件,如果浏览器支持cookie,则ASP.NET Framework使用cookie存储会话ID。即使浏览器禁用了cookie,框架还是会试图添加cookie。
当cookieless的值被设为AutoDetect时,框架检查HTTP cookie头是否存在。如果cookie头被检测到,则框架在cookie中存储会话ID,否则,将会话ID添加到页面到URL。
代码清单3-15所示的Web配置文件给cookieless属性设置了值AutoDetect,以启用不依赖于cookie的会话。
代码清单3-15 Web.config
<?xml version="1.0"?>
<configuration>
<system.web>
<sessionState
cookieless="AutoDetect"
regenerateExpiredSessionId="true" />
</system.web>
</configuration>
注解 测试无cookie会话的最简单方法是使用Mozilla Firefox浏览器,因为该浏览器可以非常简单地禁用cookie。选择Tools菜单项中的Options。从Privacy标签下取消选中Allow Site to Set Cookies选项。
注意,代码清单3-16所示的配置文件还包含了一个regenerateExpiredSessionId属性。当启用无cookie的会话状态时,就应该启用该属性,因为它能帮助用户避免不小心共享Session状态。
代码清单3-16 Web.config
<?xml version="1.0"?>
<configuration>
<system.web>
<sessionState
mode="StateServer"
stateConnectionString="tcpip=localhost:42424"
stateNetworkTimeout="10" />
<machineKey
decryption="AES"
validation="SHA1"
decryptionKey=" 306C 1FA852AB3B0115150DD8BA30821CDFD 125538A 0C 606DACA53DBB 3C 3E0AD2"
validationKey=" 61A 8E 04A 146AFFAB81B6AD 19654F 99EA 7370807F 18F 5002725DAB98B8EFD 19C 711337E26948E
26D1D174B159973EA0BE8CC9CAA6AAF513BF84E44B2247792265" />
</system.web>
</configuration>
例如,有人在一个论坛讨论区提交了一个链接,它链接到一个启用无cookie的会话状态的ASP.NET网站,它包含了会话ID。如果有人在原来的会话过期的情况下点击了该链接,则一个新的会话会自动开始。然而,如果多个用户同时点击了该链接,那么所有用户就会共享该会话ID,并且,他们就会共享相同的Session状态,这就是一个很严重的安全问题。
而启用了RegenerateExpiredSessionId,并且会话过期时,当某人请求该页面时,URL中的会话ID会重新生成。会有一个到当前页面的重定向以修改URL中的会话ID。如果链接被发布到一个论坛讨论区,或者通过邮件发送给多个用户,则每个点击链接的用户会分配一个新的会话ID。
当启用无cookie的会话,并在应用程序的页面间链接时,需要注意尽量使用相对URL。否则,会话ID不能自动添加到URL。
例如,当在网站中链接另一个页面时,使用像这样的URL(一个相对URL):
/SomeFolder/SomePage.aspx
不要使用下面这样的URL(一个绝对URL):
http://SomeSite.com/SomeFolder/SomePage.aspx
如果出于某些原因需要使用绝对URL,则使用Response.ApplyAppPathModifier()方法添加会话ID到URL。该方法接受一个绝对URL,返回一个嵌入了会话ID的URL。
3.2.6 配置Session状态存储
默认情况下,Session状态被保存在ASP.NET所在的相同进程。这样做有两个主要缺点。
首先,进程内Session状态很脆弱。如果应用程序重起,则所有的Session状态将会丢失。多种不同的事件会导致应用程序重起。例如,修改Web.config文件或者应用程序错误都能导致应用程序重起。
其次,进程内Session状态可伸缩性较差。当Session状态保存在进程中时,它是保存在特定的Web服务器端上的。换句话说,不能在Web集群中使用进程内Session状态。
如果需要实现更健壮的Session状态版本,则ASP.NET Framework提供许多选项。可以通过修改Session状态模式配置ASP.NET Framework存储Session状态到另一个地方。
可以设置Session状态模式为以下值:
q Off——禁用Session状态;
q InProc——在ASP.NET进程所在的相同进程存储Session状态;
q StateServer——存储Session状态到独立于ASP.NET进程的一个Windows NT进程;
q SQLServer——存储Session状态到SQL Server数据库;
q Custom——存储Session状态到自定义位置。
默认情况下,Session状态模式是InProc,这是出于性能方面的考虑。进程内Session状态拥有最好的性能。然而,它牺牲了健壮性和伸缩性。
当设置Session状态模式为StateServer或SQLServer时,我们牺牲性能换取了健壮性和伸缩性。存储Session状态到进程外,拥有更差的性能,因为,Session状态信息必须在网络上来回传递。
最后,可以通过继承SessionStateStoreProviderBase类创建一个新类来创建自定义Session状态存储处理程序。此时,就可以保存Session状态到任何希望的地方。例如,可以创建一个Session状态存储处理程序保存Session状态到Oracle或Foxpro数据库。
配置状态服务器端Session状态
当启用状态服务器端(State Server)Session状态时,Session状态信息被保存在独立的Windows NT服务中。该Windows NT服务可以在Web服务器端所在的服务器端上,也可以在网络中的另一台服务器端上。
如果保存Session状态到一个独立的Windows NT服务的内存中,则Session状态信息即使在ASP.NET应用程序不工作时也会一直存在。例如,如果我们的ASP.NET应用程序崩溃了,Session状态信息不会丢失,因为它们保存在一个独立的进程中。
并且,可以创建一个Web集群来使用Windows NT Service保存状态信息。可以将网络中的某一台服务器端配置为状态服务器端。Web集群中的所有服务器端可以使用这个中央状态服务器端存储Session状态。
要使用状态服务器端Session状态,必须完成下面的两个步骤:
q 启动ASP.NET状态服务;
q 配置应用程序使用ASP.NET状态服务。
可以从开始菜单、控制面板、管理工具(见图3-6)打开Services小程序来启动ASP.NET状态服务。打开Services小程序后,双击ASP.NET状态服务,点击Start运行服务。应该将启动类型设为Automatic,这样服务就能在每次重起机器后自动启动。
如果希望在网络中的一台独立的服务器端上运行ASP.NET状态服务。则需要修改部署ASP.NET状态服务的服务器端的注册表。默认情况下,ASP.NET状态服务不接受远程连接。要允许远程连接,从命令行程序执行RegEdit,设置下面的注册表键值为1:
HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/aspnet_state/
Parameters/AllowRemoteConnection
启动ASP.NET状态服务后,需要配置ASP.NET应用程序来使用它。代码清单3-16所示的Web配置文件启用了状态服务器端Session状态。
图3-6 启动ASP.NET状态服务
代码清单3-16所示的Web配置文件修改了sessionState的三个属性。首先,mode属性被设为StateServer。接着,stateConnectionString属性用来指定ASP.NET状态服务器端的位置。在代码清单3-16中,连接位置被创建为在localhost,端口42424。最后,stateNetworkTimeout属性用来指定连接超时的秒数。
注解 可以通过修改下面的注册表值来配置ASP.NET状态服务器端使用一个不同的端口:
HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/aspnet_state/Parameters/Port
需要用Services小程序停止并重起ASP.NET状态服务,使修改生效。
注意,代码清单3-17所示的Web配置文件包含了一个machineKey元素。如果安装Web集群,并且需要不同的服务器端使用相同的状态服务器端,则必须指定显式的加密和验证键值。换句话说,当ASP.NET状态服务器端就是ASP.NET应用程序所在的机器时,无需包含machineKey元素。
代码清单3-17 Web.config
<?xml version="1.0"?>
<configuration>
<system.web>
<sessionState
mode="SQLServer"
sqlConnectionString="Data Source=YourServer;Integrated Security=True"
sqlCommandTimeout="30" />
<machineKey
decryption="AES"
validation="SHA1"
decryptionKey=" 306C 1FA852AB3B0115150DD8BA30821CDFD 125538A 0C 606DACA53DBB 3C 3E0AD2"
validationKey=" 61A 8E 04A 146AFFAB81B6AD 19654F 99EA 7370807F 18F 5002725DAB98B8EFD 19C 711337E26948E
26D1D174B159973EA0BE8CC9CAA6AAF513BF84E44B2247792265" />
</system.web>
</configuration>
注意 不要不做任何修改就使用代码清单3-17所示的Web配置文件中的decryptionKey和validationKey属性。这些值应该保密。可以使用第21章讨论过的GenerateKeys.aspx页面为这些属性生成新的值。
完成这些设置后,Session状态信息就被自动保存在ASP.NET状态服务器端了。当从进程内Session状态切换到过来时,不需要修改任何应用程序代码。
3.2.7 配置SQL Server Session状态
如果希望尽可能可靠地保存Session状态,那么可以保存Session状态到SQL Server数据库。因为可以设置一个失败转移(Failover)SQL Server集群。保存在SQL Server中的Session状态几乎能够在任何情况下保持有效,哪怕是遭受到非常严重的破坏。
必须完成下面的两个步骤以启用SQL Server Session状态:
q 配置数据库支持SQL Server Session状态;
q 配置应用程序使用SQL Server Session状态。
可以使用aspnet_regsql工具添加必要的表和存储过程来支持SQL Server Session状态。aspnet_regsql工具在如下路径所示的位置:
/WINDOWS/Microsoft.NET/Framework/[version]/aspnet_regsql.exe
注解 如果打开SDK命令行提示程序,那么不需要指定Microsoft.Net文件夹也能使用aspnet_regsql工具。
执行下面的命令就能为名为YourServer的服务器端上的SQL Server数据库启用SQL Server Session状态。
aspnet_regsql -C "Data Source=YourServer;Integrated Security=True" -ssadd
当执行该命令时,一个名叫ASPState的新数据库被创建到数据库服务器端。ASPState数据库包含所有被Session状态使用的存储过程。然而,默认情况下,Session状态信息保存在TempDb数据库。当数据库服务器端重启时,TempDb数据库会自动清空。如果希望使用基于失败转移集群的SQL Server数据库,则不要使用TempDb数据库。同样地,如果希望即使数据库重启Session状态依然有效,则不要使用TempDb数据库。
如果执行下面的命令,Session状态就被保存在ASPState数据库而不是TempDb数据库:
aspnet_regsql -C "Data Source=YourServer;Integrated Security=True" -ssadd -sstype p
注意,该命令包含一个 –sstype p 选项。p代表持久化。Session状态保存到ASPState数据库被称作持久化Session状态,因为即使数据库服务器端重启,Session状态依然有效。
最后,可以保存Session状态到自定义数据库。下面的命令保存Session状态到一个名为MySessionDB的数据库中:
aspnet_regsql -C "Data Source=YourServer;Integrated Security=True"
-ssadd -sstype c -d MySessionDB
执行该命令将创建一个新的名为MySessionDB的数据库,包含用于保存Session状态的所有表和存储过程。注意,-sstype选项包含一个c代表自定义(custom)。该命令同样包含一个–d选项用于指定新数据库的名称。
如果希望从服务器端移除Session状态表和存储过程,则可以执行下面的命令:
aspnet_regsql -C "Data Source=YourServer;Integrated Security=True" -ssremove
执行该命令将移除ASPState数据库。它不会删除自定义Session状态数据库。必须手动移除自定义数据库。
配置数据库服务器端支持Session状态后,必须配置ASP.NET应用程序连接数据库。可以使用代码清单3-17所示的Web配置文件连接名为YourServer的数据库。
sessionState元素包括三个属性。mode属性设为SQLServer以启用SQL Server Session状态。第二个属性sqlConnectionString包含连接到Session状态数据库的连接字符串。最后,sqlCommandTimeout属性指定命令读取或保存Session状态的超时时间的最大秒数。
注意,代码清单3-18所示的配置文件包含了一个machineKey元素。如果Session状态服务器端的位置不在ASP.NET应用程序所在的服务器端,则需要包括machineKey元素,包含显式的加密和验证键值。
注意 不要不修改decryptionKey和validationKey属性的值就使用代码清单3-17所示的Web配置文件。可以使用第2章讨论过的GenerateKeys.aspx页面为这些属性生成新的值。
如果选择存储Session状态到自定义数据库,则当执行aspnet_regsql.exe工具时,需要在配置文件中指定自定义数据库。可以使用代码清单3-18所示的Web配置文件:
代码清单3-18 Web.config
<?xml version="1.0"?>
<configuration>
<system.web>
<sessionState
mode="SQLServer"
sqlConnectionString="Data Source=YourServer;Integrated Security=True;database=MySessionDB"
sqlCommandTimeout="30"
allowCustomSqlDatabase="true"/>
<machineKey
decryption="AES"
validation="SHA1"
decryptionKey=" 306C 1FA852AB3B0115150DD8BA30821CDFD 125538A 0C 606DACA53DBB 3C 3E0AD2"
validationKey=" 61A 8E 04A 146AFFAB81B6AD 19654F 99EA 7370807F 18F 5002725DAB98B8EFD 19C 711337E
26948E26D1D174B159973EA0BE8CC9CAA6AAF513BF84E44B2247792265" />
</system.web>
</configuration>
代码清单3-18所示的配置文件中的sessionState元素包含括allowCustomSqlDatabase属性。并且,allowCustomSqlDatabase属性包含自定义数据库的名称。
启用SQL Server Session状态不会影响你编写任何应用程序代码。可以在开始的时候使用进程内Session状态,然后在需要的时候,切换到SQL Server Session状态。