이 글에서는 Tomcat이 WAS가 아닌 Servlet Container라는 것을 인지하고, HttpSession이 언제 만들어지고, 어떻게 사용하는지, Session은 어떻게 유지되는 것인지 살펴보도록 하겠습니다.
[Tomcat은 WAS? No! Just Servlet Container!]
JAVAEE는 엔터프라이즈 애플리케이션을 위해 만든 표준이고, 수많은 클래스/인터페이스로 정의되어 있습니다. JAVAEE 6.0의 스펙에는 Web Services, Web Application, Enterprise Application, Management and Security 기술들이 정의되어 있습니다. 그리고 이 스펙을 모두 구현한 모델을 WAS라고 칭합니다.
우리가 흔히 WAS로 알고 있는 tomcat은 JAVAEE 스펙의 일부만 구현하고 있어, WAS가 아니라 Servlet Container 입니다.
tomcat 공식 문서에는 다음과 같은 내용이 명시되어 있습니다.
This is the top-level entry point of the documentation bundle for the Apache Tomcat Servlet/JSP container. Apache Tomcat version 9.0 implements the Servlet 4.0 and JavaServer Pages 2.3 specifications
우리가 웹 애플리케이션을 개발할 때 사용한 HttpSession도 Servlet Container가 생성한 인스턴스입니다. Spring Web MVC를 이용해서 구현한다 할지라도 Spring은 Servlet Container가 만든 HttpSession을 주입할 뿐, HttpSession을 생성하는 주체는 Servlet Container입니다.
(참고로 SpringSession을 이용한다면 Servlet Container가 생성한 구현체가 아니라 SpringSession이 생성한 구현체가 될 것입니다.
SpringSession은 Servlet Container가 아닌 Redis나 Mongodb 같은 데이터베이스에 세션을 저장/관리하기 위해 API를 제공합니다)
[HttpSession은 언제 만들어질까?]
HttpSession을 Servlet Container가 만드는 건 알겠는데, 그럼 언제 만들어질까요?
모든 요청에 대해 Session을 만들까요?
아닙니다. Session을 만드는 것 자체도 부담이 가는 일이기 때문에, Session을 사용할 때만 생성합니다.
Spring Web MVC에서는 HttpSession을 주입해야 할 때, 내부적으로 Servlet Container에게 Session을 달라고 합니다.
httpServletRequest.getSession()
그리고 그때 Servlet Container가 HttpSession을 생성해줍니다. Spring이 Servlet Container에게 Session을 달라고 하는 시점은 HttpSession을 사용하는 방법에 따라 약간씩 다릅니다.
[HttpSession을 사용하는 방법]
1. @Autowired로 주입받는 방식
@RestController
public class LoginController {
@Autowired
private HttpSession session;
@PostMapping("login")
public ResponseEntity<ResponseMessage> login(@RequestBody User user) {
// TODO 일치하는지 확인하는 logic 추가
session.setAttribute("LOGIN_USER", user);
return ResponseEntity.ok(new ResponseMessage(true));
}
}
HttpSession을 주입받는다고 선언하는 것만으로는 Servlet Container에게 Session을 달라고 요청하지 않습니다. setAttribute 혹은 getAttribute 같은 api를 호출하는 시점에 요청/생성합니다.
(참고로 LoginController와 HttpSession의 스코프 차이 때문에 Spring이 HttpSession 인스턴스를 동적 프록시로 생성하여 주입해줍니다. 이것을 Scoped Proxy라고 합니다. LoginController는 singleton scope를 가지며, HttpSession은 session scope를 가집니다.)
2. 메서드에서 주입받는 방식
@RestController
public class LoginController {
@PostMapping("login")
public ResponseEntity<ResponseMessage> login(@RequestBody User user, HttpSession session) {
// TODO 일치하는지 확인하는 logic 추가
session.setAttribute("LOGIN_USER", user);
return ResponseEntity.ok(new ResponseMessage(true));
}
}
메소드에서 매개변수를 통해 주입받는 방식으로 구현한다면, 선언 시에 Servlet Container에게 Session을 달라고 요청합니다. 따라서 위의 예제에서는 login 메서드를 호출하는 즉시 Session이 요청/생성됩니다.
3. @SessionAttribute, @ModelAttribute로 주입받는 방식
@Controller
@SessionAttributes("todos")
public class TodoController{
@GetMapping("/form")
public String showForm(Model model, @ModelAttribute("todos") TodoList todos){
if(!todos.isEmpty()){
model.addAttribute("todo", todos.peekLast());
} else {
model.addAttribute("todo", new TodoItem());
}
return "sessionattributesform";
}
이 방식은 Session에 이미 저장된 데이터를 조회할 때 적합하므로 로그인을 처리하는 로직과는 어울리지 않습니다. 그래서 Baeldung의 예제를 첨부했습니다.
- 2라인 : @SessionAttributes를 컨트롤러에 추가함으로써 세션 단위로 스코프를 지정하도록 명시해줍니다.
- 6라인 : @ModelAttribute을 선언함으로써 Session에서 "todos"라는 키를 가진 값을 조회합니다. 그리고 TodoList로 변환해 todos 파라미터로 주입해줍니다.
이 방식도 선언시에 Servlet Container에게 Session을 달라고 요청하기 때문에 showForm 메소드를 호출할 때 Session이 요청/생성됩니다.
[Session은 어떻게 유지될까?]
위와 같이 구현하면, HttpSession을 필요로 하는 모든 요청에 대해서(Header에 특별한 키가 없다면) 새로운 Session을 발급해 줍니다.
Tomcat의 경우 이 SessionID를 JSESSIONID라는 키의 쿠키로 생성/전달합니다.
따라서 로그인 이후의 플로우처럼 세션을 유지하기 위해서는
- 서버의 로그인 로직에서 로그인이 성공하면 발급받은 SessionId를 클라이언트에게 돌려주고,
- 클라이언트는 이후 요청을 할 때, 서버로부터 받았던 SessionId를 함께 넘겨줍니다.
- Servlet Container에서는 Request에 SessionId가 있으면 Session을 새로 발급하지 않고 전달받은 SessionId와 매핑되는 기존의 Session을 할당해줍니다.
코드와 함께 살펴보겠습니다.
1. 브라우저에서 로그인 요청을 보냅니다.
2. 서버에서는 로그인 정보가 일치하면 세션을 생성해주고, 그 세션에 Key-Value형식으로 로그인 정보를 저장한다.
다음 코드에서는 로그인 정보가 일치하는지 확인하는 로직은 생략했고, 일치한다는 가정하에 작성했습니다.
@RestController
public class LoginController {
@Autowired
private HttpSession session;
@PostMapping("login")
public ResponseEntity<ResponseMessage> login(@RequestBody User user) {
// TODO 일치하는지 확인하는 logic 추가
session.setAttribute("LOGIN_USER", user);
return ResponseEntity.ok(new ResponseMessage(true));
}
}
10라인에서 Session을 사용하므로, 이때 Servlet Container로부터 Session을 발급받습니다.
3. Servlet Container는 현 요청에서 Session을 사용하고 있으면, HttpResponse의 쿠키에 JSESSIONID 키로 SessionID를 넣어줍니다.
저는 postman에서 테스트했고, 아래 그림은 postman에서 요청을 보냈을 때의 응답 정보입니다.
Set-Cookies에 JSESSIONID가 추가되어있음을 확인할 수 있습니다.
HttpSession은 Server 내부 자원(메모리, 디스크 등)을 사용하므로 일정 시간 동안 사용되지 않으면 자원 해제가 되며 이를 Session 만료(expiration)라고 합니다. HttpSession 만료 시간 설정은 servlet-session-timeout을 참고하시기 바랍니다.
Session이 만료된 경우, Session을 생성하기 위해 다시 위 절차를 따릅니다.
'Auth (Authentication & Authorization)' 카테고리의 다른 글
OAuth? (0) | 2020.10.12 |
---|---|
Springboot-Angular-JWT 기반 Auth 구현 - refresh token 추가 (0) | 2020.10.12 |
Springboot-Angular-JWT 기반 Auth 구현 - 로그인 흐름 (0) | 2020.10.12 |
JWT란? (0) | 2020.10.12 |
Spring-security authorizeRequest 동적으로 설정하기 (0) | 2020.10.12 |
댓글