asp.net Session会话层使用与管理方法

作者:袖梨 2022-06-25

1. 保留默认的会话模块,但编写自定义的状态提供程序来更改存储介质。这样,我们还有机会重写一些用于在存储与session之间运载会话数据的辅助类。

  2. 保留默认的会话模块,但替换会话id生成器。
  3. 将默认的会话状态模块替换成自己的。这种方法提供了最好的灵活性,但也最为复杂,建议仅当的确必要且知道确切的实现方法才使用这种方案。
构建自定的会话状态提供程序
  会话状态提供程序是一种组件,负责为当前会话数据进行服务。当请求需要状态信息时,该组件便会被调用,从给定的存储介质中获取数据,并返回给主调模块。在请求结束时,也会被调用,以便将提供的数据写入存储层。
  asp教程.net支持三种状态提供程序,下表对其作了说明:
  我们还可以编写自己的状态提供程序类,使其应用自选的存储介质。
定义会话状态存储
  状态提供程序是一种继承于sessionstatestoreproviderbase的类,下表列出了其接口的主要方法:
  继承sessionstatestoreproviderbase类,并保留默认的asp.net教程会话状态模块,这种方案只能更改会话状态数据存储和数据恢复这部分功能,其他功能不能更改。
锁定与过期
  状态提供程序必须实现会话连续访问的锁定机制。会话状态模块能够判断请求需要的是会话状态的只读访问还是可读/写访问,根据判断结果,它会调用getitem或getitemexclusive。在这两个方法的实现中,提供程序的编写者应创建读取会话/写入会话的锁定机制,允许多个并发的读取操作,但要阻止向已锁定的会话写入数据。
  另一个问题是,要使会话状态模块知道给定会话的过期时间。若global.asax定义了session_end事件的处理程序,会话状态模块会调用setitemexpirecallback。通过该方法,状态提供程序会得到一个回调函数,其原型如下:
public delegate void sessionstateitemexpirecallback(string sessionid, sessionstatestoredata item);
  会话模块要将返回的委托在内部存储,在给定会话超时时调用。对过期回调的支持是可选的,事实上,只有inproc真正支持它。如果不希望自定义提供程序支持过期回调,应指示setitemexpirecallback方法返回false。
  如果要支持无cookie会话的提供程序,还必须实现createuninitialized方法,以便向数据存储中写入空的会话项。确切地讲,空的会话项是一种完整的数据项,但不包含实际的数据。也就是说,会话项应包含会话id和创建时间(可能还包含锁的id),但不包含数据。对于asp.net 2.0,在无cookie模式下,只要在会话过期后发出请求,就会生成新的id。会话状态模式会生成新的id,并使浏览器重定向。若未初始化的会话项没有被分配新的id,那么新的请求仍会被看作是过期会话中的请求。
会话数据字典的替换
  sessionstatestoredata是代表会话项的类(一种包含与会话有关的所有数据的数据结构)。事实上,getitem和getitemexclusive返回的就是该类的实例。该类有3个属性:items、staticobjects和tiemout。
  items最终用于为页面对象session属性中的键/值集合提供数据。staticobjects中包含的是隶属于当前会话的静态对象(如在global.asax中声明,且在会话范围中可见的对象)。timeout是会话状态的有效时间(以分为单位),默认值得为20分钟。
  一旦会话状态模块为请求获得会话状态,该模块会将items集合的内容写入https教程essionstatecontainer类的新实例。该对象之后被传给httpsessionstate类的构造函数,成为session属性幕后的数据容器。
  对于会话模块和状态提供程序来说,会话项的容器只不过是实现isessionstateitemcollection接口的类。默认情况下,实例使用的类为sessionstateitemcollection。只要自定义的类实现了上述接口,就可以替代sessionstateitemcollection类。
  若要编写状态提供程序,sessionstateutility类会非常有用,该类包含序列化和反序列化会话项的方法,以便将其存储到某介质中,或将其读出。此外,该类还有有些方法,可以从会话中抽取数据字典并将其添加到http上下文和session属性。
自定义会话状态提供程序的注册
  为使应用程序能够使用自定义的会话状态提供程序,我们需要在web.config文件中注册它。假设有一个叫samplesessionstaeprovider的提供程序类,被编译为mylib程序集:

customprovider="samplesessionprovider">

type="samplesessionstateprovider, mylib" />


自定义会话id的生成
  为生成会话id,asp.net 2.0使用了名为sessionidmanager的组件。该类既不是http模块也不是提供程序,它只是一个继承于system.object且实现isessionidmanager接口的类。只要实现isessionidmanager接口,自定义的类就可以替换该组件。
默认的行为
  默认的会话id模块以字节数组的形式生成会话id,并带有15个值的强随机序列进行加密。该数据之后会被编码为包含24个字符的字符串,且每个字符都符合url的标准,系统会将其用途会话id。
  会话id可通过http cookie或修改的url在服务器与客户端间传输,具体方式取决于中cookieless属性的值。注意,若使用无cookie会话,会话id模块要负责将id添加到url中,并对浏览器进行重定向。默认的生成器会将浏览器重定向到下面的虚url:
  http://www.contoso.com/test/(s(session_id))/page.aspx
  这种url发出的请求如何被正确处理呢?在无cookie会话情况下,会话id模块会利用一个轻型的isapi筛选器(aspnet_filter.dll)将传入的url动态重写为实现资源的url。请求会被正确处理,但地址栏中的路径不会改变。检测到的会话id会被置于一个叫aspfiltersessionid的标头中。
自制的会话id管理器
  会话id管理器是一个实现isessionidmanager接口的类,因而我们有两种方案来构建它:
  1. 新建一个类,并使其实现该接口。
  2. 从sessionidmanager派生出一个类,并重写其中的两个虚方法(createsessionid和validate),以实现某些特殊的逻辑。
  isessionidmanager接口的方法见下表:
  如果打算使用完全自定义的会话id生成器,应注意以下几点:
  1. 生成id的算法非常关键。如果不实现强随机加密过程,当某些会话处于活动状态时,恶意用户可能会猜到有效的id。为此,生成全局唯标识符(guid)是一个不错的选择。
  2. 我们可以选择是否支持无cookie的会话。如果添加这种支持,则必须使该组件能从http请求中抽取会话id,并对浏览器进行重定向。为此,可能需要isapi筛选器或http模块,对请求进行预处理并对其做适当的修改。
  创建好会话id模块后,便可将其在配置文件中注册:


 

使用

会话状态模块负责管理所有这些任务的执行。为此,它还需要利用两个组件:会话id生成器和会话状态提供程序。在asp.net 2.0和更高版本中,二者可以由自定义的组件代替。

会话的标识
  每个活动的asp.net会话由15个字节(120位)的字符串标识,其中只包含符合url标准的字符。会话id是随机生成的,且是唯一的,以避免恶意攻击和数据冲突。通过某种算法从一个现有的id推算出一个有效的会话id几乎是不可能的。
会话id的生成
  会话id的长度为15个字节,由“随机数字生成器(rng)”密码提供程序生成。该服务提供程序能返回15个随机生成的数字序列,这个数字数组之后被映射为有效的url字符,并以字符串的形式返回。
  如果会话中未包含任何数据,那么每个请求都会获得新生成的会话id,但会话状态不会由状态提供程序保存。然而,如果设置了session_start事件的处理程序,会话状态总会被保存(即使它为空)。因此,定义session_start事件的处理程序应谨慎,不到万不得已不要这么做,特别是在不使用进程内会话提供程序时。
  相反,如果是非空会话字典,那么在其超时或被放弃后,会话id仍会被保留。根据设计,即使会话状态已过时,会话id仍会持续到浏览器会话结束。这意味着,只要浏览器实例不变,同一个会话id就会用于表示多个同时存在的会话。
会话cookie
  在浏览器与服务器之间,sessionid字符串以两种方式传递:使用cookie,或使用修改的url。默认情况下,会话状态模块会在客户端创建http cookie,但修改的url可以嵌入sessionid字符串(这种方式特别适合无cookie浏览器的情况)采用哪种方法取决于存储在web.config文件中的设置,默认会采用cookie方案。
  实际上,cookie无非是一种位于客户端硬盘上与web页面关联的文本文件。在asp.net中,cookie由httpcookie类实例表示。通常,cookie数据保护名称、值的集合和过期时间。此外,我们还可配置cookie的虚拟路径,还可选择是否通过安全连接(如https)传输。
  asp.net 2.0及更高版本利用了浏览器http-only的会话cookie支持。安装ie 6.0 sp1和winxp sp2的计算机都支持。http-only功能能够防止客户端脚本使用cookie,从而降低了为窃取会话id来进行跨站点脚本攻击的潜在危险。
  若启用cookie,会话状态模块会创建一个带有特定名称的cookie,并将会话id存入其中。该cookie的创建过程如下:
httpcookie sessioncookie;
sessioncookie = new httpcookie("asp.net_sessionid", sessionid);
sessioncookie.path = "/";
  asp.net_sessionid是这个cookie的名称,sessionid字符是值。cookie还与当前域的根关联。path属性用于描述cookie的相对url。会话cookie会被分配一段非常短暂的过期时间,并在每个请求成功地结束后更新。cookie对象的expires属性用于指示客户端cookie过期的天数。如果没有显式的设置,与会话cookie一样,expires属性默认为datetime.minvalue,即.net中定义的最短时间。
  要写入cookie的服务器端模块,需要向response.cookies集合添加httpcookie对象。在客户端发现的所有与被请求域关联的cookie都会被上传到服务器,随后可通过request.cookies集合读取。
无cookie会话
  为使会话状态正常工作,客户端必须能够将会话id上传至服务器端应用程序。这个过程具体细节取决于应用程序的配置。asp.net应用程序通过配置文件的定义会话特有的设置。会确定cookie的支持方式,我们需要将cookieless属性设置为下表中的某个值,这些值为httpcookiemode枚举类型:
  在禁用cookie支持的情况下,假设用户通过这样的url来请求某个页面:
  http://www.contoso.com/test/sessions.aspx
  浏览器地址栏中显示的地址会发生一些变化的(其中包含会话id),如下所示:
  http://www.contoso.com/test/(s(sylgasdfueoruikfjoiueriljk))/sessions.aspx
  当会话状态模块实例化时,它会检查cookiesless属性值,如果发现cookie被禁用,请求会被重定向到一个被修改的虚拟url(http状态码为302),其中包含一个会话id,刚好在页面名的前面。当再次开始处理请求时,这个会话id会嵌入请求中。这个请求会由一个特殊的isapi筛选器(aspnet_filter.exe组件)做预处理--解析其url,若带有会话id,则将其重写为正确的url。被检测到的id会存储在单独的http标头(aspfiltersessionid)中,以便稍后使用。
无cookie会话带来的问题
  当会话开始时,不论用户发出的是否为应用程序页面的绝对url,无cookie都会引发重定向。
  若使用cookie,且在地址栏填入另一个应用程序的地址,那么在返回之前的页面时,获取的是相同的会话值。如果禁用cookie,当页面回发时会自动通过相对url来实现,不受影响。但如果使用绝对url链接,则会造成会话数据则会丢失,这种情况下,总会创建新的会话。例如下面的代码就会打断当前会话:
  有什么办法能够自动修改链接和超链接中的绝对url,使其融入会话id信息?
  我们可以使用httpresponse类的applyapppathmodifier方法:
  applyapppathmodifier方法接受一个代表相对url的字符串,返回的是带有会话信息的绝对url。这个技巧非常适合将http页面重定向到https页面,这时必须使用完整的绝对地址。注意,如果会话cookie是启用的,或传入的路径是绝对路,那么applyapppathmodifier返回的则是原始的url。
无cookie会话与安全
  使用无cookie会话引发的另一个问题与安全有关。会话劫持(session hijacking)是最常见的攻击类型之一,它通过生成另一个合法用户的会话id来访问外部系统。为理解这种攻击方式,可以这样做:将应用程序配置为不使用cookie,并访问某个页面,获取带有会话id的url(可取自浏览器的地址栏),并立即通过电子邮件发送给一个您的朋友。让对方将该url粘贴到自己浏览器的地址栏中,并单击“转到”。只要您的会话还是活动的,那么您的朋友也能访问相同的会话。
  出于对系统安全的考虑,生成随机id很关键,因为这会使攻击者很难猜测到有效的会话id。对于无cookie会话,会话id暴露在地址栏中,对外界可见。因此,如果要将私人或敏感信息存储在会话中,建议使用安全嵌套字层(secure sockets layer,ssl)或传输层安全性(transport layer security,tls)对浏览器和服务器间包含会话id的通信进行加密。
  此外,若用户认为这样会降低安全性,我们还应为其提供注销功能,并调用abandon方法。这样会缩短在某个攻击者设法找到合法用户的会话id的时间。而且从安全性角度来讲,在使用无cookie会话时,有必要对应用程序进行配置,使其避免重复使用过期的会话id。在asp.net中,该行为可通过区段进行配置。
会话状态的配置
  在asp.net 1.x向asp.net 2.0过渡的过程中,区段的选项也随之增加,如下所示:
mode="off|inproc|stateserver|sqlserver|custom"
timeout="number of minutes"
cookiename="session cookie name"
cookieless="http cookie mode"
regenerateexpiredsessionid="true|false"
sqlconnectionstring="sql connection string"
sqlcommandtimeout="number of seconds"
allowcustomsqldatabase="true|false"
usehostingidentity="true|false"
partitionresolvertype=""
sessionidmanagertype="custom session id generator"
stateconnectionstring="tcpip=server:port"
statenewworktimeout="number of seconds"
customprovider="custom provider name">

...

  sessionstate的属性说明见下表:
  此外,子区段用户设置所有自定义会话状态存储提供程序。
会话的生存期
  会话状态的生存期起始于首个数据项被添加到内存中的字典时。该字典是一个sessiondictionary的内部类实例。
session_start事件
  会话启动事件与会话状态无关。session_start事件将在会话状态模块为用户的首个请求提供服务且需要新会话id时引发。asp.net运行库能在单个会话上下文中为多个请求服务,但只有第一个请求会引发session_start事件。
  若不向字典中写入数据,便会在请求页面时创建新的会话id并引发session_start事件。
session_end事件
  session_end事件用于通知会话的结束,并执行终止会话涉及的清理代码。但应注意,该属性要求当前处于inproc模式下(会话数据存储在asp.net工作线程)。
  为使session_end引发,会话状态必须事先已经存在。这意味着,我们必须在会话状态中存储一些数据,且至少完成一个请求。当第一个值被添加到会话字典中时,会有一个对应项被插入asp.net缓存。该行为针对的是进程内状态提供程序,进程外状态服务器和sql server状态服务都不涉及cache对象。
  添加到缓存的会话状态项会被指定一个可调的过期时间(会话超时设置中的间隔时间),只要有请求在当前会话上下文中处理,可调的时间会自动更新。会话状态模块会在处理endrequest事件时重置这个超时设置。该模块只要对缓存执行一次读取操作,就能达到期望的效果。asp.net cache对象的内部结构,使其能够估算出该可调时间段的长短。因此,当缓存项过期时,会话状态也已超时。
  过期项会自动从缓存中移除。作为过期策略的一部分,会话状态模块还会指定一个移除回调函数。缓存对象会自动调用该移除函数,而该函数会引发session_end事件。
  cache中代表会话状态的项无法在system.web程序集之外访问,更不能进行枚举,因为它们被置于缓存的系统保留区域内。也就是说,我们不能以编程的方式访问位于另一个会话中的数据,移除就更谈不上了。
为什么会话状态有时会丢失
  session对象中的值可以编程方式移除,也会在会话超时或被放弃时由系统移除。但在某些情况下,会话状态会莫名其妙的丢失,这是为什么呢?
  当处在inproc模式下时,会话状态会被映射到处理当前请求的appdomain内存空间中。因而,会话状态会受进程回收和appdomain重启的影响。asp.net工作线程会周期性的重启以便保持总体上的良好性能,重启后,会话状态便会丢失。进程回收的执行会根据内存的占用率和被服务的请求数。虽然该过程是周期性的,但无法估计出回收的间隔。因此,在设计基于会话的、进程内的应用程序时,应充分考虑这个问题。会话状态可能在试图访问时不存在。使用异常处理,还是使用恢复技术,要根据具体的应用程序来分析。
  当某页面运行出现错误时,会话状态会受到怎样的影响?当前的会话字典是会被保存还是丢失?若页面在请求结束时发生错误(server对象的getlasterror方法返回一个异常对象),会话状态则不会被保存。然而,如果在异常处理程序中通过调用server.clearerror方法来重置异常状态,那么会话状态数据便会按常规方式保存,就像没有发生错误一样。
将会话状态保存在远程服务器中
  对于前面提到的inproc模式下会话状态会丢失的问题,可以利用进程外状态提供程序来解决。但如果这样的话,会话状态不存储在asp.net工作线程中,需要一层额外的代码来对存储介质中的数据进行序列化和反序列化,该操作发生在请求被处理期间。
  将会话数据从外部存储区复制到本地会话字典中,可能造成状态管理进程的性能下降15%--25%。
  若选择进程外状态提供程序,应考虑在应用程序进入生产环境前事先建立运行时环境。这涉及启用有关stateserver的windows服务或配置sqlserver数据库教程
状态的序列化和反序列化
  若选择inproc模式,存储在会话状态中的对象是类的实例,无需执行序列化或反序列化。我们可以在会话状态中存储开发者所创建的任何对象,访问它们也不会有额外的开销。但如果选择进行外状态提供程序,情况则迥然不同。
  在进程外架构中,会话值需要从原始的存储介质复制到处理请求的appdomain的内存中。这方面的工作需要序列化/反序列化来完成,这是进程外状态提供程序主要的开销之一。这么我们的代码有何影响?首先,我们应确保字典中只存储可序列化的对象。
  在序列化和反序列化方面,asp.net中有两种方式,性能各有不同。对于基本的类型,asp.net会使用经优化的内部序列化程序;而对于其他类型,asp.net会使用.net二进制格式化程序,其速度较慢。
  经优化的序列化程序(一个叫altserialization的内部类)会使用binarywriter对象,先写入代表相应“类型”的字节,然后才是它的值。在读取时,altserialization类首先会抽取一个字节,判断要读取的数据的类型,然后借助binaryreader类中类型特定的方法进行读取具体的值。每个类型会根据一个内部的表与一个索引值关联。
  布尔类型和数据类型的大小是固定的,而字符串的长度却是可变的。那么,读取器是如何确定字符串的长度呢?binaryreader.readstring方法利用了这样一个技巧:在底层流中,字符串总带有一个长度前缀。对于datatime类型,该方法会通过日期和时间的总刻度数的形式写入,作为int64类型读取。
  对于复杂对象,只要它被标记为“可序列化”,则会通过binaryformatter类对其进行序列化,其速度相对较慢。简单类型和复杂类型都会使用同一个流,但所有非基本类型由同一个类型id来标识。
  我们不应该将任何对象都存储在session中。如果使用进程外方案,对dataset的存储应该谨慎。这与dataset类的序列化过程有关。由于dataset是复杂类型,它是通过二进制格式化程序进行序列化的。而dataset本身的序列化会生成许多xml数据,而这会对应用程序造成严重的缺陷,尤其对于存储大量数据的大型应用程序。
会话数据的存储
  若工作在stateserver模式下,整个httpsessionstate对象的内容会被序列化到一个外部应用程序,即一个名为aspnet_state.exe的microsoft windows nt服务。该服务用于在请求结束时对会话状态进行序列化。在内部,该服务使用字节数组来存储每个会话状态。若开始处理新的请求,与给定会话id对应的数组会被复制到内存流中,然后被反序列化成内部的会话状态项对象。该对象代表整个会话的内容。页面实际使用的httpsessionstae对象只是其应用程序编程接口。
stateserver提供程序的配置
  若采用进程外存储方案,会话状态的生存期便会被延迟。这样一来,应用程序的健壮性更强。通过将会话状态与页面分离,我们还能够将现有的应用程序逐步向web farm和web garden架构转化。
  asp.net会话状态提供程序是一个叫aspnet_state.exe的windows服务,该服务的可执行文件位于asp.net的安装文件夹:%windows%microsoft.netframework[version]。
  注意,具体的路径取决于实际运行的.net framework的版本。在使用状态服务前,应确保该服务在本地或用于存储会话的远程计算机上正常运行。
  我们需要为asp.net应用程序指定运行会话状态服务的计算机的ip地址。为启用远程会话状态,我们需要在web.config中进行配置:


stateconnectionstring="tcpip=mymachine:42424" />

  服务器名既可以是ip地址,也可以是计算机名称。
  状态服务器不会为请求者设置任何身份验证障碍,这样,网络用户可以自由访问会话数据。为保护会话状态,应确保其只能接受来自web服务器计算机的访问。为此,我们可以使用防火墙、ipsec策略或安全网络10.x.x.x,这样,外部攻击都便不能直接访问了。我们还可以修改其服务端口,修改注册表中的键值“hkey_local_machinesystemcurrentcontrolsetservicesaspnet_stateparameters”即可修改状态服务的端口。
  asp.net应用程序会在加载后立即尝试连接到会话状态服务器。状态服务会使用.net remoting进行数据通信。
  默认情况下,状态服务器只监听本地连接,如果状态服务器和web服务器是不同的计算机,我们需要启用远程连接。为此,我们只要在注册表中修改键值“hkey_local_machinesystemcurrentcontrolsetservicesaspnet_stateallowremoteconnection”,将其设置一个非0值即可。
将数据保存到sql server
  如果应用程序对健壮性要求较高,比如状态提供服务被停用后,数据仍然不会丢失,不妨将sql server服务。
  若asp.net工作在sql server模式下,会话数据存储在一个专用的数据库表中,因此,即使sql server崩溃,会话数据也不会丢失,但这需要更高的系统开销。
  为将sql server作为状态提供程序,需要对web.config文件进行配置:


sqlconnectionstring="server=127.0.0.1;integrated security=sspi;" />

  如果不通过allowcustomsqldatabase启用自定义数据库,那么该连接字符串中不能包含database和initial catalog这样的设置。仅当allowcustomsqldatabase设置被启用时,我们才可通过database和initial catalog指定数据库名称。
  连接字符串也可以引用定义在区段中的连接字符串,连接字符串名称可以在相应属性中指定。
  对数据库的访问凭据,我们可以通过用户id和密码来提供,也可以利用“集成安全性”。不论使用哪个帐户来访问sql server中的会话状态,都应确保用户至少拥有db_datareader和db_datawriter权限。还应注意,为配置存储会话状态的sql server环境,需要管理员权限,因为要创建新的数据库和存储过程。
  对于sql server模式下的会话状态,我们能指定自定义命令的超时值(以秒为单位)sqlcommandtimeout属性来进行设置。
sql server数据库的创建
  asp.net提供了两对用于配置数据库环境的脚本,以便创建必要的表、存储过程、触发器、作业等。
  第一对脚本为installsqlstate.sql和uninstallsqlstate.sql。它们能够创建一个aspstate数据和几个存储过程,但数据存储在tempdb数据库的几个表中。这样,如果sql server所处的计算机被重启,会话数据将丢失。
  另一对脚本为installperisistsqlstate.sql和uninstallperisistsqlstate.sql。与第一对脚本的不同之处是,它们创建的表在aspstate数据库中,是持久性的。共有两个表,表名分别为aspstatetempapplications和aspstatetempsessions。
  所有脚本可以在以下路径中找到:
  %systemroot%microsoft.netframework[version]
  包含这些脚本文件只是为了向后兼容,我们应使用aspnet_regsql.exe来安装和卸载sql会话状态。
  当前运行的每个asp.net应用程序对应于aspstatetempapplications表中的一条记录。下表对表中各列做了说明:
  aspstatetempsessions表用于存储实际的会话数据,每个活动的会话对应于表中的一条记录,下表描述了该表的结构:
  在安装会话的sql server支持时,还有一个作业被添加,它用于从会话状态数据库中删除过期的会话。该作业名称为aspstate_job_deleteexpiredsessions,默认配置是每分钟运行一次。

相关文章

精彩推荐