Tomcat的Session管理机制

Session和Cookie请求的过程

Http连接本身是无状态的,即前一次发起的连接跟后一次没有任何关系,是属于两次独立的连接请求,
但是互联网访问基本上都是需要有状态的,即服务器需要知道两次连接请求是不是同一个人访问的。
JSESSIONID是一个唯一标识号,用来标识服务器端的Session,也用来标识客户端的Cookie,客户端和服务器端通过这个JSESSIONID来一一对应。
客户端第一次请求到服务器连接,这个连接是没有附带任何东西的,没有Cookie,没有JSESSIONID。

服务器端接收到请求后,会检查这次请求有没有传过来JSESSIONID或者Cookie,如果没有JSESSIONID和Cookie,则服务器端会创建一个Session,

并生成一个与该Session相关联的JSESSIONID返回给客户端,客户端会保存这个JSESSIONID,并生成一个与该JSESSIONID关联的Cookie。
第二次请求的时候,会把该Cookie(包含JSESSIONID)一起发送给服务器端,

这次服务器发现这个请求有了Cookie,便从中取出JSESSIONID,然后根据这个JSESSIONID找到对应的Session,这样便把Http的无状态连接变成了有状态的连接。
但是有时候浏览器(即客户端)会禁用Cookie,我们知道Cookie是通过Http的请求头部的一个cookie字段传过去的,

如果禁用,那么便得不到这个值,JSESSIONID便不能通过Cookie传入服务器端。
这时可以通过url重写和隐藏表单,url重写就是把JSESSIONID附带在url后面传过去。隐藏表单是在表单提交的时候传入一个隐藏字段JSESSIONID。


session的管理机制(转自 tomcat架构分析 (Session管理)

Session管理主要涉及到这几个方面:

创建session
注销session
持久化及启动加载session

Tomcat通过每个context容器内的一个Manager对象来管理session。
这个manager对象可以根据tomcat提供的接口或基类来自己定制,也可以使用Tomcat的标准实现。
Tomcat中的session管理主要在org.apache.catalina.session包中实现。

1.创建session

在具体说明session的创建过程之前,先看一下BS访问模型吧,这样理解直观一点。 

browser发送Http request;
tomcat内核Http11Processor会从HTTP request中解析出“jsessionid”(具体的解析过程为先从request的URL中解析,这是为了有的浏览器把cookie功能禁止后,将URL重写考虑的,如果解析不出来,再从cookie中解析相应的jsessionid),解析完后封装成一个request对象(当然还有其他的http header);
servlet中获取session,其过程是根据刚才解析得到的jsessionid(如果有的话),从session池(session maps)中获取相应的session对象;这个地方有个逻辑,就是如果jsessionid为空的话(或者没有其对应的session对象,或者有session对象,但此对象已经过期超时),可以选择创建一个session,或者不创建;
如果创建新session,则将session放入session池中,同时将与其相对应的jsessionid写入cookie通过Http response header的方式发送给browser,然后重复第一步。

以上是session的获取及创建过程。在servlet中获取session,通常是调用request的getSession方法。这个方法需要传入一个boolean参数,这个参数就是实现刚才说的,当jsessionid为空或从session池中获取不到相应的session对象时,选择创建一个新的session还是不创建。 
看一下核心代码逻辑;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
protected  Session doGetSession( boolean  create) {
 
……
// 先获取所在context的manager对象
Manager manager =  null ;
if  (context !=  null )
manager = context.getManager();
if  (manager ==  null )
return  ( null );  // Sessions are not supported
 
//这个requestedSessionId就是从Http request中解析出来的
if  (requestedSessionId !=  null ) {
try  {
//manager管理的session池中找相应的session对象
session = manager.findSession(requestedSessionId);
catch  (IOException e) {
session =  null ;
}
//判断session是否为空及是否过期超时
if  ((session !=  null ) && !session.isValid())
session =  null ;
if  (session !=  null ) {
//session对象有效,记录此次访问时间
session.access();
return  (session);
}
}
 
// 如果参数是false,则不创建新session对象了,直接退出了
if  (!create)
return  ( null );
if  ((context !=  null ) && (response !=  null ) &&
context.getCookies() &&
response.getResponse().isCommitted()) {
throw  new  IllegalStateException
(sm.getString( "coyoteRequest.sessionCreateCommitted" ));
}
 
// 开始创建新session对象
if  (connector.getEmptySessionPath()
&& isRequestedSessionIdFromCookie()) {
session = manager.createSession(getRequestedSessionId());
else  {
session = manager.createSession( null );
}
 
// 将新session的jsessionid写入cookie,传给browser
if  ((session !=  null ) && (getContext() !=  null )
&& getContext().getCookies()) {
Cookie cookie =  new  Cookie(Globals.SESSION_COOKIE_NAME,
session.getIdInternal());
configureSessionCookie(cookie);
response.addCookieInternal(cookie);
}
//记录session最新访问时间
if  (session !=  null ) {
session.access();
return  (session);
else  {
return  ( null );
}
}

尽管不能贴出所有代码,但是上述的核心逻辑还是很清晰的。从中也可以看出,我们经常在servlet中这两种调用方式的不同;

1
2
3
4
5
新创建session
request.getSession(); 或者request.getSession( true );
 
不创建session
request.getSession( false );

接下来,看一下StandardManager的createSession方法,了解一下session的创建过程;

1
2
3
4
5
6
7
8
9
10
public  Session createSession(String sessionId) {
是个session数量控制逻辑,超过上限则抛异常退出
if  ((maxActiveSessions >=  0 ) &&
(sessions.size() >= maxActiveSessions)) {
rejectedSessions++;
throw  new  IllegalStateException
(sm.getString( "standardManager.createSession.ise" ));
}
return  ( super .createSession(sessionId));
}

  

这个最大支持session数量maxActiveSessions是可以配置的,先不管这个安全控制逻辑,看其主逻辑,即调用其基类的createSession方法;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public  Session createSession(String sessionId) {
 
// 创建一个新的StandardSession对象
Session session = createEmptySession();
 
// Initialize the properties of the new session and return it
session.setNew( true );
session.setValid( true );
session.setCreationTime(System.currentTimeMillis());
session.setMaxInactiveInterval( this .maxInactiveInterval);
if  (sessionId ==  null ) {
//设置jsessionid
sessionId = generateSessionId();
}
session.setId(sessionId);
sessionCounter++;
return  (session);
}

  

关键是jsessionid的产生过程,接着看generateSessionId方法;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
protected  synchronized  String generateSessionId() {
 
byte  random[] =  new  byte [ 16 ];
String jvmRoute = getJvmRoute();
String result =  null ;
 
// Render the result as a String of hexadecimal digits
StringBuffer buffer =  new  StringBuffer();
do  {
int  resultLenBytes =  0 ;
if  (result !=  null ) {
buffer =  new  StringBuffer();
duplicates++;
}
 
while  (resultLenBytes <  this .sessionIdLength) {
getRandomBytes(random);
random = getDigest().digest(random);
for  ( int  j =  0 ;
j < random.length && resultLenBytes <  this .sessionIdLength;
j++) {
byte  b1 = ( byte ) ((random[j] &  0xf0 ) >>  4 );
byte  b2 = ( byte ) (random[j] &  0x0f );
if  (b1 <  10 )
buffer.append(( char ) ( '0'  + b1));
else
buffer.append(( char ) ( 'A'  + (b1 -  10 )));
if  (b2 <  10 )
buffer.append(( char ) ( '0'  + b2));
else
buffer.append(( char ) ( 'A'  + (b2 -  10 )));
resultLenBytes++;
}
}
if  (jvmRoute !=  null ) {
buffer.append( '.' ).append(jvmRoute);
}
result = buffer.toString();
//注意这个do…while结构
while  (sessions.containsKey(result));
return  (result);
}

  

这里主要说明的不是生成jsessionid的算法了,而是这个do…while结构。把这个逻辑抽象出来,可以看出;



如图所示,创建jsessionid的方式是由tomcat内置的加密算法算出一个随机的jsessionid,如果此jsessionid已经存在,则重新计算一个新的,直到确保现在计算的jsessionid唯一。

好了,至此一个session就这么创建了,像上面所说的,返回时是将jsessionid以HTTP response的header:“Set-cookie”发给客户端。

2.注销session

主动注销

Session创建完之后,不会一直存在,或是主动注销,或是超时清除。即是出于安全考虑也是为了节省内存空间等。

例如,常见场景:用户登出系统时,会主动触发注销操作。 
主动注销时,是调用标准的servlet接口: 

1
session.invalidate();

看一下tomcat提供的标准session实现(StandardSession)

1
2
3
4
5
6
7
public  void  invalidate() {
if  (!isValidInternal())
throw  new  IllegalStateException
(sm.getString( "standardSession.invalidate.ise" ));
// 明显的注销方法
expire();
}

  

Expire方法的逻辑稍后再说,先看看超时注销,因为它们调用的是同一个expire方法。

超时注销

Tomcat定义了一个最大空闲超时时间,也就是说当session没有被操作超过这个最大空闲时间时间时,再次操作这个session,这个session就会触发expire。 
这个方法封装在StandardSession中的isValid()方法内,这个方法在获取这个request请求对应的session对象时调用,可以参看上面说的创建session环节。

也就是说,获取session的逻辑是,先从manager控制的session池中获取对应jsessionid的session对象,如果获取到,就再判断是否超时,如果超时,就expire这个session了。 
看一下tomcat提供的标准session实现(StandardSession)

1
2
3
4
5
6
7
8
9
10
11
12
public  boolean  isValid() {
……
//这就是判断距离上次访问是否超时的过程
if  (maxInactiveInterval >=  0 ) {
long  timeNow = System.currentTimeMillis();
int  timeIdle = ( int ) ((timeNow - thisAccessedTime) / 1000L);
if  (timeIdle >= maxInactiveInterval) {
expire( true );
}
}
return  ( this .isValid);
}

Expire方法 
是时候来看看expire方法了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public  void  expire( boolean  notify) {
 
synchronized  ( this ) {
......
//设立标志位
setValid( false );
 
//计算一些统计值,例如此manager下所有session平均存活时间等
long  timeNow = System.currentTimeMillis();
int  timeAlive = ( int ) ((timeNow - creationTime)/ 1000 );
synchronized  (manager) {
if  (timeAlive > manager.getSessionMaxAliveTime()) {
manager.setSessionMaxAliveTime(timeAlive);
}
int  numExpired = manager.getExpiredSessions();
numExpired++;
manager.setExpiredSessions(numExpired);
int  average = manager.getSessionAverageAliveTime();
average = ((average * (numExpired- 1 )) + timeAlive)/numExpired;
manager.setSessionAverageAliveTime(average);
}
 
// 将此session从manager对象的session池中删除
manager.remove( this );
......
}
}

不需要解释,已经很清晰了。

这个超时时间是可以配置的,缺省在tomcat的全局web.xml下配置,也可在各个app下的web.xml自行定义;

1
2
3
< session-config >
< session-timeout >30</ session-timeout >
</ session-config > 

单位是分钟。

3.Session持久化及启动初始化

这个功能主要是,当tomcat执行安全退出时(通过执行shutdown脚本),会将session持久化到本地文件,通常在tomcat的部署目录下有个session.ser文件。

当启动tomcat时,会从这个文件读入session,并添加到manager的session池中去。

这样,当tomcat正常重启时, session没有丢失,对于用户而言,体会不到重启,不影响用户体验。

看一下概念图吧,觉得不是重要实现逻辑,代码就不说了。

由此可以看出,session的管理是容器层做的事情,应用层一般不会参与session的管理,也就是说,如果在应用层获取到相应的session,已经是由tomcat提供的,

因此如果过多的依赖session机制来进行一些操作,例如访问控制,安全登录等就不是十分的安全,因为如果有人能得到正在使用的jsessionid,则就可以侵入系统。

 


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值