Tomcat禁用Session

  • Post author:
  • Post category:其他



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

postman



方式3:tomcat服务管理器

tomcat01
安装和进入服务管理器
tomcat02
查看会话数量统计


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" />



版权声明:本文为weixin_38270240原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。