聊聊springboot session timeout参数设置

时间:2025-03-19 11:44:43

本文主要介绍下spring boot中对session timeout参数值的设置过程。

ServerProperties

spring-boot-autoconfigure-1.5.!/org/springframework/boot/autoconfigure/web/

@Override
	public void customize(ConfigurableEmbeddedServletContainer container) {
		if (getPort() != null) {
			(getPort());
		}
		if (getAddress() != null) {
			(getAddress());
		}
		if (getContextPath() != null) {
			(getContextPath());
		}
		if (getDisplayName() != null) {
			(getDisplayName());
		}
		if (getSession().getTimeout() != null) {
			(getSession().getTimeout());
		}
		//......
	}
复制代码

对应的配置如下

=120
复制代码

需要注意单位是秒

TomcatEmbeddedServletContainerFactory

spring-boot-1.5.!/org/springframework/boot/context/embedded/tomcat/

protected void configureContext(Context context,
			ServletContextInitializer[] initializers) {
		TomcatStarter starter = new TomcatStarter(initializers);
		if (context instanceof TomcatEmbeddedContext) {
			// Should be true
			((TomcatEmbeddedContext) context).setStarter(starter);
		}
		(starter, NO_CLASSES);
		for (LifecycleListener lifecycleListener : ) {
			(lifecycleListener);
		}
		for (Valve valve : ) {
			().addValve(valve);
		}
		for (ErrorPage errorPage : getErrorPages()) {
			new TomcatErrorPage(errorPage).addToContext(context);
		}
		for ( mapping : getMimeMappings()) {
			((), ());
		}
		configureSession(context);
		for (TomcatContextCustomizer customizer : ) {
			(context);
		}
	}
private void configureSession(Context context) {
		long sessionTimeout = getSessionTimeoutInMinutes();
		((int) sessionTimeout);
		if (isPersistSession()) {
			Manager manager = ();
			if (manager == null) {
				manager = new StandardManager();
				(manager);
			}
			configurePersistSession(manager);
		}
		else {
			(new DisablePersistSessionListener());
		}
	}
private long getSessionTimeoutInMinutes() {
		long sessionTimeout = getSessionTimeout();
		if (sessionTimeout > 0) {
			sessionTimeout = ((sessionTimeout), 1L);
		}
		return sessionTimeout;
	}
复制代码

这里要注意一下,它内部转成分钟,然后设置给tomcat原生的StandardContext

可以从源码看到,如果设置小于60秒的话,则会默认取1分钟

StandardContext

tomcat-embed-core-8.5.!/org/apache/catalina/core/

@Override
    public void setSessionTimeout(int timeout) {

        int oldSessionTimeout = ;
        /*
         * SRV.13.4 ("Deployment Descriptor"):
         * If the timeout is 0 or less, the container ensures the default
         * behaviour of sessions is never to time out.
         */
         = (timeout == 0) ? -1 : timeout;
        ("sessionTimeout",
                                   oldSessionTimeout,
                                   );

    }
复制代码

这一步就是设置给原生的tomcat的StandardContext

session失效的计算

tomcat-embed-core-8.5.!/org/apache/catalina/session/

/**
     * Return the <code>isValid</code> flag for this session.
     */
    @Override
    public boolean isValid() {

        if (!) {
            return false;
        }

        if () {
            return true;
        }

        if (ACTIVITY_CHECK && () > 0) {
            return true;
        }

        if (maxInactiveInterval > 0) {
            int timeIdle = (int) (getIdleTimeInternal() / 1000L);
            if (timeIdle >= maxInactiveInterval) {
                expire(true);
            }
        }

        return ;
    }
复制代码

这里会去计算timeIdle,然后通过timeIdle的值跟设定的session timeout比较,超出则设置session失效

getIdleTimeInternal

	/**
     * Return the idle time from last client access time without invalidation check
     * @see #getIdleTime()
     */
    @Override
    public long getIdleTimeInternal() {
        long timeNow = ();
        long timeIdle;
        if (LAST_ACCESS_AT_START) {
            timeIdle = timeNow - lastAccessedTime;
        } else {
            timeIdle = timeNow - thisAccessedTime;
        }
        return timeIdle;
    }
复制代码

维护了两个变量,一个是lastAccessedTime,一个是thisAccessedTime

这个是在这个方法中更新

	/**
     * End the access.
     */
    @Override
    public void endAccess() {

        isNew = false;

        /**
         * The servlet spec mandates to ignore request handling time
         * in lastAccessedTime.
         */
        if (LAST_ACCESS_AT_START) {
             = ;
             = ();
        } else {
             = ();
             = ;
        }

        if (ACTIVITY_CHECK) {
            ();
        }

    }
复制代码

正常请求更新

Http11Processor

tomcat-embed-core-8.5.!/org/apache/coyote/http11/

    @Override
    public SocketState service(SocketWrapperBase<?> socketWrapper)
        throws IOException {
        //......

        while (!getErrorState().isError() && keepAlive && !isAsync() && upgradeToken == null &&
                sendfileState ==  && !()) {

            // ......
            // Process the request in the adapter
            if (!getErrorState().isError()) {
                try {
                    (.STAGE_SERVICE);
                    getAdapter().service(request, response);
                    // Handle when the response was committed before a serious
                    // error occurred.  Throwing a ServletException should both
                    // set the status to 500 and set the errorException.
                    // If we fail here, then the response is likely already
                    // committed, so we can't try and set headers.
                    if(keepAlive && !getErrorState().isError() && !isAsync() &&
                            statusDropsConnection(())) {
                        setErrorState(ErrorState.CLOSE_CLEAN, null);
                    }
                } catch (InterruptedIOException e) {
                    setErrorState(ErrorState.CLOSE_CONNECTION_NOW, e);
                } catch (HeadersTooLargeException e) {
                    ((""), e);
                    // The response should not have been committed but check it
                    // anyway to be safe
                    if (()) {
                        setErrorState(ErrorState.CLOSE_NOW, e);
                    } else {
                        ();
                        (500);
                        setErrorState(ErrorState.CLOSE_CLEAN, e);
                        ("Connection", "close"); // TODO: Remove
                    }
                } catch (Throwable t) {
                    (t);
                    ((""), t);
                    // 500 - Internal Server Error
                    (500);
                    setErrorState(ErrorState.CLOSE_CLEAN, t);
                    getAdapter().log(request, response, 0);
                }
            }

            // Finish the handling of the request
            (.STAGE_ENDINPUT);
            if (!isAsync()) {
                // If this is an async request then the request ends when it has
                // been completed. The AsyncContext is responsible for calling
                // endRequest() in that case.
                endRequest();
            }
            
            //......
        }
        //......
    }
复制代码

这里的service方法在getErrorState().isError()为false的时候,会调用adapter的service方法

CoyoteAdapter

tomcat-embed-core-8.5.!/org/apache/catalina/connector/

    @Override
    public void service( req,  res)
            throws Exception {

        Request request = (Request) (ADAPTER_NOTES);
        Response response = (Response) (ADAPTER_NOTES);

        //...

        try {
            // Parse and set Catalina and configuration specific
            // request parameters
            postParseSuccess = postParseRequest(req, request, res, response);
            if (postParseSuccess) {
                //check valves if we support async
                (
                        ().getContainer().getPipeline().isAsyncSupported());
                // Calling the container
                ().getContainer().getPipeline().getFirst().invoke(
                        request, response);
            }
            if (()) {
                async = true;
                ReadListener readListener = ();
                if (readListener != null && ()) {
                    // Possible the all data may have been read during service()
                    // method so this needs to be checked here
                    ClassLoader oldCL = null;
                    try {
                        oldCL = ().bind(false, null);
                        if (()) {
                            ().onAllDataRead();
                        }
                    } finally {
                        ().unbind(false, oldCL);
                    }
                }

                Throwable throwable =
                        (Throwable) (RequestDispatcher.ERROR_EXCEPTION);

                // If an async request was started, is not going to end once
                // this container thread finishes and an error occurred, trigger
                // the async error process
                if (!() && throwable != null) {
                    ().setErrorState(throwable, true);
                }
            } else {
                ();
                ();
            }

        } catch (IOException e) {
            // Ignore
        } finally {
            //......

            // Recycle the wrapper request and response
            if (!async) {
                ();
                ();
            }
        }
    }
复制代码

会在finally里头调用()

Request#recycle

tomcat-embed-core-8.5.!/org/apache/catalina/connector/ 里头的方法会调用recycleSessionInfo

protected void recycleSessionInfo() {
        if (session != null) {
            try {
                ();
            } catch (Throwable t) {
                (t);
                ((""), t);
            }
        }
        session = null;
        requestedSessionCookie = false;
        requestedSessionId = null;
        requestedSessionURL = false;
        requestedSessionSSL = false;
    }
复制代码

这里就更新了两个事件

forward中更新

适合处理error直接forward的情况,比如鉴权不通过,直接forward的,这个时候还没进入到servlet的service方法

ApplicationDispatcher#recycleRequestWrapper

tomcat-embed-core-8.5.!/org/apache/catalina/core/

private void invoke(ServletRequest request, ServletResponse response,
            State state) throws IOException, ServletException {

        //......

        // Get the FilterChain Here
        ApplicationFilterChain filterChain =
                (request, wrapper, servlet);

        // Call the service() method for the allocated servlet instance
        try {
            // for includes/forwards
            if ((servlet != null) && (filterChain != null)) {
               (request, response);
             }
            // Servlet Service Method is called by the FilterChain
        } catch (ClientAbortException e) {
            ioException = e;
        } catch (IOException e) {
            ().error(("",
                             ()), e);
            ioException = e;
        } catch (UnavailableException e) {
            ().error(("",
                             ()), e);
            servletException = e;
            (e);
        } catch (ServletException e) {
            Throwable rootCause = (e);
            if (!(rootCause instanceof ClientAbortException)) {
                ().error(("",
                        ()), rootCause);
            }
            servletException = e;
        } catch (RuntimeException e) {
            ().error(("",
                             ()), e);
            runtimeException = e;
        }

        // Release the filter chain (if any) for this request
        try {
            if (filterChain != null)
                ();
        } catch (Throwable e) {
            (e);
            ().error(("",
                             ()), e);
            // FIXME: Exception handling needs to be similar to what is in the StandardWrapperValue
        }

        // Deallocate the allocated servlet instance
        try {
            if (servlet != null) {
                (servlet);
            }
        } catch (ServletException e) {
            ().error(("",
                             ()), e);
            servletException = e;
        } catch (Throwable e) {
            (e);
            ().error(("",
                             ()), e);
            servletException = new ServletException
                (("",
                              ()), e);
        }

        // Reset the old context class loader
        (false, oldCCL);

        // Unwrap request/response if needed
        // See Bugzilla 30949
        unwrapRequest(state);
        unwrapResponse(state);
        // Recycle request if necessary (also BZ 30949)
        recycleRequestWrapper(state);

        // ......

    }
复制代码

执行完servlet之后(不管成功还是失败),会调用recycleRequestWrapper

private void recycleRequestWrapper(State state) {
        if ( instanceof ApplicationHttpRequest) {
            ((ApplicationHttpRequest) ).recycle();        }
    }
复制代码

tomcat-embed-core-8.5.!/org/apache/catalina/core/

	/**
     * Recycle this request
     */
    public void recycle() {
        if (session != null) {
            ();
        }
    }
复制代码

这里会调用endAccess,更新两个时间

小结

  • 每次的请求,都会跟新session的lastAccessedTime和thisAccessedTime,只有没有访问超过设定时间才会失效
  • 设定的单位是秒,但是小于60的话,会被重置为60,内部转为分钟单位来算,默认1800是30分钟