1.禁用session的必要性
I.请求和响应过程中session对象相关操作
Tomcat在首次接收客户端请求时会自动在JVM中创建session对象
session的主要作用是标记和保持会话,每个session有唯一的32位16进制大写字符串格式的sessionId,Tomcat使用Map对象存储每一个session对象,其key正是sessionId
在响应到客户端的信息中,tomcat在响应头添加了Set-Cookie属性,其值为JSESSIONID=sessionId
客户端以浏览器为例,所有现代主流浏览器都能解析Set-Cookie属性,如果cookie没有被禁用,浏览器将为服务器域名添加JSESSIONID=sessionId的cookie
在浏览器进行再次请求时,会在请求头中的Cookie属性中携带服务器域名下的所有cookie[]数组对象
这个cookie数组对象的值包括了上次存储的JSESSIONID=sessionId,还可能包括服务器域名下的其它cookie值
特别地,如果服务器域名下对外提供多个web服务,比如localhost:8080/service1、localhost:8080/service2和localhost:8180/service3
则访问每一个服务时都会在localhost下创建一个名为JSESSIONID的cookie,也就是说,本例中会存在3个同名的cookie
Tomcat接收到请求后会查看请求头中是否携带cookie[],若无则认为是新会话的请求
如果携带cookie[],会遍历cookie[],检查是否存在名为JSESSIONID的cookie,若无则认为是新会话的请求
如果存在名为JSESSIONID的cookie,取出其value,检查Map中是否存在相同的sessionId,若无,继续遍历cookie[]直到遍历结束或找到匹配的sessionId
若遍历到最后仍未找到匹配的sessionId,则认为是新会话的请求
对于新的会话请求,Tomcat会创建新的session对象
对于老的会话请求,不再创建session对象
II.无效的session对象以及因此产生的问题
①浏览器禁用cookie
如果浏览器禁用了cookie,则不能在浏览器中读写cookie,从而导致在每次请求时,并不能携带上一次服务器响应的JSESSIONID
这将导致服务器认定浏览器的每一次请求都是首次请求,继而创建新的session对象
禁用cookie提升了客户端的安全性,避免了cookie被恶意web程序读取进行跨站请求伪造等破坏性操作
有很多网页程序需要用户开放cookie,否则拒绝提供服务,但无论如何,最正确的做法是禁用cookie
即使不去禁用,浏览网页的一个最佳习惯仍然是退出浏览器时清理所有cookie
在cookie被禁用的情况下,如果服务器还在不停地为每一个请求创建新的session的话,这些session对象将永远派不上用场
不仅如此,随着请求的增加,Tomcat的Map对象的size将会不断膨胀,在匹配sessionId时将会越来越耗时
更关键的是,这些无效的session对象占据了大量的内存
Tomcat中HttpSession的默认实现为org.apache.catalina.session.StandardSession,StandardSession是一个重量级的大对象
Tomcat在创建一个session对象后,即使不向其中添加任何额外属性,一个最小的session对象也占据超过1.5KB的内存
这个数据可在压力测试中得到检验,当发起10000个请求时,仅这些session对象就占据15MB的内存
Tomcat默认的会话时效期为30min,禁用cookie时,低并发场景下,30min内也可能会使得session对象累积超过100MB,后果不言而喻
②分布式系统共享token
Tomcat默认使用org.apache.catalina.session.StandardManager管理session对象,这是一种单机JVM保存session对象的模式
在分布式系统中,需要在多个JVM中共享会话标识,以实现跨进程的会话保持
曾经,在8.0版本之前,Tomcat针对分布式集群也有对应的session管理器ClusterManager,不过或许实在是没人去用,自8.0版本之后到如今(20190914)该类已经被删除
分布式系统中无论是共享session还是token,都和Tomcat没有任何关系了
这些共享的会话标记生成自认证中心系统,保存在共享库如redis中
因此,Tomcat默认生成的Session彻底不再被需要
综述,session在任何情况下都应该被禁用,在技术条件许可的今天,分布式共享的、不受限于客户端cookie的会话标识才是更正确的选择
2.禁用session的方法
I.jsp页面session属性设置为false(jsp页面级关闭session)
jsp中关闭session
<%@ page session=”false” %>
控制类方法中无法关闭session,除非重写session方法
无论如何,只对页面级和方法级服务关闭session是远远不够的,通常情况下,应该对整个web服务关闭session
II.Session方法重写+过滤器替换(web服务级关闭session)
step1.自定义ServletRequest类,继承HttpServletRequestWrapper,重写getSession()方法覆盖Tomcat默认的Session逻辑
package com.liuwei;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpSession;
/**
* @author liuwei
* @date 2019-09-14 13:41:58
* @desc 重写Tomcat的getSession()方法,禁止生成Session
* 对getSession()返回null即可
*/
public class HttpSessionForbidden extends HttpServletRequestWrapper{
public HttpSessionForbidden(HttpServletRequest request) {
super(request);
}
@Override
public HttpSession getSession() {
return null;
}
@Override
public HttpSession getSession(boolean create) {
return null;
}
}
step2.单独配置一个过滤器,设置过滤器优先级最高,在过滤器中使用自定义的ServletRequest对象替换请求的ServletRequest对象
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
chain.doFilter(new HttpSessionForbidden((HttpServletRequest) request), response);
}
此步骤也可以不单独配置过滤器,可以直接将替换操作放在另一个高优先级的过滤器中,比如放在鉴权过滤器中
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
//替换ServletRequest对象
HttpServletRequest HttpRequest = new HttpSessionForbidden((HttpServletRequest) request);
//鉴权过滤器后续操作...
//过滤器链放行
chain.doFilter(HttpRequest, response);
}
替换ServletRequest对象后,Tomcat在处理每一次的请求时仍会调用getSession()方法,只是这一次不再创建session对象了
Tomcat的Map对象此后一直将是size为0的new HashMap()对象
既然不生成session对象,也不在响应头添加Set-Cookie属性了
在Tomcat中启动服务后,可使用以下几种方式进行session禁用的验证
会话id响应的关键代码为:”会话id:”+(null==HttpRequest.getSession(true)?null:HttpRequest.getSession(true).getId())
方式1:浏览器
方式2:postman
方式3:tomcat服务管理器
III.自定义session管理器+tomcat context.xml替换(tomcat级关闭session)
如果想要在整个tomcat上关闭session功能,在不改变tomcat源码的情况下,需要自定义session管理器替换tomcat的默认管理器StandardManager
整个过程分为2步,实测并不起作用,可能是jar没被Tomcat扫描到,也可能是配置文件不生效,暂未解决
虽实测不通过,但不失为一个解决问题的方向,因此下文还是给出了当前的实现步骤
1.自定义新的管理器类并生成jar拷到tomcat的lib目录下
1.1自定义管理器类
package com.liuwei.session;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import org.apache.catalina.Context;
import org.apache.catalina.Manager;
import org.apache.catalina.Session;
import org.apache.catalina.SessionIdGenerator;
/**
* @author liuwei
* @date 2019-09-14 16:11:43
* @desc 自定义禁用Session的管理器
* 实现Manager接口,方法全部使用空实现
*/
public class SessionForbiddenManager implements Manager {
@Override
public Context getContext() {
// TODO Auto-generated method stub
return null;
}
@Override
public void setContext(Context context) {
// TODO Auto-generated method stub
}
...
}
1.2生成jar拷贝到tomcat的lib目录
不赘述
2.修改tomcat的conf/context.xml文件指定新的管理器类
<!-- Uncomment this to disable session persistence across Tomcat restarts -->
<!--
<Manager pathname="" />
-->
改为
<Manager pathname="com.liuwei.session.SessionForbiddenManager" />