본문 바로가기

Backend Development/Spring boot

[Spring boot] Spring Security 분석 - Session timeout 설정하기

application.properties에서 타임아웃 설정

 

embedded tomcat을 사용한다면 기본적으로 application.properties에 다음 property로 세션 타임아웃 설정이 가능하다.

 

application.properties:109

 

# session timeout: unit default SECOND - default 120 minutes
server.servlet.session.timeout=10

 

위에서 설정된 Property값은 부팅시 아래 configuration에 의해 시스템에 로딩된다.

 

org/springframework/boot/spring-boot-autoconfigure/2.5.6/spring-boot-autoconfigure-2.5.6-sources.jar!/org/springframework/boot/autoconfigure/web/ServerProperties.java:45

 

@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {

	/**
	 * Server HTTP port.
	 */
	private Integer port;

	/**
	 * Network address to which the server should bind.
	 */
	private InetAddress address;

	@NestedConfigurationProperty
	private final ErrorProperties error = new ErrorProperties();

	/**
	 * Strategy for handling X-Forwarded-* headers.
	 */
	private ForwardHeadersStrategy forwardHeadersStrategy;

	/**
	 * Value to use for the Server response header (if empty, no header is sent).
	 */
	private String serverHeader;

	/**
	 * Maximum size of the HTTP message header.
	 */
	private DataSize maxHttpHeaderSize = DataSize.ofKilobytes(8);

	/**
	 * Type of shutdown that the server will support.
	 */
	private Shutdown shutdown = Shutdown.IMMEDIATE;

	@NestedConfigurationProperty
	private Ssl ssl;

	@NestedConfigurationProperty
	private final Compression compression = new Compression();

	@NestedConfigurationProperty
	private final Http2 http2 = new Http2();

	private final Servlet servlet = new Servlet();

	private final Tomcat tomcat = new Tomcat();

	private final Jetty jetty = new Jetty();

	private final Netty netty = new Netty();

	private final Undertow undertow = new Undertow();

 

org/springframework/boot/spring-boot/2.5.6/spring-boot-2.5.6-sources.jar!/org/springframework/boot/web/servlet/server/Session.java:57

 

/**
 * Session properties.
 *
 * @author Andy Wilkinson
 * @since 2.0.0
 */
public class Session {

	@DurationUnit(ChronoUnit.SECONDS)
	private Duration timeout = Duration.ofMinutes(30);

	private Set<Session.SessionTrackingMode> trackingModes;

	private boolean persistent;

	/**
	 * Directory used to store session data.
	 */
	private File storeDir;

	private final Cookie cookie = new Cookie();

	private final SessionStoreDirectory sessionStoreDirectory = new SessionStoreDirectory();

	public Cookie getCookie() {
		return this.cookie;
	}

	public Duration getTimeout() {
		return this.timeout;
	}

	public void setTimeout(Duration timeout) {
		this.timeout = timeout;
	}

 

설정된 timeout 값을 가져가는 부분 즉 setTimeout 호출 부를 확인해 보면 다음과 같다.

 

org/springframework/boot/spring-boot/2.5.6/spring-boot-2.5.6-sources.jar!/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactory.java:420

 

getSessionTimeoutInMinutes:426, TomcatServletWebServerFactory (org.springframework.boot.web.embedded.tomcat)
configureSession:394, TomcatServletWebServerFactory (org.springframework.boot.web.embedded.tomcat)
configureContext:383, TomcatServletWebServerFactory (org.springframework.boot.web.embedded.tomcat)
prepareContext:246, TomcatServletWebServerFactory (org.springframework.boot.web.embedded.tomcat)
getWebServer:198, TomcatServletWebServerFactory (org.springframework.boot.web.embedded.tomcat)
createWebServer:182, ServletWebServerApplicationContext (org.springframework.boot.web.servlet.context)
onRefresh:160, ServletWebServerApplicationContext (org.springframework.boot.web.servlet.context)
refresh:577, AbstractApplicationContext (org.springframework.context.support)
refresh:145, ServletWebServerApplicationContext (org.springframework.boot.web.servlet.context)
refresh:754, SpringApplication (org.springframework.boot)
refreshContext:434, SpringApplication (org.springframework.boot)
run:338, SpringApplication (org.springframework.boot)
run:1343, SpringApplication (org.springframework.boot)
run:1332, SpringApplication (org.springframework.boot)
main:27, SdpApplication (com.sdp)
invoke0:-1, NativeMethodAccessorImpl (jdk.internal.reflect)
invoke:62, NativeMethodAccessorImpl (jdk.internal.reflect)
invoke:43, DelegatingMethodAccessorImpl (jdk.internal.reflect)
invoke:566, Method (java.lang.reflect)
run:49, RestartLauncher (org.springframework.boot.devtools.restart)

 

private long getSessionTimeoutInMinutes() {
   Duration sessionTimeout = getSession().getTimeout();
   if (isZeroOrLess(sessionTimeout)) {
      return 0;
   }
   return Math.max(sessionTimeout.toMinutes(), 1);
}

public long toMinutes() {
    return this.seconds / 60L;
}

 

여기서 주의할 것은... 위 코드를 보면 알겠지만... 설정단위는 분 단위로 관리된다. 즉 sessionTimeout을 1분 미만으로 설정하면 위 메소드에서 1분으로 바꿔버리고 초단위를 /60으로 분으로 환산한다. 나머지는 올림하지 않는다.

 

Session Listner 로 setMaxInactiveInterval 설정하기

 

부팅 시 property 값으로 설정하기 외에 동적으로 세션 만료 시간을 바꾸고 싶으면 아래 메소드를 호출하면 된다.

 

org/apache/tomcat/embed/tomcat-embed-core/9.0.54/tomcat-embed-core-9.0.54-sources.jar!/javax/servlet/http/HttpSession.java:22

 

public interface HttpSession {

    ... 생략

    /**
     * Specifies the time, in seconds, between client requests before the
     * servlet container will invalidate this session. A zero or negative time
     * indicates that the session should never timeout.
     *
     * @param interval
     *            An integer specifying the number of seconds
     */
    public void setMaxInactiveInterval(int interval);

 

페이지 렌더링 시에 세션이 생성되면 아래와 같이 세션 생성이 되고 등록한 세션 리스너를 호출한다.

 

setMaxInactiveInterval:576, StandardSession (org.apache.catalina.session)
createSession:719, ManagerBase (org.apache.catalina.session)
doGetSession:3093, Request (org.apache.catalina.connector)
getSession:2493, Request (org.apache.catalina.connector)
getSession:908, RequestFacade (org.apache.catalina.connector)
getSession:920, RequestFacade (org.apache.catalina.connector)
getSession:253, HttpServletRequestWrapper (javax.servlet.http)
getSession:253, HttpServletRequestWrapper (javax.servlet.http)
getSession:253, HttpServletRequestWrapper (javax.servlet.http)
getSession:253, HttpServletRequestWrapper (javax.servlet.http)
getSession:253, HttpServletRequestWrapper (javax.servlet.http)
preHandle:30, Interceptor (com.sdp.common.interceptor)
applyPreHandle:148, HandlerExecutionChain (org.springframework.web.servlet)
doDispatch:1062, DispatcherServlet (org.springframework.web.servlet)
doService:963, DispatcherServlet (org.springframework.web.servlet)
processRequest:1006, FrameworkServlet (org.springframework.web.servlet)
doGet:898, FrameworkServlet (org.springframework.web.servlet)
service:655, HttpServlet (javax.servlet.http)
service:883, FrameworkServlet (org.springframework.web.servlet)
service:764, HttpServlet (javax.servlet.http)
internalDoFilter:227, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:53, WsFilter (org.apache.tomcat.websocket.server)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:218, AbstractAuthenticationProcessingFilter (org.springframework.security.web.authentication)
doFilter:212, AbstractAuthenticationProcessingFilter (org.springframework.security.web.authentication)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:36, XssEscapeServletFilter (com.navercorp.lucy.security.xss.servletfilter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:327, FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
invoke:115, FilterSecurityInterceptor (org.springframework.security.web.access.intercept)
doFilter:81, FilterSecurityInterceptor (org.springframework.security.web.access.intercept)
doFilter:336, FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
doFilter:121, ExceptionTranslationFilter (org.springframework.security.web.access)
doFilter:115, ExceptionTranslationFilter (org.springframework.security.web.access)
doFilter:336, FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
doFilter:126, SessionManagementFilter (org.springframework.security.web.session)
doFilter:81, SessionManagementFilter (org.springframework.security.web.session)
doFilter:336, FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
doFilter:105, AnonymousAuthenticationFilter (org.springframework.security.web.authentication)
doFilter:336, FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
doFilter:149, SecurityContextHolderAwareRequestFilter (org.springframework.security.web.servletapi)
doFilter:336, FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
doFilter:63, RequestCacheAwareFilter (org.springframework.security.web.savedrequest)
doFilter:336, FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
doFilterInternal:149, BasicAuthenticationFilter (org.springframework.security.web.authentication.www)
doFilter:119, OncePerRequestFilter (org.springframework.web.filter)
doFilter:336, FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
doFilter:103, LogoutFilter (org.springframework.security.web.authentication.logout)
doFilter:89, LogoutFilter (org.springframework.security.web.authentication.logout)
doFilter:336, FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
doHeadersAfter:90, HeaderWriterFilter (org.springframework.security.web.header)
doFilterInternal:75, HeaderWriterFilter (org.springframework.security.web.header)
doFilter:119, OncePerRequestFilter (org.springframework.web.filter)
doFilter:336, FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
doFilter:110, SecurityContextPersistenceFilter (org.springframework.security.web.context)
doFilter:80, SecurityContextPersistenceFilter (org.springframework.security.web.context)
doFilter:336, FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
doFilterInternal:55, WebAsyncManagerIntegrationFilter (org.springframework.security.web.context.request.async)
doFilter:119, OncePerRequestFilter (org.springframework.web.filter)
doFilter:336, FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
doFilterInternal:211, FilterChainProxy (org.springframework.security.web)
doFilter:183, FilterChainProxy (org.springframework.security.web)
invokeDelegate:358, DelegatingFilterProxy (org.springframework.web.filter)
doFilter:271, DelegatingFilterProxy (org.springframework.web.filter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:100, RequestContextFilter (org.springframework.web.filter)
doFilter:119, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:93, FormContentFilter (org.springframework.web.filter)
doFilter:119, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:201, CharacterEncodingFilter (org.springframework.web.filter)
doFilter:119, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
invoke:197, StandardWrapperValve (org.apache.catalina.core)
invoke:97, StandardContextValve (org.apache.catalina.core)
invoke:540, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:135, StandardHostValve (org.apache.catalina.core)
invoke:92, ErrorReportValve (org.apache.catalina.valves)
invoke:78, StandardEngineValve (org.apache.catalina.core)
service:357, CoyoteAdapter (org.apache.catalina.connector)
service:382, Http11Processor (org.apache.coyote.http11)
process:65, AbstractProcessorLight (org.apache.coyote)
process:895, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1722, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1191, ThreadPoolExecutor (org.apache.tomcat.util.threads)
run:659, ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:834, Thread (java.lang)

 

세션 리스너는 다음과 같이 구현하고 Application entry에 등록한다.

 

public class SessionListener implements HttpSessionListener {

	@Value("${server.servlet.session.timeout}")
	private int SESSION_TIMEOUT;

	private Logger log = LoggerFactory.getLogger(this.getClass());

	@Override
	public void sessionCreated(HttpSessionEvent se) {
		se.getSession().setMaxInactiveInterval(SESSION_TIMEOUT); //세션만료60분
	}

	@Override
	public void sessionDestroyed(HttpSessionEvent se) {

	}

}

 

@SpringBootApplication
public class SdpApplication extends SpringBootServletInitializer implements WebApplicationInitializer {

	@Bean
	public HttpSessionListener httpSessionListener(){
		return new SessionListener();
	}

	@Override
	protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
		return application.sources(SdpApplication.class);
	}

	public static void main(String[] args) {
		SpringApplication.run(SdpApplication.class, args);
	}

}