서블릿이 제공하는 HttpSession을 이용하여 로그인을 처리했다.
일단, 세션의 동작 방식을 간단히 복습해보자.
sessionId
값을 만들어 세션 저장소에 저장한다.Map
형태key
: sessionId
value
: 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
뒤의 상수는 다른 로직들과 구분을 위한 용도라 생각하면 된다.