Loading the catalog…
Loading the catalog…
이번 프로젝트에서는 세션 기반 인증을 사용해보려고 했다. 그런데 HTTP Session과 Spring Session의 역할이 어디까지 겹치고 무엇이 다른지 너무너무 이해가 되지 않았다. 이 글에서는 Servlet의 HttpSession , Spring Session, Spring Session의 동작 원리를 정리해보려고 한다~ HTTP Session HTTP는 stateless한 프로토콜이기 때문에 각 요청을 독립적으로 처리한다. 따라서 서버는 별도의 방법이 없다면 이전 요청을 보낸 클라이언트와 현재 요청을 보낸 클라이언트가 동일한 사용자인지 알 수 없다. 하지만 로그인 상태나 장바구니와 같이 여러 요청에 걸쳐 유지되어야 하는 정보들이 있다. Servlet에서는 이러한 상태를 관리할 수 있도록 HttpSession 인터페이스를 제공한다. 아래의 내용은 Jakarta 공식 문서 를 참고했다! 세션 추적 메커니즘 들어온 HTTP 요청이 어떤 Session에 해당하는지 알기 위해서는 세션을 추적해야 한다. 세션 추적 방식에는 Cookie, SSL Sessions, URL Rewriting이 있다. Cookie 쿠키를 통해서 세션을 추적하는 것이 가장 자주 쓰이는 방식이다. 그렇기에 모든 Servlet container가 지원하도록 요구된다고 한다. 컨테이너는 클라이언트에게 Cookie를 설정하여 보내고, 클라이언트는 뒤따르는 요청에 Cookie를 붙여 보낸다. 이때 세션과 관련된 Cookie의 표준 이름은 JSESSIONID 이다.(Servlet Container에 따라 설정을 통해 session 관련 Cookie의 이름을 커스텀할 수 있을 수도 있다.) 또한, 세션 식별자는 인증 상태와 직접 연결될 수 있기 때문에 Session Cookie에는 일반적으로 HttpOnly , Secure , SameSite 등의 보안 속성을 함께 고려해야 한다. SSL Sessions SSL에는 클라이언트로부터 들어오는 여러 요청이 동일한 세션의 일부임을 명확하게 식별할 수 있는 메커니즘이 내장되어 있다. Servlet Container는 이 데이터를 활용하여 손쉽게 세션을 정의할 수 있다. URL rewriting Cookie를 사용할 수 없는 환경에서 세션을 추적하기 위해서는 URL rewriting을 사용할 수 있다. 그런데 URL rewriting을 통해 세션을 추적하는 경우에는 로그, 즐겨찾기, referer header, 캐시된 HTML 및 URL 표시줄에 세션 식별자가 노출되기 때문에 Cookie를 사용할 수 있는 환경이라면 URL rewriting을 사용해서는 안 된다. 클라이언트가 Cookie를 허용하지 않는 경우 등... 정말로 Cookie를 사용할 수 없는 상황에서만 URL을 통해 세션을 추적하도록 하자! 세션의 Attribute HttpSession에는 key-value 형태로 데이터를 저장할 수 있다.(Attribute를 Binding 한다고 함) 예를 들어, 로그인한 사용자의 식별자를 Session에 저장하면 이후 요청에서도 같은 session을 통해 해당 사용자의 정보를 확인할 수 있다. 세션에 바인딩된 데이터는 같은 ServletContext에 속하며 같은 세션에 속하는 요청을 처리하는 다른 모든 Servlet에서도 사용 가능하다. 세션 Timeout HTTP 프로토콜에는 클라이언트가 더이상 활동하지 않는다는 사실을 명시적으로 알려주는 종료 신호가 없다. 따라서 클라이언트가 더이상 활동하지 않는다고 판단하는 데 사용할 수 있는 유일한 메커니즘은 Timeout이다. 세션의 기본 Timeout 시간은 Servlet Container가 정의하며, 이 값은 ServletContext.getSessionTimeout() 또는 HttpSession.getMaxInactiveInterval() 을 통해 확인할 수 있다. 정의상 Timeout 값이 0 이하로 설정되면 해당 세션은 절대 자동으로 만료되지 않는다. 세션 invalidation은 해당 세션을 사용 중인 모든 servlet이 service 메서드를 종료하기 전까지 완전히 효력이 발생하지 않으며, 세션 invalidation이 시작된 이후에 새로 들어오는 요청은 해당 세션을 볼 수 없어야 한다. Spring Session 아래의 내용은 Spring Session 공식 문서 를 참고했다! Spring Session은 세션 정보를 관리하기 위한 API와 구현체를 제공하며, 특정 애플리케이션 컨테이너에 종속되지 않으면서 clustered sessions를 쉽게 구현할 수 있도록 지원한다. 📝 clustered session은 서버가 여러 대인 환경에서 세션을 여러 서버가 공유해서 쓸 수 있도록 하는 것이다. 동일한 사용자의 요청이 처음에는 A 서버로 가고, 다음에는 B 서버로 간다면 B 서버에서는 A 서버에서 발급한 세션에 대해 모르는 상황이 발생할 수 있다. 이런 상황을 방지하기 위해 여러 서버가 세션 정보를 공유하는 것이다. 📝 특정 애플리케이션 컨테이너에 종속되지 않는다는 것은 Tomcat 같은 특정 환경에 의존하지 않는다는 뜻이다. 세션 저장과 조회 방식을 애플리케이션 컨테이너의 구현으로부터 추상화한다고 볼 수 있다. 따라서 Tomcat에서 Jetty 등으로 컨테이너를 변경하더라도 세션과 관련된 구현을 크게 변경하지 않아도 된다는 장점이 있다. 정말 순수하고 간단한 세션만 구현하는 것이 목표라면(단일 서버에서 Servlet Container가 제공하는 기본 HttpSession 만으로 충분하다면) 굳이 Spring Session까지는 사용하지 않아도 된다. Spring Session은 여러 서버가 세션을 공유해야 하거나, 세션을 Redis 같은 외부 저장소에서 관리할 때 유용하다. Spring Session의 존재 이유 사용자가 웹 애플리케이션과 상호작용하면, 서버는 사용자의 활동을 추적하기 위해 세션을 생성한다. 이 세션에는 사용자 설정, 로그인 상태, 장바구니 내용과 같은 정보가 저장될 수 있다. 하지만 세션은 일반적으로 서버의 메모리에 저장되기 때문에, 분산 환경에서는 문제가 될 수 있다. 왼쪽 그림에서는 각 Spring 애플리케이션이 자기 자신만 접근할 수 있는 위치, 보통은 각 서버의 메모리에 세션을 저장하고 있다. 하지만 이런 방식은 분산 환경에서 문제가 될 수 있다. Spring App #2가 Session #3 을 가진 요청을 받는 상황을 생각해보면, App #2는 해당 세션 데이터를 읽을 수 없다. Session #3 의 데이터는 Spring App #1의 메모리에 저장되어 있기 때문이다. 하지만 오른쪽 그림과 같은 구성을 사용하면, 세션 저장소에 접근할 수 있는 모든 애플리케이션이 해당 세션을 사용할 수 있게 된다. Spring Session은 이를 위해 애플리케이션과 세션 관리 사이에 추상화 계층을 제공하여 세션 데이터를 관계형 데이터베이스, NoSQL 데이터베이스 등 다양한 영속 저장소에 저장할 수 있게 해준다. 따라서 어떤 저장소를 사용하든 동일한 API로 세션을 관리할 수 있다. 전반적으로 Spring Session은 웹 애플리케이션에서 사용자 세션 관리를 단순화해주며, 개발자가 애플리케이션의 핵심 기능 개발에 더 집중할 수 있게 해준다. Spring Session은 다음과 같은 목적을 위해 주로 사용된다: 분산 웹 애플리케이션: 웹 애플리케이션이 여러 서버에 분산되어 있다면, 사용자 세션을 관리하는 일이 어려워질 수 있다. Spring Session은 세션 데이터를 공유 데이터베이스나 Redis에 저장함으로써, 모든 서버가 세션 데이터에 접근하고 수정할 수 있도록 해준다. 세션 확장성: 많은 사용자가 동시에 접속하는 대규모 웹 애플리케이션에서는, 세션을 서버 메모리에 저장하는 방식이 확장성 문제를 일으킬 수 있다. Spring Session은 세션 데이터를 외부 영속 저장소에 저장할 수 있게 해주므로, 확장성을 높이고 메모리 부족 오류의 위험을 줄일 수 있다. 세션 백업 및 복구: 세션 데이터를 외부 영속 저장소에 저장하면 서버 장애나 중단이 발생했을 때 세션 데이터를 백업하고 복구할 수 있는 수단도 제공할 수 있다. Spring Session + HTTP Session 이 섹션에서는 Spring Session이 HttpSession과 어떻게 통합되는지 설명한다. 내부적으로 어떤 일이 일어나는지에 대한 설명이기 때문에 개발자가 직접 설정해야 하는 것들에 대한 설명은 아니다! Spring Session은 먼저 HttpSession의 사용자 지정 구현을 반환하는 HttpServletRequest 를 아래와 같이 생성한다. public class SessionRepositoryRequestWrapper extends HttpServletRequestWrapper { public SessionRepositoryRequestWrapper(HttpServletRequest original) { super(original); } public HttpSession getSession() { return getSession(true); } public HttpSession getSession(boolean createNew) { // create an HttpSession implementation from Spring Session } // ... other methods delegate to the original HttpServletRequest ... } HttpServletRequestWrapper 를 상속하여 Session과 관련된 메서드를 오버라이드 했기 때문에, Session과 관련된 기능들은 모두 커스텀된 Spring Session 버전으로 실행되고 오버라이드 하지 않은 나머지 기능들은 기존 HttpServletRequest 구현체로 처리를 위임한다. 또한 아래 코드와 같이 SessionRepositoryFilter 라는 Servlet Filter를 사용하여 요청으로 들어온 HttpServletRequest 를 SessionRepositoryRequestWrapper 로 교체한다. 그럼 Filter Chain에 원본 request를 그대로 전달하는 것이 아니라, 위에서 정의한 wrapped request( SessionRepositoryRequestWrapper )를 전달함으로써 해당 Filter 이후에 호출되는 모든 요소가 커스텀 HttpSession 구현체를 사용하도록 보장할 수 있다. (그렇기 때문에 Spring Session의 SessionRepositoryFilter는 HttpSession과 상호작용하는 모든 요소보다 앞서 배치해야 한다!) public class SessionRepositoryFilter implements Filter { public doFilter(ServletRequest request, ServletResponse response, FilterChain chain) { HttpServletRequest httpRequest = (HttpServletRequest) request; SessionRepositoryRequestWrapper customRequest = new SessionRepositoryRequestWrapper(httpRequest); chain.doFilter(customRequest, response, chain); } // ... } 정리하면... Client │ SESSION ID ▼ SessionRepositoryFilter │ ▼ SessionRepositoryRequestWrapper │ getSession() ▼ Spring Session HttpSession │ ▼ SessionRepository │ ├── Redis └── JDBC 원래 HTTP 요청 객체는 HttpServletRequest 인터페이스를 따르는 형태로 Servlet Container가 직접 생성한다. Spring Session은 이 요청 객체를 직접 바꾸는 대신, HttpServletRequestWrapper 를 상속한 SessionRepositoryRequestWrapper 로 원본 요청 객체를 감싼다. 그리고 SessionRepositoryFilter 를 앞단에 두어 이후의 Filter, Servlet, Controller에는 원본 HttpServletRequest 가 아니라 커스텀한 Wrapper 객체가 전달되도록 한다. SessionRepositoryRequestWrapper 는 getSession() 처럼 세션과 관련된 메서드를 직접 오버라이드하여 Spring Session이 관리하는 HttpSession 을 반환하도록 한다. 반면 세션과 관련 없는 메서드는 별도로 다시 구현하지 않고, 부모 클래스인 HttpServletRequestWrapper 의 구현을 사용한다. HttpServletRequestWrapper 는 내부에 보관하고 있는 원본 HttpServletRequest 에 실제 처리를 위임하므로, 결과적으로 세션 관련 동작만 Spring Session 방식으로 바뀌고 나머지 HTTP 요청 처리 방식은 기존 Servlet Container의 구현을 그대로 사용할 수 있다. 마무리 와 드디어 Spring Session이 왜 존재하는지를 알게 되었다... 개발을 공부하다보면 이미 있는 구현을 감싸서 새로운 기술을 구현하고... 추상화를 해서 뭘 어떻게 하고... 이런 게 너무 많아서 기술들을 이해하는 게 쉽지 않은 것 같다. 내가 이해가 안 되는 부분을 시원하게 긁어주는 글은 잘 없었는데 공식 문서 덕분에 시원하게 해결할 수 있었다. 따봉 공식 문서야 고마워~
What RADAR observed and classified to build this opportunity. It is what the source published, not a verification that the offer is still active.
[Spring] HTTP Session과 Spring Session. 이번 프로젝트에서는 세션 기반 인증을 사용해보려고 했다. 그런데 HTTP Session과 Spring Session의 역할이 어디까지 겹치고 무엇이 다른지 너무너무 이해가 되지 않았다. 이 글에서는 Servlet의 HttpSession , Spring Session, Spring Session의 동작 원리를 정리해보려고 한다~ HTTP Session HTTP는 stateless한 프로토콜이기 때문에 각 요청을 독립적으로 처리한다. 따라서 서버는 별도의 방법이 없다면 이전 요청을 보낸 클라이언트와 현재 요청을 보낸 클라이언트가 동일한 사용자인지 알 수 없다. 하지만 로그인 상태나 장바구니와 같이 여러 요청에 걸쳐 유지되어야 하는 정보들이…
Open source