tomcat · 2022-01-23 0

tomcat中JSESSIONID生成原理和作用

一、概述

tomcat 识别用户,依靠的 cookie 和 session。cookie 保存在客户端,name 是 "JSESSIONID",values 是 sessionId。session 保存在 tomcat 的 StandardManager 对象中。

二、JSESSIONID 的生成与响应头

当在 servlet 中使用,request.getSession() 时,会得到 session,这个request 是 RequestFacade,会执行 Request 的 getSession(true)

1、查看 request 的成员变量 session 是否有值,如果有,直接返回 session

2、如果 request 的成员变量 session 为空,查看 request 的成员变量 requestedSessionId 是否有值,如果有从 StandardManager 获得 session (StandardManager 有 Map<String, Session> sessions,保存的 sessionId 与 session)

3、查看 request 的 session 为 null,requestedSessionId 也为 null,使用 StandardManager 创建 StandardSesison,并放到 StandardManager 的 sessions 中;创建 cookie,cookie 的 name 是 "JSESSIONID",值是 sessionId,把 cookie 放到响应头,例:addHeader("Set-Cookie", "JSESSIONID=E5C491ACB2326D22649FCFCC994500CF; Path=/servlet_hello; HttpOnly")

三、JSESSIONID 的作用与请求头

当请求到达 tomcat,会分析请求,然后执行 filter 和 servlet

1、执行 CoyoteAdapter 的 service(request, response) 方法

2、执行 CoyoteAdapter 的 postParseRequest(req, request, res, response) 分析请求,把 name 是 JSESSIONID 的 cookie 的值,赋给 Request 的 requestedSessionId 成员变量

3、执行 AuthenticatorBase 的 invoke(request, response) 方法,request.getSessionInternal(false),
根据 requestedSessionId 获得 从 StandardManager 获得 Session,把 Session 赋值给 Request。

4、 ApplicationFilterChain 执行 doFilter(request, response) 方法,执行Filter,然后执行 Servlet

四、分析

<dependencies>
    <!--
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>4.0.1</version>
        <scope>provided</scope>
    </dependency>
    -->

    <dependency>
        <groupId>org.apache.tomcat.embed</groupId>
        <artifactId>tomcat-embed-core</artifactId>
        <version>9.0.79</version>
        <scope>provided</scope>
    </dependency>
</dependencies>
  1. 第一次请求
    1) 执行 NonLoginAuthenticator 的 invoke,会调用 request.getSessionInternal(false)
    2) 业务执行 request.getSession(),Request 创建 session,并把 cookie 写入响应头,响应头为 Set-Cookie: JSESSIONID=E5C491ACB2326D22649FCFCC994500CF; Path=/servlet_hello; HttpOnly

  2. 第二次请求
    1) 请求携带 cookie,请求头为 Cookie: JSESSIONID=E5C491ACB2326D22649FCFCC994500CF
    2) 执行 NonLoginAuthenticator 的 invoke,会调用 request.getSessionInternal(false),根据 requestedSessionId,从 StandardManager 对象中找到 session。赋值 request 中 session
    3) 业务执行 request.getSession(),从 request 中得到 session

// NonLoginAuthenticator.java
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
    Session session = request.getSessionInternal(false);
}
// Request.java
public Session getSessionInternal(boolean create) {
    return doGetSession(create);
}

protected Session doGetSession(boolean create) {
    Context context = getContext();
    if (context == null) {
        return null;
    }

    if ((session != null) && !session.isValid()) {
        session = null;
    }
    if (session != null) {
        return session;
    }

    Manager manager = context.getManager();
    if (manager == null) {
        return null;
    }
    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;
        }
    }

    if (!create) {
        return null;
    }
    ...
    String sessionId = getRequestedSessionId();
    ...
    session = manager.createSession(sessionId);

    if (session != null && trackModesIncludesCookie) {
        Cookie cookie =
                ApplicationSessionCookieConfig.createSessionCookie(context, session.getIdInternal(), isSecure());

        response.addSessionCookieInternal(cookie);
    }

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

    session.access();
    return session;
}
// StandardManager.java
@Override
public Session createSession(String sessionId) {
    ...
    Session session = createEmptySession();

    session.setNew(true);
    session.setValid(true);
    session.setCreationTime(System.currentTimeMillis());
    session.setMaxInactiveInterval(getContext().getSessionTimeout() * 60);
    String id = sessionId;
    if (id == null) {
        id = generateSessionId();
    }
    session.setId(id);
    sessionCounter++;

    SessionTiming timing = new SessionTiming(session.getCreationTime(), 0);
    synchronized (sessionCreationTiming) {
        sessionCreationTiming.add(timing);
        sessionCreationTiming.poll();
    }
    return session;
}

@Override
public Session createEmptySession() {
    return getNewSession();
}

protected StandardSession getNewSession() {
    return new StandardSession(this);
}

StandardSession 执行 setId() 方法时,会调用事件通知

// StandardSession.java
@Override
public void setId(String id) {
    setId(id, true);
}

@Override
public void setId(String id, boolean notify) {

    if ((this.id != null) && (manager != null)) {
        manager.remove(this);
    }

    this.id = id;

    if (manager != null) {
        manager.add(this);
    }

    if (notify) {
        tellNew();
    }
}

public void tellNew() {
    fireSessionEvent(Session.SESSION_CREATED_EVENT, null);
    Context context = manager.getContext();
    Object listeners[] = context.getApplicationLifecycleListeners();
    if (listeners != null && listeners.length > 0) {
        HttpSessionEvent event = new HttpSessionEvent(getSession());
        for (Object o : listeners) {
            if (!(o instanceof HttpSessionListener)) {
                continue;
            }
            HttpSessionListener listener = (HttpSessionListener) o;
            try {
                context.fireContainerEvent("beforeSessionCreated", listener);
                listener.sessionCreated(event);
                context.fireContainerEvent("afterSessionCreated", listener);
            } catch (Throwable t) {
                ...
                context.fireContainerEvent("afterSessionCreated", listener);
                ...
            }
        }
    }

}