HttpSession & Spring Session

HttpSession & Spring Session


HttpSession

HttpSessionSpring이 아니라 Tomcate과 같은 Servlet Container가 생성하여 Spring에 주입한다. Tomcat은 기본적으로 HttpSession의 구현체로 StandardSession을 사용한다.


SpringSession

SpringSession은 어플리케이션 컨테이너(Tomcat 등) 내에서 HttpSession을 대체할 수 있다. 또한 Session Clustering과 RestAPI를 지원한다.

또한 Spring Session은 filter 기반 작동 방식이기 때문에 서블릿 컨테이너에서 filter가 어떻게 작동하는지를 먼저 파악해야 한다.


HttpSession Integeration

Spring SessionHttpSession과의 투명한 통합을 제공한다. (개발자는 HttpSession 구현체를 Spring Session 구현체로 바꿔 사용 가능하다.)


Why Spring Session and HttpSession

그렇다면 HttpSession 대신 Spring Session을 사용했을 떄의 이점은 무엇일까?

  • Session Clustring 지원
    • 어플리케이션에 종속적이지 않은 Session Clustring을 지원한다.
      • 특정 컨테이너(톰캣, 제우스 등)에 종속적이지 않다는 의미.
  • RESTful APIs
    • Spring SessionRESTful API의 헤더에 제공된 세션을 이용할 수 있다.

HttpSession with Redis

HttpSession을 사용하는 모든 작업에 대하여 Servlet Filter를 추가함으로써 Spring SessionHttpSession을 함께 사용할 수 있다.

  • Java-based Configuration을 통해 설정이 가능하다.
  • XML-based Configuration을 통해 설정이 가능하다.

위와 같이 Java-Configuration으로 설정할 경우, HttpSession의 구현체를 Spring Session이 지원하는 구현체로 대체하는 Servlet Filter를 생성할 수 있다.

@EnableRedisHttpSession 어노테이션은 springSessionRepositoryFilter라는 이름을 가진 Bean을 생성하는데, 이 Filter가 바로 HttpSession의 구현체를 Spring Session이 지원하는 구현체로 교체하는 역할을 수행한다. 위의 경우 Spring SessionRedis에 지원을 받는다.

LettuceConnectionFactory()를 반환함으로써 RedisConnectionFactory를 가질 수 있는데, RedisConnectionFactoryRedis ServerSpring Session 사이의 연결 객체를 갖는다.


서블릿 컨테이너에서의 Filter 동작

Spring-SessionFilter로 동작한다고 하였다. 따라서 **서블릿 컨테이너(Tomcate, Jeus etc.)**에서 필터가 어떤 방식으로 동작하는지 먼저 알아야 한다.

톰캣의 경우를 살펴보면, 서블릿 컨테이너는 지정된 포트(8080)에서 TCP Socket을 리스닝 하고 있다가 네트워크로부터 데이터가 들어오면 작업을 시작한다. 즉, 클라이언트로부터 HTTP Request 수신 시 일을 시작한다.

소켓으로 들어온 데이터를 Request 객체로 캡슐화하여 서블릿을 호출할 때 넘겨준다. 여기서 서블릿을 호출하기 전에 미리 정의해놓은 Filter들을 먼저 실행시킨다. 톰캣은 FilterChain에게 Filter를 순서대로 호출하고 마지막에 서블릿 호출하도록 위임한다.


HttpSession

Session 구현을 위해 존재하는 HttpSession 인터페이스는 세션의 속성을 CRUD 하거나 타임아웃 시간을 설정하는 퍼블릭 인터페이스를 제공한다. Tomcat클라이언트별로 Session 객체를 할당하여 내부에 보관하고 있다가(Memory) 다음 요청이 있을 때 참조한다. 여기서 Session Tracking 개념이 등장한다.


Session Tracking

일반적인 Http Request에는 유저에 대한 정보가 없다. 서블릿 컨테이너(Tomcat)는 수신한 요청이 누가 보낸 요청인지, 즉 식별자가 있어야 내부에 저장하고 있는 Session 객체를 읽어서 참조할 것이다. 다시 말하면, 서블릿 컨테이너가 누가 요청을 보냈는지 어떻게 식별하느냐에 대한 이야기가 Session Tracking Mechanism이다.

보통 Session을 추적하기 위해서 Cookie를 사용하는것이 일반적이다. **컨테이너에서 요청에 대한 응답을 보낼 때 Session을 만들고 SessionIDCookie로 내려보내는 것이다.

클라이언트가 받은 Cookie는 다음 요청에 덧붙여져서 오기 때문에 컨테이너가 요청을 받았을 때 Cookie에 있는 Session 정보를 이용해서 요청자를 식별할 수 있다.

그래서 웹브라우저의 개발자 도구로 쿠키목록을 보면 JSESSIONID가 있는 것이다.


게으른 Session

컨테이너에서 Session 객체를 만들거나 Reuqest에 있는 IDSession을 가져오는 비용은 생각보다 크기 때문에 요청이 있을 때 새로 만들거나 찾아온다. 여기서 요청이 있을 때란, Filter, Servlet 등에서 Reuqest 객체에 getSession() 메시지를 최초로 전송할 때 이다.

Session Tracking을 위해 Cookie를 사용한다고 설정했을 경우, Session을 새로 만들 때 Response 객체의 헤더에 나 이런 SessionID로 만들어놨어라고 헤더를 하나 추가한다. 그 헤더가 Set-Cookie이다. 이 헤더는 애플리케이션에서 쿠키 생성이 필요할 때 호출하는 response.addCookie() 오퍼레이션으로 추가되는 것이다. 즉 addCookie 메서드는 응답 헤더에 Set-Cookie 하나를 추가하는 일을 한다.

이렇게 Session이 만들어지는 타이밍이 가장 먼저 Session을 요구했을 때(getSession())이기 때문에 누가 먼저 Session을 점령하는지가 중요하다. 왜 그런지 Spring-Session 필터를 살펴보며 이해해보자.


Spring-Session Filter

https://velog.io/@yaho1024/spring-security-delegatingFilterProxy

Spring-Session을 사용하려면 Filter를 추가해야 한다. 다시 이야기하지만 Spring-Session은 Filter로 동작한다. 애노테이션을 이용한 설정을 하더라도 자동으로 필터를 추가하는 코드가 들어가 있다.

<filter>
	<filter-name>springSessionRepositoryFilter</filter-name>
	<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
	<filter-name>springSessionRepositoryFilter</filter-name>
	<url-pattern>/*</url-pattern>
	<dispatcher>REQUEST</dispatcher>
	<dispatcher>ERROR</dispatcher>
</filter-mapping>

그런데 필터 클래스가 DelegatingFilterProxy다. 따라서 DelegatingFilterProxyspringSessionRepositoryFilter라는 이름의 Bean을 찾아 Filter로 캐스팅을 한다. 따라서 **모든 DelegatingFilterProxy 호출에 대해 springSessionRepositoryFilter가 호출된다.

springSessionRepositoryFilterRequest 객체의 getSession()을 호출했을 때 Spring-Session에서 관리하는 위치에서 Session 정보를 가져올 수 있도록 Request 객체를 Wrapping하고 Session 객체를 SessionRepository를 이용하여 저장하는 일을 수행한다.

SessionRepository는 객체로 만들어진 Session을 어딘가에 저장하는 기능을 제공하기 위한 인터페이스로써 **어떤 구현체(RDBMS or REDIS 등)**을 이용하느냐에 다르다.

Request 정보는 필터들을 거치고 Servlet에서 얼마든지 변경이 가능하기 때문에 모든 서비스가 수행되고 난 뒤 최종적으로 그 결과를 저장소에 저장해야 한다.


참고