Bootstrap

SpringSession源码分析

默认对常规Session的理解和使用,如何使用Set-Cookie。

Maven库

常见的spring-session-data-redis依赖spring-session-core

    <dependency>
      <groupId>org.springframework.session</groupId>
      <artifactId>spring-session-core</artifactId>
    </dependency>

带着问题看源码

1、Controller代码块内request.getSession()是在哪创建的呢?
2、响应头Set-Cookie是在什么地方赋值的呢?

SessionRepositoryFilter 拦截器

org.springframework.session.web.http.SessionRepositoryFilter

    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);
        // 请求包装,进入包装类可以看到request.getSession()源码
        SessionRepositoryFilter<S>.SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(request, response);
        SessionRepositoryFilter<S>.SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(wrappedRequest, response);

        try {
            filterChain.doFilter(wrappedRequest, wrappedResponse);
        } finally {
            // 响应头`Set-Cookie`赋值入口(这里不理解为啥使用请求提交,而不是用响应提交`wrappedResponse.commitSession()`)
            // 方法跳转到DefaultCookieSerializer类就看见具体Set-Cookie了
            wrappedRequest.commitSession();
        }

    }

SessionRepositoryRequestWrapper 包装类

org.springframework.session.web.http.SessionRepositoryFilter.SessionRepositoryRequestWrapper
Spring中存在很多继承HttpServletRequestWrapper的包装类

        public SessionRepositoryFilter<S>..SessionRepositoryRequestWrapper.HttpSessionWrapper getSession(boolean create) {
            SessionRepositoryFilter<S>..SessionRepositoryRequestWrapper.HttpSessionWrapper currentSession = this.getCurrentSession();
            if (currentSession != null) {
                return currentSession;
            } else {
                S requestedSession = this.getRequestedSession();
                if (requestedSession != null) {
                    if (this.getAttribute(SessionRepositoryFilter.INVALID_SESSION_ID_ATTR) == null) {
                        requestedSession.setLastAccessedTime(Instant.now());
                        this.requestedSessionIdValid = true;
                        currentSession = new HttpSessionWrapper(requestedSession, this.getServletContext());
                        currentSession.markNotNew();
                        this.setCurrentSession(currentSession);
                        return currentSession;
                    }
                } else {
                    if (SessionRepositoryFilter.SESSION_LOGGER.isDebugEnabled()) {
                        SessionRepositoryFilter.SESSION_LOGGER.debug("No session found by id: Caching result for getSession(false) for this HttpServletRequest.");
                    }

                    this.setAttribute(SessionRepositoryFilter.INVALID_SESSION_ID_ATTR, "true");
                }

                if (!create) {
                    return null;
                } else if (SessionRepositoryFilter.this.httpSessionIdResolver instanceof CookieHttpSessionIdResolver && this.response.isCommitted()) {
                    throw new IllegalStateException("Cannot create a session after the response has been committed");
                } else {
                    if (SessionRepositoryFilter.SESSION_LOGGER.isDebugEnabled()) {
                        SessionRepositoryFilter.SESSION_LOGGER.debug("A new session was created. To help you troubleshoot where the session was created we provided a StackTrace (this is not an error). You can prevent this from appearing by disabling DEBUG logging for " + SessionRepositoryFilter.SESSION_LOGGER_NAME, new RuntimeException("For debugging purposes only (not an error)"));
                    }

                    S session = SessionRepositoryFilter.this.sessionRepository.createSession();
                    session.setLastAccessedTime(Instant.now());
                    currentSession = new HttpSessionWrapper(session, this.getServletContext());
                    this.setCurrentSession(currentSession);
                    return currentSession;
                }
            }
        }
        // 看这里
        @Override
        public SessionRepositoryFilter<S>..SessionRepositoryRequestWrapper.HttpSessionWrapper getSession() {
            return this.getSession(true);
        }

DefaultCookieSerializer

org.springframework.session.web.http.DefaultCookieSerializer
特别注意地方是:Set-Cookie内值是通过Base64加密的。

    public void writeCookieValue(CookieSerializer.CookieValue cookieValue) {
        HttpServletRequest request = cookieValue.getRequest();
        HttpServletResponse response = cookieValue.getResponse();
        StringBuilder sb = new StringBuilder();
        sb.append(this.cookieName).append('=');
        String value = this.getValue(cookieValue);
        if (value != null && value.length() > 0) {
            this.validateValue(value);
            sb.append(value);
        }

        int maxAge = this.getMaxAge(cookieValue);
        if (maxAge > -1) {
            sb.append("; Max-Age=").append(cookieValue.getCookieMaxAge());
            ZonedDateTime expires = maxAge != 0 ? ZonedDateTime.now(this.clock).plusSeconds((long)maxAge) : Instant.EPOCH.atZone(ZoneOffset.UTC);
            sb.append("; Expires=").append(expires.format(DateTimeFormatter.RFC_1123_DATE_TIME));
        }

        String domain = this.getDomainName(request);
        if (domain != null && domain.length() > 0) {
            this.validateDomain(domain);
            sb.append("; Domain=").append(domain);
        }

        String path = this.getCookiePath(request);
        if (path != null && path.length() > 0) {
            this.validatePath(path);
            sb.append("; Path=").append(path);
        }

        if (this.isSecureCookie(request)) {
            sb.append("; Secure");
        }

        if (this.useHttpOnlyCookie) {
            sb.append("; HttpOnly");
        }

        if (this.sameSite != null) {
            sb.append("; SameSite=").append(this.sameSite);
        }

        response.addHeader("Set-Cookie", sb.toString());
    }

🔚

源码分析主要是找到入口,关键入口代码已经贴上了,剩下的自己打断点看吧。

;