您的位置:首页 > 其它

定制会话状态管理

2008-01-03 20:38 260 查看
http://book.csdn.net/bookfiles/73/100732052.shtml

13.4 定制会话状态管理

ASP.NET会话状态从一开始就被设计成一个易于定制和扩展的特征。由于各种原因,在ASP.NET
1.x中,它实现了高度定制,但是完全缺乏可扩展性。在ASP.NET
2.0中,会话状态子系统被重构,以允许开发人员替代大多数功能——即,一个通常称为会话状态可插性(session-state
pluggability)的特征。

总的看来,可以用如下三种方案来定制会话状态管理:

仍然使用默认的会话状态模块,但是编写一个定制的状态提供程序以改变存储介质(例如,一个非SQL Server数据库或一个不同的表布局)。这样做时,还有机会重写一些辅助类(主要是集合类),这些辅助类用来把数据从存储器中传输到Session对象或反之。

仍然使用默认的会话状态模块,但是替换会话ID生成器。生成会话ID的算法是应用程序的关键要素,因为如果使会话ID对攻击者而言太好猜测,可能会直接导致会话拦截攻击。然而,这仍然是会话状态一个可定制的方面,只要正确地使用,可以使我们的应用程序更安全。

我们可以关掉会话状态模块,而启动自己的模块。在ASP.NET
1.x中,这种方案在技术上也是可行的,但应当把它作为最后一着。显然,这种方案提供了最大的灵活性,但是它也特别复杂,并且只有在完全必须并且确切地知
道我们要做什么时才建议使用这种方案。本书不介绍该主题。

第1个方案最容易、最不复杂,它解决了大多数需要定制的会话管理的场景。因此,让我们先来介绍它。

13.4.1 建立一个定制的会话状态提供程序

会话状态提供程序是负责提供与当前会话有关的任何数据的组件。会话状态提供程序在请求需要获取状态信息时被调用,从给定的存储介质中获取数据,并把
它返回给会话状态模块。然后在请求结束时被会话状态模块调用,把提供的数据写入存储层。如前所述,ASP.NET支持三个状态提供程序,如表13.11所
示。

表13.11 默认的状态提供程序

名 称



存储介质

InProc

InProcSessionStateStore

把数据作为活对象存储到ASP.NET Cache中

StateServer

OutOfProcSessionStateStore

把数据作为序列化对象存储到称为aspnet_state.exe的Windows服务的内存中

SQLServer

SqlSessionStateStore

把数据作为序列化对象存储到一个SQL Server数据库中

在ASP.NET 2.0中,可以编写自己的状态提供程序类,使用自己所选的存储介质。要注意的是,默认的状态提供程序也使用各种辅助类传输数据。在定制提供程序中,也可以替换这些类,或者坚持使用标准的类。

1. 定义会话状态存储

状态提供程序(通常也称为会话状态存储)是一个继承SessionStateStoreProviderBase的类。表13.12列出了SessionStateStoreProviderBase类的主要方法。

表13.12 SessionStateStoreProviderBase类的方法

方 法

描 述

CreateNewStoreData

创建一个包含新会话的数据的对象。它应当返回一个SessionStateStoreData类型的对象

CreateUninitializedItem

在数据源中创建一个新的、未初始化的会话。在一个无cookie的会话状态中,当一个过期的会话被请求时,调用该方法。在这种情况下,会话状态模块必须生成一个新的会话ID。该方法创建的会话项使用该新生成的会话ID防止下一个请求被错误地定向到一个过期的会话

Dispose

释放状态提供程序使用的所有资源(除了内存以外)

EndRequest

当默认的会话状态模块开始处理EndRequest时间时调用该方法

GetItem

返回数据存储中与指定的ID匹配的会话项。所选的会话项被锁定进行读操作。该方法服务于那些从使用只读会话状态的应用程序发出的请求

GetItemExclusive

返回数据存储中与指定的ID匹配的会话项,并锁定它进行读取操作。用于那些使用读/写会话状态的应用程序发出的请求

Initialize

继承基本的提供程序类,执行一次性的初始化

InitializeRequest

当默认的会话状态模块开始处理AcquireRequestState事件时调用该方法

ReleaseItemExclusive

对以前通过调用GetItemExclusive方法被锁定的一个会话项进行解锁

RemoveItem

从数据存储中删除一个会话项。当一个会话结束时或被放弃时调用该方法

ResetItemTimeout

重置一个会话项的到期时间。当应用程序禁用会话支持时调用该方法

SetAndReleaseItemExclusive

把一个会话项写入数据存储中

SetItemExpireCallback

默认的会话状态模块调用该方法,以通知数据存储类调用者已经注册了一个Session_End事件处理程序

继承SessionStateStoreProviderBase类的类使用默认的ASP.NET会话状态模块,并且只替换其中处理会话状态数据存储和检索的部分。其他任何会话功能都不变。

2. 封锁和到期

相同会话的两个请求可以并发进行吗?当然。请回头看看图13.3。请求可以并行到
达——例如,从两个框架同时到达的两个请求,或者当用户使用相同浏览器的两个实例,其中第2个实例作为新窗口打开时。为了避免问题,状态提供程序必须实现
一种封锁机制,以序列化对一个会话的访问。会话状态模块确定该请求需要对会话状态进行只读访问还是读/写访问,并相应地调用GetItem或
GetItemExclusive。在这些方法的实现中,提供程序的作者应当创建一种reader/writer封锁机制,以允许多个并发读取操作,但是
防止写入被锁定的会话。

另一个问题涉及让会话状态模块知道给定的会话何时到期。如果在global.asax中定义了一个Session_End处理程序,则会话状态模块调用SetItemExpireCallback方法。通过该方法,状态提供程序接受一个回调方法,该回调方法的原型如下:

public delegate void SessionStateItemExpireCallback(

string sessionID, SessionStateStoreData item);

它必须在内部存储这个委派,并在给定的会话超时时调用它。支持到期回调是可选的,实际上,只有InPorc提供程序真正支持它。如果定制提供程序不愿意支持到期回调,则应当指示SetItemExpireCallback方法返回false。


注意


打算支持无cookie的提供程序还必须实现CreateUninitialized方法,以便把一个空的会话项写入数
据存储中。更确切地说,一个空的会话项表示一个具备任何其他条件只是不包含任何数据的会话项。换句话说,该会话项应包含会话ID、创建日期、或许还包括锁
ID,但是不包含任何数据。每当对一个过期的会话发出一个请求时,ASP.NET
2.0生成一个新的会话ID(只有在无cookie模式下)。会话状态模块生成这个新的会话ID,并重定向到浏览器。如果没有用一个新生成的ID标记的一
个未初始化的会话项,则新请求将再次被认为是对一个过期会话发出的请求。

3. 替换会话数据字典

SessionStateStoreData是表示会话项的类——即,包含与该会话有关的所有数据的数据结构。实际上,定义GetItem和
GetItemExclusive的目的是返回该类的一个实例。该类有3个属性:Items、StaticObjects和Timeout。

Items指出最终将通过Session属性传递给页面的名称/值集合;StaticObjects列出属于该会话的静态对象,如
global.asax文件中定义的且在该会话范围内的对象。顾名思义,Timeout指出会话状态项在多长时间内是有效的(以分钟为单位)。默认值为
20分钟。

一旦会话状态模块获得了请求的会话状态,它就用Items集合的内容刷新HttpSessionStateContainer类的一个新实例。然后该对象传递给HttpSessionState类的构造方法,成为我们熟悉的Session属性的数据容器。

SessionStateStoreData类在基本状态提供程序类的定义中使用,这就意味着不能完全替换它。然而,如果不喜欢它,可以从它那儿继
承一个新类。对于会话模块和状态提供程序来说,会话项的容器只是一个实现了ISessionStateItemCollection接口的类。默认使用的
真实的类是SessionStateItemCollection。我们可以用自己的类替换该类,前提是实现上述接口。

提示


要编写一个状态提供程序,可以发现SessionStateUtility类的方法很有帮助。该类包含把会话项序列化到存储介质
中的方法,以及从存储介质中反序列化出会话项的方法。同样,该类具有提取一个会话的数据字典,并把它添加到HTTP上下文和Session属性的方法。

4. 注册一个定制的会话状态提供程序

为了使一个定制的会话状态提供程序可用于一个应用程序,需要在web.config文件中注册它。假设已经调用了提供程序类SampleSessionStateProvider,并把它编译成MyLib。下面给出了需要输入的内容:

<system.web>

<sessionState mode="Custom"

customProvider="SampleSessionProvider">

<providers>

<add name="SampleSessionProvider"

type="SampleSessionStateProvider, MyLib" />

</providers>

</sessionState>

</system.web>

提供程序的名称是任意的,但是必不可少的。要让会话状态模块找到它,应将mode属性设置为Custome。

13.4.2 生成一个定制的会话ID

为了生成会话ID,ASP.NET
2.0使用一个称为SessionIDManager的专用组件。从技术上讲,该类既不是一个HTTP模块,也不是一个提供程序。更简单地讲,它是一个继
承System.Object的类,并且实现了ISessionIDManager接口。我们可以用定制组件替换该接口,前提是定制组件要实现相同的
ISessionIDManager接口。为了决定是否真的需要一个定制的会话ID生成器,首先来回顾一些有关该默认模块的事实。

1. 默认行为

默认的session-ID模块作为一个字节数组生成一个会话ID,该数组有15个强加密的随机数。然后将该数组编码成一个24 URL可接受的字符串,而系统把它作为会话ID看待。

根据<sessionState>配置节中的cookieless属性的值,该会话ID能够以HTTP
cookie或mangled
URL往返客户端。要注意的是,如果使用无cookie会话,则session-ID模块负责把ID添加到URL和重定向浏览器。默认的生成器把浏览器重
定向到一个如下所示的虚假URL:
http://www.contoso.com/test/(S(session_id))/page.aspx
在ASP.NET
1.x中,该虚假URL略有不同,不包括S(…)限定符。如何正确地服务对该虚假URL的请求呢?在无cookie会话情况下,session-ID模块
根据一个小且简单的ISAPI筛选器(aspnet_filter.dll,ASP.NET
1.x也有),动态地重写要访问的URL。该请求被正确地服务,但是地址栏上的路径不变。检测到的会话ID置于一个称为
AspFilterSessionId的请求头中。

2. 自制的session-ID管理器

弄清楚session-ID管理器是一个实现了ISessionIDManager的类后,我们可以采用两种实现方案:建立一个新类并从头开始实现
该接口;或者从ISessionIDManager继承一个新类,并重写两个虚拟方法以应用一些个性化。第1种方案提供了最大灵活性;第2种方案的实现更
兼容、更快,并且它提出了我们可能必须建立一个定制的session-ID生成器的最有说服力的理由——提供自己的session-ID值。

首先让我们回顾ISessionIDManager接口的方法,如表13.13所示。

表13.13 ISessionIDManager接口的方法

方 法

描 述

CreateSessionID

虚拟方法。它为会话创建一个惟一的会话标识符

Decode

使用HttpUtility.UrlDecode对会话ID进行解码

Encode

使用HttpUtility.UrlEncode对会话ID进行编码

Initialize

在实例化后立即被会话状态激活;执行该组件的一次性初始化

InitializeRequest

正在为请求获取会话状态时被会话状态调用

GetSessionID

从当前的HTTP请求获得会话ID

RemoveSessionID

从cookie或URL中删除会话ID

SaveSessionID

把一个新建的会话ID保存到HTTP响应

Validate

确认会话ID是有效的

如果计划自己编写完全定制的session-ID生成器,请记住以下几点:

ID生成算法非常关键。如果不实现强加密随机性,则在相同的会话仍然有效时恶意用户可能会猜测一个有效的会话ID,从而访问一些用户数据。(这就是所谓的会话拦截。)定制的session-ID算法的一个良好实例是一个返回全局惟一标识符(GUID)的算法。

可以选择是否支持无cookie会话。如果这样,必须赋予组件从HTTP请求提取会话ID和重定向浏览器的能力。可能需要一个ISAPI筛选器或HTTP模块来预处理该请求,并进行相应的修改。用来存储无cookie会话ID的算法由我们自己决定。

如果决定让系统使用自己的会话ID,则从SessionIDManager派生一个新类,并重写两个方法:CreateSessionID和
Validate。前一个方法返回一个包含会话ID的字符串;后者对给定的会话ID进行验证,确保它符合我们设定的规范。创建了一个定制的session
-ID模块以后,还要在配置文件中注册它。下面的清单说明了如何注册:

<sessionState

sessionIDManagerType="Samples.MyIDManager, MyLib" />

</sessionState>

会话状态性能最佳实践

状态管理是一种无可避免之灾祸。通过启用它,应用程序增加了额外的负担。MSDN Magazine的2005年12月期刊出了这样一篇文章,即运用ASP.NET团队的最佳编码实践,减少会话状态对Web应用程序的性能的影响。

第1条指导原则是尽可能地禁用会话状态。然而,为了防止会话过期,HTTP模块仍然在数据存储中把该会话标记为活动。对于进程外的状态服务器,这意
味着需要一个往返行程。定制的session-ID管理器为那些已经知道不需要获取会话状态的请求返回一个null会话ID,这是避开此问题的最佳办法,
并且完全避免了开销。(编写一个继承SessionIDManager的类,并重写GetSessionID方法。)

第2条指导原则要求对会话数据的竞争达到最小,使会话启用的处理程序不必对框架和可下载的资源提供服务。

第3条指导原则与数据序列化和反序列化有关。就会话管理而言,始终应当使用简单类型,至少应当将复杂的类分解成简单属性的数组。换句话说,不能建议
大家提取DAL类——就像把它们序列化到会话存储中一样。另一种方法需要建立一个定制的序列化算法,专门针对会话状态存储进行优化。将一个类分解成很多不
同的属性(每个属性存储在一个会话槽中)有优点,这不仅仅是由于使用了简单类型,而且还由于解决方案的极限粒度,使数据发生变化时的保存量达到最小。如果
一个属性发生了变化,只需更新一个具有简单类型的会话槽,而不是一个具有复杂类型的会话槽。

上一页 首页 下一页
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: