对 Tomcat 而言,Session 是一块在服务器开辟的内存空间,其存储结构为 ConcurrentHashMap;
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 都是同一个值,被服务器认为是同一个会话。
首先,从 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:
只要用户知道 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,其实现类是 StandardSession,查看源码,可以找到一个 attributes 成员属性,即存储 session 的数据结构,为 ConcurrentHashMap,支持高并发的 HashMap 实现;
- /**
- * The collection of user data attributes associated with this Session.
- */
- protected Map < String,
- Object > attributes = new ConcurrentHashMap < String,
- Object > ();
那么,tomcat 中多个会话对应的 session 是由谁来维护的呢?ManagerBase 类,查看其代码,可以发现其有一个 sessions 成员属性,存储着各个会话的 session 信息:
- /**
- * The set of currently active Sessions for this Manager, keyed by
- * session identifier.
- */
- protected Map < String,
- Session > sessions = new ConcurrentHashMap < String,
- 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);
- }
- }
这个我们一般调用 getAttribute/setAttribute 方法:
getAttribute 方法很简单,就是根据 key 从 map 中获取 value;
setAttribute 方法稍微复杂点,除了设置 key-value 外,如果添加了一些事件监听(HttpSessionAttributeListener)的话,还要通知执行,如 beforeSessionAttributeReplaced, afterSessionAttributeReplaced, beforeSessionAttributeAdded、 afterSessionAttributeAdded。。。
来源: http://www.bubuko.com/infodetail-1993808.html