热门标签 | HotTags
当前位置:  开发笔记 > 前端 > 正文

Tomcat中实现Session小结

什么是Session 对Tomcat而言,Session是一块在服务器开辟的内存空间,其存储结构为ConcurrentHashMap;

什么是Session

对Tomcat而言,Session是一块在服务器开辟的内存空间,其存储结构为ConcurrentHashMap;

Session的目的

Http协议是一种无状态协议,即每次服务端接收到客户端的请求时,都是一个全新的请求,服务器并不知道客户端的历史请求记录;

Session的主要目的就是为了弥补Http的无状态特性。简单的说,就是服务器可以利用session存储客户端在同一个会话期间的一些操作记录;

实现机制

先看两个问题,如下:

1、服务器如何判断客户端发送过来的请求是属于同一个会话?

答:用Session id区分,Session id相同的即认为是同一个会话,在Tomcat中Session id用JSESSIONID表示;

2、服务器、客户端如何获取Session id?Session id在其之间是如何传输的呢?

答:服务器第一次接收到请求时,开辟了一块Session空间(创建了Session对象),同时生成一个Session id,并通过响应头的Set-COOKIE:“JSESSIOnID=XXXXXXX”命令,向客户端发送要求设置COOKIE的响应;

客户端收到响应后,在本机客户端设置了一个JSESSIOnID=XXXXXXX的COOKIE信息,该COOKIE的过期时间为浏览器会话结束;

接下来客户端每次向同一个网站发送请求时,请求头都会带上该COOKIE信息(包含Session id);

然后,服务器通过读取请求头中的COOKIE信息,获取名称为JSESSIONID的值,得到此次请求的Session id;

ps:服务器只会在客户端第一次请求响应的时候,在响应头上添加Set-COOKIE:“JSESSIOnID=XXXXXXX”信息,接下来在同一个会话的第二第三次响应头里,是不会添加Set-COOKIE:“JSESSIOnID=XXXXXXX”信息的;

而客户端是会在每次请求头的COOKIE中带上JSESSIONID信息;

举个例子:

以chrome浏览器为例,访问一个基于tomcat服务器的网站的时候,

浏览器第一次访问服务器,服务器会在响应头添加Set-COOKIE:“JSESSIOnID=XXXXXXX”信息,要求客户端设置COOKIE,如下图:

同时我们也可以在浏览器中找到其存储的sessionid信息,如下图

接下来,浏览器第二次、第三次...访问服务器,观察其请求头的COOKIE信息,可以看到JSESSIONID信息存储在COOKIE里,发送给服务器;且响应头里没有Set-COOKIE信息,如下图:

只要浏览器未关闭,在访问同一个站点的时候,其请求头COOKIE中的JSESSIONID都是同一个值,被服务器认为是同一个会话。

 再举个简单的例子加深印象,新建个Web工程,并写一个Servlet,在doGet中添加如下代码,主要做如下工作

首先,从session中获取key为count的值,累加,存入session,并打印;

然后,每次从请求中获取打印COOKIE信息,从响应中获取打印Header的Set-COOKIE信息:

  /**
   * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
   */
  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    if(request.getSession().getAttribute("count") == null){
      request.getSession().setAttribute("count", 0);
      response.getWriter().write(0+"");
    }else{
      int a = Integer.parseInt(request.getSession().getAttribute("count").toString());
      request.getSession().setAttribute("count", ++a);
      response.getWriter().write(a+"");
    }

    COOKIE[] COOKIEs = request.getCOOKIEs();
    StringBuffer sb = new StringBuffer();
    if(COOKIEs!=null){
      for(COOKIE COOKIE : COOKIEs){
        sb.append(COOKIE.getName()+":"+COOKIE.getValue()+",");
      }
      sb.deleteCharAt(sb.length()-1);
    }

    System.out.println("[第"+(++index)+"次访问]from client request, COOKIEs:" + sb);
    System.out.println("[第"+(index)+"次访问]from server response, header-Set-COOKIE:" + response.getHeader("Set-COOKIE"));;
  }

部署到tomcat后,连续访问该servlet,观察控制台输出,如下,客户端第一次访问服务器的时候,在服务端的响应头里添加了JSESSIONID信息,且接下来客户端的每次访问都会带上该JSESSIONID:

其实这里有一个问题,session劫持

只要用户知道JSESSIONID,该用户就可以获取到JSESSIONID对应的session内容,还是以上面这个例子为例,

我先用IE浏览器访问该站点,比如连续访问了5次,此时,session中的count值为:

查看该会话的Session id,为6A541281A79B24BC290ED3270CF15E32

接下来打开chrome控制台,将IE浏览器获取过来的JSESSIONID信息(“6A541281A79B24BC290ED3270CF15E32”)写入到COOKIE中,如下

接着删除其中的一个,只留下JSESSIONID为“6A541281A79B24BC290ED3270CF15E32”的COOKIE;

刷新页面,发现我们从session获取的count值已经变成6了,说明此次chrome浏览器的请求劫持了IE浏览器会话中的session,

Tomcat中的session实现

Tomcat中一个会话对应一个session,其实现类是StandardSession,查看源码,可以找到一个attributes成员属性,即存储session的数据结构,为ConcurrentHashMap,支持高并发的HashMap实现;

  /**
   * The collection of user data attributes associated with this Session.
   */
  protected Map attributes = new ConcurrentHashMap();

那么,tomcat中多个会话对应的session是由谁来维护的呢?ManagerBase类,查看其代码,可以发现其有一个sessions成员属性,存储着各个会话的session信息:

  /**
   * The set of currently active Sessions for this Manager, keyed by
   * session identifier.
   */
  protected Map sessiOns= new ConcurrentHashMap();

接下来,看一下几个重要的方法,

服务器查找Session对象的方法

客户端每次的请求,tomcat都会在HashMap中查找对应的key为JSESSIONID的Session对象是否存在,可以查看Request的doGetSession方法源码,如下源码:

protected Session doGetSession(boolean create) {

    // There cannot be a session if no context has been assigned yet
    Context cOntext= getContext();
    if (cOntext== null) {
      return (null);
    }

    // Return the current session if it exists and is valid
    if ((session != null) && !session.isValid()) {
      session = null;
    }
    if (session != null) {
      return (session);
    }

    // Return the requested session if it exists and is valid
    Manager manager = context.getManager();
    if (manager == null) {
      return null;    // Sessions are not supported
    }
    if (requestedSessionId != null) {
      try {
        session = manager.findSession(requestedSessionId);
      } catch (IOException e) {
        session = null;
      }
      if ((session != null) && !session.isValid()) {
        session = null;
      }
      if (session != null) {
        session.access();
        return (session);
      }
    }

    // Create a new session if requested and the response is not committed
    if (!create) {
      return (null);
    }
    if ((context != null) && (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 to 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;
        }
        sessiOnId= getRequestedSessionId();
      }
    } else {
      sessiOnId= null;
    }
    session = manager.createSession(sessionId);

    // Creating a new session COOKIE based on that session
    if ((session != null) && (getContext() != null)
        && getContext().getServletContext().
            getEffectiveSessionTrackingModes().contains(
                SessionTrackingMode.COOKIE)) {
      COOKIE COOKIE =
        ApplicationSessionCOOKIEConfig.createSessionCOOKIE(
            context, session.getIdInternal(), isSecure());

      response.addSessionCOOKIEInternal(COOKIE);
    }

    if (session == null) {
      return null;
    }

    session.access();
    return session;
  }

先看doGetSession方法中的如下代码,这个一般是第一次访问的情况,即创建session对象,session的创建是调用了ManagerBase的createSession方法来实现的; 另外,注意response.addSessionCOOKIEInternal方法,该方法的功能就是上面提到的往响应头写入“Set-COOKIE”信息;最后,还要调用session.access方法记录下该session的最后访问时间,因为session是可以设置过期时间的;

 session = manager.createSession(sessionId);

    // Creating a new session COOKIE based on that session
    if ((session != null) && (getContext() != null)
        && getContext().getServletContext().
            getEffectiveSessionTrackingModes().contains(
                SessionTrackingMode.COOKIE)) {
      COOKIE COOKIE =
        ApplicationSessionCOOKIEConfig.createSessionCOOKIE(
            context, session.getIdInternal(), isSecure());

      response.addSessionCOOKIEInternal(COOKIE);
    }

    if (session == null) {
      return null;
    }

    session.access();
    return session;

再看doGetSession方法中的如下代码,这个一般是第二次以后访问的情况,通过ManagerBase的findSession方法查找session,其实就是利用map的key从ConcurrentHashMap中拿取对应的value,这里的key即requestedSessionId,也即JSESSIONID,同时还要调用session.access方法,记录下该session的最后访问时间;

    if (requestedSessionId != null) {
      try {
        session = manager.findSession(requestedSessionId);
      } catch (IOException e) {
        session = null;
      }
      if ((session != null) && !session.isValid()) {
        session = null;
      }
      if (session != null) {
        session.access();
        return (session);
      }
    }

在session对象中查找和设置key-value的方法

这个我们一般调用getAttribute/setAttribute方法:

getAttribute方法很简单,就是根据key从map中获取value;

setAttribute方法稍微复杂点,除了设置key-value外,如果添加了一些事件监听(HttpSessionAttributeListener)的话,还要通知执行,如beforeSessionAttributeReplaced, afterSessionAttributeReplaced, beforeSessionAttributeAdded、 afterSessionAttributeAdded。。。

session存在的问题

  • 安全性,session劫持,这个前面已经举过例子了;
  • 增加服务器压力,因为session是直接存储在服务器的内存中的;
  • 如果存在多台服务器的话,还存在session同步问题,当然如果只有一台tomcat服务器的话,也就没有session同步的事情了,然而现在一般的应用都会用到多台tomcat服务器,通过负载均衡,同一个会话有可能会被分配到不同的tomcat服务器,因此很可能出现session不一致问题;解决session同步问题,实际上主要是保证能够抽离出一块共享空间存放session信息,且这块空间不同的tomcat服务器都可以访问到;一般这块共享的空间可以是数据库,或者某台服务器的内存空间,甚至硬盘空间,或者客户端的COOKIE也是可以的;

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


推荐阅读
  • 深入理解Cookie与Session会话管理
    本文详细介绍了如何通过HTTP响应和请求处理浏览器的Cookie信息,以及如何创建、设置和管理Cookie。同时探讨了会话跟踪技术中的Session机制,解释其原理及应用场景。 ... [详细]
  • 如何配置Unturned服务器及其消息设置
    本文详细介绍了Unturned服务器的配置方法和消息设置技巧,帮助用户了解并优化服务器管理。同时,提供了关于云服务资源操作记录、远程登录设置以及文件传输的相关补充信息。 ... [详细]
  • 网络攻防实战:从HTTP到HTTPS的演变
    本文通过一系列日记记录了从发现漏洞到逐步加强安全措施的过程,探讨了如何应对网络攻击并最终实现全面的安全防护。 ... [详细]
  • 解决MongoDB Compass远程连接问题
    本文记录了在使用阿里云服务器部署MongoDB后,通过MongoDB Compass进行远程连接时遇到的问题及解决方案。详细介绍了从防火墙配置到安全组设置的各个步骤,帮助读者顺利解决问题。 ... [详细]
  • 在现代网络环境中,两台计算机之间的文件传输需求日益增长。传统的FTP和SSH方式虽然有效,但其配置复杂、步骤繁琐,难以满足快速且安全的传输需求。本文将介绍一种基于Go语言开发的新一代文件传输工具——Croc,它不仅简化了操作流程,还提供了强大的加密和跨平台支持。 ... [详细]
  • 网络运维工程师负责确保企业IT基础设施的稳定运行,保障业务连续性和数据安全。他们需要具备多种技能,包括搭建和维护网络环境、监控系统性能、处理突发事件等。本文将探讨网络运维工程师的职业前景及其平均薪酬水平。 ... [详细]
  • 本文探讨了 Spring Boot 应用程序在不同配置下支持的最大并发连接数,重点分析了内置服务器(如 Tomcat、Jetty 和 Undertow)的默认设置及其对性能的影响。 ... [详细]
  • 本文总结了汇编语言中第五至第八章的关键知识点,涵盖间接寻址、指令格式、安全编程空间、逻辑运算指令及数据重复定义等内容。通过详细解析这些内容,帮助读者更好地理解和应用汇编语言的高级特性。 ... [详细]
  • 本文详细介绍了如何使用Python编写爬虫程序,从豆瓣电影Top250页面抓取电影信息。文章涵盖了从基础的网页请求到处理反爬虫机制,再到多页数据抓取的全过程,并提供了完整的代码示例。 ... [详细]
  • UNP 第9章:主机名与地址转换
    本章探讨了用于在主机名和数值地址之间进行转换的函数,如gethostbyname和gethostbyaddr。此外,还介绍了getservbyname和getservbyport函数,用于在服务器名和端口号之间进行转换。 ... [详细]
  • 360SRC安全应急响应:从漏洞提交到修复的全过程
    本文详细介绍了360SRC平台处理一起关键安全事件的过程,涵盖从漏洞提交、验证、排查到最终修复的各个环节。通过这一案例,展示了360在安全应急响应方面的专业能力和严谨态度。 ... [详细]
  • 本文深入探讨了Linux系统中网卡绑定(bonding)的七种工作模式。网卡绑定技术通过将多个物理网卡组合成一个逻辑网卡,实现网络冗余、带宽聚合和负载均衡,在生产环境中广泛应用。文章详细介绍了每种模式的特点、适用场景及配置方法。 ... [详细]
  • 本文详细分析了Hive在启动过程中遇到的权限拒绝错误,并提供了多种解决方案,包括调整文件权限、用户组设置以及环境变量配置等。 ... [详细]
  • 本文详细探讨了HTTP 500内部服务器错误的成因、解决方案及其在Web开发中的影响。通过对具体案例的分析,帮助读者理解并解决此类问题。 ... [详细]
  • 本文介绍了如何使用PHP代码实现微信平台的媒体素材上传功能,详细解释了API接口的使用方法和注意事项,确保文件路径正确以避免常见的错误。 ... [详细]
author-avatar
手机用户2602925875
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有