서블릿이 제공하는 HttpSession을 이용하여 로그인을 처리했다.
일단, 세션의 동작 방식을 간단히 복습해보자.


sessionId값을 만들어 세션 저장소에 저장한다.Map 형태key : sessionIdvalue : Member 객체
sessionId를 쿠키에 담아 보낸다.sessionId를 쿠키 저장소에 저장한다.mySessionId==zz0101xx...와 같은 key=value 형태로 저장된다.)
sessionId를 쿠키에서 조회 후 서버에 전달한다.sessionId 쿠키 정보로 세션 저장소를 조회해서 로그인시 세션 정보를 사용한다.지금까지 로그인 요청시 세션이 어떻게 동작하는지 간단히 알아보았다.
중요한 것은 쿠키에는 sessionId가 저장된다는 것이고, 클라이언트가 서버로 로그인 요청을 보낼 때에는 이 값을 서버로 보내고 서버는 세션 저장소에sessionId를 뒤져서 이 값에 맞는 value(여기서는 Member객체)를 클라이언트한테 전송한다.
이를 코드로 간단히 살펴보자.
세션 저장소
private Map<String, Object> sessionStore = new ConcurrentHashMap<>();
세션 생성 로직
public void createSession(Object value, HttpServletResponse response) {
// 세션 id를 생성하고, 값을 세션에 저장
String sessionId = UUID.randomUUID().toString();
sessionStore.put(sessionId, value);
// 쿠키를 생성
Cookie mySessionCookie = new Cookie(SESSION_COOKIE_NAME, sessionId);
response.addCookie(mySessionCookie);
}
자, 위 내용을 상기한 상태에서 기존에 작성했던 로그인 요청을 처리하는 메소드를 보자.
@PostMapping("/login")
public String loginV4(@Validated @ModelAttribute LoginForm form,
BindingResult bindingResult,
@RequestParam(defaultValue = "/") String redirectURL,
HttpServletRequest request) {
if (bindingResult.hasErrors()) {
return "login/loginForm";
}
Member loginMember = loginService.login(form.getLoginId(), form.getPassword());
if (loginMember == null) {
bindingResult.reject("loginFail", "아이디 또는 비밀번호가 잘못되었습니다.");
return "login/loginForm";
}
// 로그인 성공 처리
// 세션이 있으면 있는 세션을 리턴, 없으면 신규 세션을 생성
HttpSession session = request.getSession();
// 세션에 로그인 회원 정보 보관
session.setAttribute(LOGIN_MEMBER, loginMember);
// redirectURL 적용
return "redirect:" + redirectURL;
}
무언가 이상한 점을 발견할 수 있을 것이다. 분명히 HttpSession을 사용하면 session.setAttribute(), session.getAttribute() 를 사용하여 세션 저장소에 값을 넣고 꺼내온다고 학습했다.
기존 세션의 동작 원리를 생각한다면 파라미터로 sessionId와 Member 객체를 넣어줘야 하는데 LOGIN_MEMBER이라는 상수가 들어가있다.
이렇게 상수를 넣어주면 클라이언트끼리 어떻게 구분을 할까? 아니 애초에 저 상수는 왜쓰는거지? 라는 생각도 든다. 두 가지를 살펴보자.
클라이언트끼리 구분할 수 있는 원리
실제로는 세션들을 보관하고 있는 세션 저장소가 하나 더 있다. 이 저장소는 sessionId를 key로, Map을 value로 한다. 즉, sessionId를 가지고 특정 사용자만 사용하는 Map을 가져오게 되는 것이다. 따라서, 같은 key값이 같아도 특정 클라이언트의 요청을 구분할 수 있는 것이다.
참고
sessionId는 tomcat이 생성한다.
참고
예전에 웹 스코프에서 배운 request 스코프 빈의 HTTP request 구분 또한 이러한 원리이다. (request 스코프 빈들을 어떻게 구분하는지....)
상수를 쓰는 이유
Session 로직을 직접 구현한 SessionManager와 HttpSession을 비교해보자.
SessionManager : One Session for Multi User HttpSession : One Session for One User HttpSession은 하나의 클라이언트에 마다 하나의 세션 저장소를 제공한다.
이 세션 저장소는 바로 위에서 언급한 또 다른 세션 저장소이며key가 sessionId이며 value는 Map타입이다.
그리고, 이 Map 타입의 key가 로그인 등의 세션의 사용 용도가 적힌 세션 상수라고 생각하면 된다!
따라서, 위 코드에서 setAttribute뒤의 상수는 다른 로직들과 구분을 위한 용도라 생각하면 된다.