标签:
1 Session概述
HTTP协议是无状态的,但通过session机制,就能把无状态的变成有状态的。Session的功能就是保存HTTP请求之间的状态数据。有了session的支持,就很容易实现诸如用户登录、购物车等网站功能。在Servlet API中,有一个HttpSession
的接口。
在Java代码中访问session
//在一个请求中,保存session的状态 // 取得session对象 HttpSession session = request.getSession(); // 在session中保存用户状态 session.setAttribute("loginId", "myName"); //在另一个请求中,取出session的状态: // 得到"myName" String myName = (String) session.getAttribute("loginId");
Session数据存储
1)保存在应用服务器的内存中
一般的做法,是将session对象保存在内存里。同一时间,会有很多session被保存在服务器的内存里。由于内存是有限的,较好的服务器会把session对象的数据交换到文件中,以确保内存中的session数目保持在一个合理的范围内。
为了提高系统扩展性和可用性,我们会使用集群技术 —— 就是一组独立的机器共同运行同一个应用。对用户来讲,集群相当于一台“大型服务器”。而实际上,同一用户的两次请求可能被分配到两台不同的服务器上来处理。这样一来,怎样保证两次请求中存取的session值一致呢?
一种方法是使用session复制:当session的值被改变时,将它复制到其它机器上。以广播的方式随时保持着服务器同步,网络负担重,不具备高度可扩展性。
另一种方法是TCP-Ring的方式,也就是把集群中所有的服务器看成一个环,A→B→C→D→A,首尾相接。缺点:一是配置复杂;二是每增添/减少一台机器时,ring都需要重新调整,这将成为性能瓶颈;三是要求前端的Load Balancer具有相当强的智能,才能将用户请求分发到正确的机器上。
2)保存在单一数据源中
将session保存在单一的数据源中,这个数据源可被集群中所有的机器所共享。这样一来,就不存在复制的问题了。问题一:然而单一数据源的性能成了问题。每个用户请求,都需要访问后端的数据源(很可能是数据库)来存取用户的数据。二: 很少有应用服务器直接支持这种方案。更不用说数据源有很多种(MySQL、Oracle、Hsqldb等各种数据库、专用的session server等)了。三:数据源成了系统的瓶颈,一但这个数据源崩溃,所有的应用都不可能正常运行了。
3)保存在客户端
把session保存在客户端。这样一来,由于不需要在服务器上保存数据,每台服务器就变得独立,能够做到线性可扩展和极高的可用性。
具体怎么做呢?目前可用的方法,恐怕就是保存在cookie中了。但需要提醒的是,cookie具有有以下限制,因此不可无节制使用该方案:
Cookie数量和长度的限制。每个domain最多只能有20条cookie,每个cookie长度不能超过4KB,否则会被截掉。
安全性问题。如果cookie被人拦截了,那人就可以取得所有的session信息。即使加密也与事无补,因为拦截者并不需要知道cookie的意义,他只要原样转发cookie就可以达到目的了。
有些状态不可能保存在客户端。例如,为了防止重复提交表单,我们需要在服务器端保存一个计数器。如果我们把这个计数器保存在客户端,那么它起不到任何作用。
虽然有上述缺点,但是对于其优点(极高的扩展性和可用性)来说,就显得微不足道。可以用下面的方法来回避上述的缺点:
通过良好的编程,控制保存在cookie中的session对象的大小。
通过加密和安全传输技术(SSL),减少cookie被破解的可能性。
只在cookie中存放不敏感数据,即使被盗也不会有重大损失。
控制cookie的生命期,使之不会永远有效。偷盗者很可能拿到一个过期的cookie。
4)将客户端、服务器端组合的方案
任何一种session方案都有其优缺点。最好的方法是把它们结合起来。这样就可以弥补各自的缺点。
将大部分session数据保存在cookie中,将小部分关键和涉及安全的数据保存在服务器上。由于我们只把少量关键的信息保存在服务端,因而服务器的压力不会非常大。在服务器上,单一的数据源比复制session的方案,更简单可靠。我们可以使用数据库来保存这部分session,也可以使用更廉价、更简单的存储,例如Berkeley DB就是一种不错的服务器存储方案。将session数据保存在cookie和Berkeley DB(或其它类似存储技术)中,就可以解决我们的绝大部分问题。
通用的session框架
多数应用服务器并没有留出足够的余地,来让你自定义session的存储方案。纵使某个应用服务器提供了对外扩展的接口,可以自定义session的方案,但是我们希望保留选择应用服务器软件的自由。因此,最好的方案,不是在应用服务器上增加什么新功能,而是在WEB应用框架中实现灵活的session框架,那么我们的应用可以跑在任何标准的JavaEE应用服务器上。
除此之外,一个好的session框架还应该做到对应用程序透明。具体表现在:
使用标准的HttpSession
接口,而不是增加新的API。这样任何WEB应用,都可以轻易在两种不同的session机制之间切换。
应用程序不需要知道session中的对象是被保存到了cookie中还是别的什么地方。
Session框架可以把同一个session中的不同的对象分别保存到不同的地方去,应用程序同样不需要关心这些。例如,把一般信息放到cookie中,关键信息放到Berkeley DB中。甚至同是cookie,也有持久和临时之分,有生命期长短之分。
Webx实现了这种session框架,把它建立在Request Contexts的基础上。
2. Session框架
1)最简配置
例 Session框架基本配置(/WEB-INF/webx.xml
)
<services:request-contexts xmlns="http://www.alibaba.com/schema/services/request-contexts"> <buffered /> <lazy-commit /> ... <session> <stores> <session-stores:simple-memory-store id="simple" /> </stores> <store-mappings> <match name="*" store="simple" /> </store-mappings> </session> </services:request-contexts>
以上的配置,创建了一个最基本的session实现:将所有数据(name=*
)保存在内存里(simple-memory-store)。
2)Session ID
Session ID唯一标识了一个session对象。把session ID保存在cookie里是最方便的。这样,凡是cookie值相同的所有的请求,就被看作是在同一个session中的请求。在servlet中,还可以把session ID编码到URL中。Session框架既支持把session ID保存在cookie中,也支持把session ID编码到URL中。
完整的session ID配置如下:
<session> <id cookieEnabled="true" urlEncodeEnabled="false"> <cookie name="JSESSIONID" domain="" maxAge="0" path="/" httpOnly="true" secure="false" /> <url-encode name="JSESSIONID" /> <session-idgens:uuid-generator /> </id> </session>
上面这段配置包含了关于Session ID的所有配置以及默认值。如果不指定上述参数,则系统将使用默认值,其效果等同于上述配置。
配置<session><id> —— 将Session ID保存于何处? | |
---|---|
cookieEnabled |
是否把session ID保存在cookie中,如若不是,则只能保存的URL中。 默认为开启: |
urlEncodeEnabled |
是否支持把session ID编码在URL中。如果为 默认为关闭: |
配置<session><id><cookie> —— 将Session ID存放于cookie的设置 | |
---|---|
name |
Session ID cookie的名称。 默认为 |
domain |
Session ID cookie的domain。 默认为空,表示根据当前请求自动设置domain。这意味着浏览器认为你的cookie属于当前域名。如果你的应用包含多个子域名,例如: |
maxAge |
Session ID cookie的最长存活时间(秒)。 默认为 |
path |
Session ID cookie的path。 默认为 |
httpOnly |
在session ID cookie上设置 在IE6及更新版本中,可以缓解XSS攻击的危险。默认为 |
secure |
在session ID cookie上设置 这样,只有在https请求中才可访问该cookie。默认为 |
配置<session><id><url-encode> —— 将Session ID编码到URL的设置 | |
---|---|
name |
指定在URL中表示session ID的名字,默认也是 此时,如果 response.encodeURL("http://localhost:8080/test.jsp?id=1") 将得到类似这样的结果:
http://localhost:8080/test.jsp;JSESSIONID=xxxyyyzzz?id=1
|
配置<session><id><session-idgens:*> —— 如何生成session ID? | |
---|---|
uuid-generator |
以UUID作为新session ID的生成算法。 这是默认的session ID生成算法。 |
为了达到最大的兼容性,我们分两种情况来处理JSESSIONID
:
当一个新session到达时,假如cookie或URL中已然包含了JSESSIONID
,那么我们将直接利用这个值。为什么这样做呢?因为这个JSESSIONID
可能是由同一域名下的另一个不相关应用生成的。如果我们不由分说地将这个cookie覆盖掉,那么另一个应用的session就会丢失。
多数情况下,对于一个新session,应该是不包含JSESSIONID
的。这时,我们需要利用SessionIDGenerator
来生成一个唯一的字符串,作为JSESSIONID
的值。SessionIDGenerator
的默认实现UUIDGenerator
。
3)Session的生命期
session从创建到失效的整个过程。其状态变迁如下图所示:
总结一下:
第一次打开浏览器时,JSESSIONID
还不存在,或者存在由同一域名下的其它应用所设置的无效的JSESSIONID
。这种情况下,session.isNew()
返回true
。
随后,只要在规定的时间间隔内,以及cookie过期之前,每一次访问系统,都会使session得到更新。此时session.isNew()
总是返回false
。Session中的数据得到保持。
如果用户有一段时间不访问系统了,超过指定的时间,那么系统会清除所有的session内容,并将session看作是新的session。
用户可以调用session.invalidate()
方法,直接清除所有的session内容。此后所有试图session.getAttribute()
或session.setAttribute()
等操作,都会失败,得到IllegalStateException
异常,直到下一个请求到来。
在session框架中,有一个重要的特殊对象,用来保存session生命期的状态。这个对象叫作session model。它被当作一个普通的对象存放在session中,但是通过HttpSession
接口不能直接看到它。
关于session生命期的完整配置如下:
<session maxInactiveInterval="0" keepInTouch="false" forceExpirationPeriod="14400" modelKey="SESSION_MODEL"> ... </session>
参数名 | 说明 |
---|---|
maxInactiveInterval |
指定session不活动而失效的期限,单位是秒。 默认为 |
keepInTouch |
是否每次都touch session(即更新最近访问时间)。 如果是 默认为 |
forceExpirationPeriod |
指定session强制作废期限,单位是秒。 无论用户活动与否,从session创建之时算起,超过这个期限,session将被强制作废。这是一个安全选项:万一cookie被盗,过了这个期限的话,那么无论如何,被盗的cookie就没有用了。 默认为 |
modelKey |
指定用于保存session状态的对象的名称。 默认为" |
4)Session Store
Session Store是session框架中最核心的部分。Session框架最强大的部分就在于此。我们可以定义很多个session stores,让不同的session对象分别存放到不同的Session Store中。前面提到有一个特殊的对象“SESSION_MODEL
”也必须保存在一个session store中。
类似于Servlet的配置,Session store的配置也包含两部分内容:session store的定义,和session store的映射(mapping)。
<session> <stores> <session-stores:store id="store1" /> <session-stores:store id="store2" /> <session-stores:store id="store3" /> </stores> <store-mappings> <match name="*" store="store1" /> <match name="loginName" store="store2" /> <matchRegex pattern="key.*" store="store3" /> </store-mappings> </session>
定义session stores:你可以配置任意多个session store,只要ID不重复。此处,store1
、store2
和store3
分别是三个session store的名称。
映射session stores:match
标签用来精确匹配attribute name。一个特别的值是“*
”,它代表默认匹配所有的names。本例中, 如果调用session.setAttribute("loginName", user.getId())
,那么这个值将被保存到store2
里;如果调用session.setAttribute("other", value)
将被默认匹配到store1
中。
映射session stores:matchRegexp
标签用正则表达式来匹配attribute names。本例中, key_a
、key_b
等值都将被保存到store3
里。
注意以下几点:
在整个session配置中,只能有一个store拥有默认的匹配。
假如有多个match
或matchRegex
同时匹配某个attribute name,那么遵循以下匹配顺序:
精确的匹配最优先。
正则表达式的匹配遵循最大匹配的原则,假如有两个以上的正则表达式被同时匹配,长度较长的匹配胜出。
默认匹配*
总是在所有的匹配都失败以后才会被激活。
必须有一个session store能够用来存放session model。
你可以用<match name="*">
来匹配session model;
也可以用精确匹配:<match name="SESSION_MODEL" />
。其中session model的名字是必须和前述modelKey配置的值相同,其默认值为“SESSION_MODEL
”。
5)Session Model
Session Model是用来记录当前session的生命期数据的,例如:session的创建时间、最近更新时间等。默认情况下,
当需要保存session数据时,SessionModel
对象将被转换成一个JSON字符串(如下所示),然后这个字符串将被保存在某个session store中:
{id:"SESSION_ID",ct:创建时间,ac:最近访问时间,mx:最长不活动时间}
需要读取时,先从store中读到上述格式的字符串数据,然后再把它解码成真正的SessionModel
对象。
以上转换过程是通过一个SessionModelEncoder
接口来实现的。为了提供更好的移植性,Session框架可同时支持多个SessionModelEncoder
的实现。配置如下:
<session> <session-model-encoders> <model-encoders:default-session-model-encoder /> <model-encoders:model-encoder class="..." /> <model-encoders:model-encoder class="..." /> </session-model-encoders> </session>
三个SessionModelEncoder
的实现。第一个是默认的实现,第二、第三个是任意实现。
当从store取得SessionModel
对象时,框架将依次尝试所有的encoder,直到解码成功为止。
当将SessionModel
对象保存到store之前,框架将使用第一个encoder来编码对象。
6)Session Interceptor
Session Interceptor拦截器的作用是拦截特定的事件,甚至干预该事件的执行结果。目前有两种拦截器接口:
Session Interceptor拦截器接口
接口 | 功能 |
---|---|
SessionLifecycleListener |
监听以下session生命期事件:
|
SessionAttributeInterceptor |
拦截以下session读写事件:
|
Session框架自身已经提供了两个有用的拦截器:
名称 | 说明 |
---|---|
<lifecycle-logger> |
监听session生命期事件,并记录日志。 |
<attribute-whitelist> |
控制session中的attributes,只允许白名单中所定义的attribute名称和类型被写入到或读出于session store中。 这个功能对于cookie store是很有用的。因为cookie有长度的限制,所以需要用白名单来限制写入到cookie中的数据数量和类型。 |
可以同时配置多种拦截器,如下所示。
<session> <request-contexts:interceptors xmlns="http://www.alibaba.com/schema/services/request-contexts/session/interceptors"> <lifecycle-logger /> <attribute-whitelist> <attribute name="_csrf_token" /> <attribute name="_lang" /> <attribute name="loginUser" type="com.alibaba...MyUser" /> <attribute name="shoppingCart" type="com.alibaba....ShoppingCart" /> </attribute-whitelist> <interceptor class="..." /> </request-contexts:interceptors> </session>
Webx学习笔记(八)Request Context之Session
标签:
原文地址:http://www.cnblogs.com/Vae98Scilence/p/4631317.html