tomcat中JSESSIONID生成原理以及条件

  • Post author:
  • Post category:其他





序言:




写这个文章的目的,就是把平时学习的一些东西做个笔记,以防以后忘记和查找方便使用。感兴趣的同学可以通过本文对tomcat中的session机制进行了解。写的不好之处请见谅。




1、session和cookie的基础




由于http协议是无状态的协议,为了能够记住请求的状态,于是引入了Session和Cookie的机制。我们应该有一个很明确的概念,那就是Session是存在于服务器端的,在单体式应用中,他是由tomcat管理的,存在于tomcat的内存中,而Cookie则是存在于客户端,更方便理解的说法,可以说存在于浏览器。Cookie并不常用,至少在企业或者互联网开发中,并没有什么场景需要我们过多的关注Cookie。http协议允许从服务器返回Response时携带一些Cookie,并且同一个域下对Cookie的数量有所限制,之前说过Session的持久化依赖于服务端的策略,而Cookie的持久化则是依赖于本地文件。虽然说Cookie并不常用,但是有一类特殊的Cookie却是我们需要额外关注的,那便是与Session相关的sessionId,他是真正维系客户端和服务端的桥梁。





2、环境搭建










为了研究出tomcat中的

JSESSIONID是如何生成的,我们需要搭建一个web测试环境,然后追踪tomcat的源码,一窥究竟。















这里为了方便,我使用SpringBoot来快速搭建一个web环境,开发工具采用的是IDEA,构建工具为Maven。相关配置如下:













父pom配置:





















<groupId>com.my.learn</groupId>
<artifactId>spring-root</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>

<name>spring-root</name>

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.10.RELEASE</version>
</parent>

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
</properties>
<dependencies>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.16.6</version>
    </dependency>
</dependencies>
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>



工程pom配置:


<artifactId>session-tomcat</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>session-tomcat</name>
  <parent>
      <groupId>com.my.learn</groupId>
      <artifactId>spring-root</artifactId>
      <version>1.0-SNAPSHOT</version>
  </parent>
<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>
</dependencies>



添加一个controller负责接受浏览器请求:
















@RestController
@CommonsLog
public class CookieController {

    @RequestMapping("/test/cookie")
    public String cookie(HttpServletRequest request,
                         HttpServletResponse response,
                         HttpSession session) {

        Cookie[] cookies = request.getCookies();
        if(cookies !=null) {
            Arrays.stream(cookies).forEach((cookie) ->
                    log.info(cookie.getName() + " : " + cookie.getValue())
            );
        }

        return "index";
    }

    @RequestMapping("/test/cookie1")
    public String cookie1( HttpServletRequest request) {
        HttpSession session = request.getSession(false);

        return "index";
    }

}


3、分析问题追踪源码














第一步、在chorm浏览器输入地址http://localhost:8080/test/cookie,可以看到如下结果:

































第二步:删除第一步中产生的cookie[JESSIONID],访问连接

http://localhost:8080/test/cookie1,产生如下结果:






























这时候没有产生绑定session的cookie。













是什么原因导致

JESSIONID没有生成。















通过结果我们分析,服务器往浏览器中返回cookie信息,一般都是通过HttpServletResponse的addCookie去完成。















我们在/test/cookie请求中添加一段代码,



















response.addCookie(new Cookie("key","value"));

在上面打断点跟踪源代码发现,最终添加cookie的代码是在ResponseFacade.addCookie 方法中,类

ResponseFacade 含有org.apache.catalina.connector.Response的实例对象,addCookie方法通过对象response完成调用。














@Override
    public void addCookie(Cookie cookie) {

        if (isCommitted()) {
            return;
        }

        response.addCookie(cookie);

    }



在response对象又含有org.apache.coyote.Response对象,最终的添加通过

org.apache.coyote.Response对象来完成的。
















 private void addHeader(String name, String value, Charset charset) {

        if (name == null || name.length() == 0 || value == null) {
            return;
        }

        if (isCommitted()) {
            return;
        }

        // Ignore any call from an included servlet
        if (included) {
            return;
        }

        char cc=name.charAt(0);
        if (cc=='C' || cc=='c') {
            if (checkSpecialHeader(name, value))
            return;
        }

        getCoyoteResponse().addHeader(name, value, charset);
    }



我们通过对上面的底层代码大断点,最后发现session中

JESSIONID实在下面的调用过程中创建的。











在 org.apache.catalina.connector.Request对象的doGetSession方法中调用的。
















// Create a new session if requested and the response is not committed
        if (!create) {
            return (null);
        }
        if (response != null
                && context.getServletContext()
                        .getEffectiveSessionTrackingModes()
                        .contains(SessionTrackingMode.COOKIE)
                && response.getResponse().isCommitted()) {
            throw new IllegalStateException(
                    sm.getString("coyoteRequest.sessionCreateCommitted"));
        }

        // Re-use session IDs provided by the client in very limited
        // circumstances.
        String sessionId = getRequestedSessionId();
        if (requestedSessionSSL) {
            // If the session ID has been obtained from the SSL handshake then
            // use it.
        } else if (("/".equals(context.getSessionCookiePath())
                && isRequestedSessionIdFromCookie())) {
            /* This is the common(ish) use case: using the same session ID with
             * multiple web applications on the same host. Typically this is
             * used by Portlet implementations. It only works if sessions are
             * tracked via cookies. The cookie must have a path of "/" else it
             * won't be provided for requests to all web applications.
             *
             * Any session ID provided by the client should be for a session
             * that already exists somewhere on the host. Check if the context
             * is configured for this to be confirmed.
             */
            if (context.getValidateClientProvidedNewSessionId()) {
                boolean found = false;
                for (Container container : getHost().findChildren()) {
                    Manager m = ((Context) container).getManager();
                    if (m != null) {
                        try {
                            if (m.findSession(sessionId) != null) {
                                found = true;
                                break;
                            }
                        } catch (IOException e) {
                            // Ignore. Problems with this manager will be
                            // handled elsewhere.
                        }
                    }
                }
                if (!found) {
                    sessionId = null;
                }
            }
        } else {
            sessionId = null;
        }
        session = manager.createSession(sessionId);

        // Creating a new session cookie based on that session
        if (session != null
                && context.getServletContext()
                        .getEffectiveSessionTrackingModes()
                        .contains(SessionTrackingMode.COOKIE)) {
            Cookie cookie =
                ApplicationSessionCookieConfig.createSessionCookie(
                        context, session.getIdInternal(), isSecure());

            response.addSessionCookieInternal(cookie);
        }

通过代码我发现是由,Request中getSession(boolean create)发起的。当我们的create设置为true的时候,当session被new出来之后,会在当前的Resposne中自动添加

JESSIONID的这个cookie。假如获取session这是为false,或者不从容器中获取session,就不添加

JESSIONID这个cookie。



我们在controller的方法上添加断点,可以看到response对象的实际封装结构。如下













这时候传递给我们的response对象里面已经包含了JESSIONID的cookie信息了。












4、结论












对于tomcat容器来说,当服务端的session被创建时,Response中自动添加了一个Cookie:JSESSIONID:xxxx,再后续的请求中,浏览器也是自动的带上了这个Cookie,服务端根据Cookie中的JSESSIONID取到了对应的session。这验证了一开始的说法,客户端服务端是通过JSESSIONID进行交互的,并且,添加和携带key为JSESSIONID的Cookie都是tomcat和浏览器自动帮助我们完成的,这很关键。





















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