클라이언트가 웹페이지에 접근하려고 한다.
웹페이지는 회원만 접근 가능하므로 서버는 클라이언트 요청에 로그인 페이지로 응답한다. 클라이언트는 ID와 비밀번호를 입력하고 서버로 전송한다. 서버는 ID와 비밀번호를 토대로 회원여부를 검사하고 회원이 맞다면 웹페이지 접근을 허용한다.
이것이 '로그인(Log-In)'이다.
그런데 한 가지 문제가 있다.
클라이언트와 서버는 HTTP 프로토콜로 통신한다. HTTP는 상태 비저장(Stateless) 프로토콜이다. 전화 연결처럼 한번 연결되면 상태가 유지되는 것이 아니다. 그저 클라이언트에서 서버로 데이터만 전송되고 모든 연결상태는 사라진다. 그러므로 로그인으로 특정 페이지에 접근해도 다른 페이지로 이동하려면 또 다시 로그인을 해야한다.
그래서 HTTP의 Stateless한 성질을 보조할만한 수단이 필요했는데, 그것이 쿠키와 세션이다. 이번 포스팅에서는 쿠키에 대해서 다루어보겠다.
쿠키(Cookie)
쿠키는 서버가 로그인에 성공한 클라이언트에게 제공하는 '입장권'같은 개념이다. 로그인에 성공한 클라이언트가 다른 페이지를 요청할 때 쿠키를 담아 전송하는 것이다. 서버는 쿠키를 확인하고 클라이언트의 요청을 인가한다.
그럼 그 과정을 코드로 확인해보자.
로그인 화면에서 ID와 비밀번호를 입력하고 로그인 버튼을 클릭하면 Spring Server에서는 Controller의 login 메소드가 호출된다.
LoginController
@PostMapping("/login")
public String login(@Valid @ModelAttribute LoginForm form, HttpServletResponse response){
// 회원여부 검사 비즈니스 로직
Member loginMember = loginService.login(form.getLoginId(), form.getPassword());
// [ 회원이 아닌 경우 ]
if(loginMember == null){
bindingResult.reject("loginFail","아이디 또는 비밀번호가 맞지 않습니다. ");
return "login/loginForm";
}
// [ 회원인 경우 ]
// 응답객체에 쿠키를 담아 클라이언트에게 전달
// 쿠키명 + 쿠키데이터
Cookie idCookie = new Cookie("memberId", String.valueOf(loginMember.getId()));
response.addCookie(idCookie);
// 홈페이지로 리다이렉트
return "redirect:/";
}
회원여부 검사하는 비즈니스 로직을 실행하고 회원이 아닌 경우에는 에러메시지를, 회원인 경우에는 응답(Response)객체에 쿠키를 담아 클라이언트로 전달한다. 클라이언트는 쿠키를 로컬환경에 저장하고 서버에 요청할 때마다 입장권처럼 사용한다. 로그인에 성공하면 홈페이지 경로로 리다이렉트 된다.
그럼 홈페이지 Controller 코드를 보자.
HomeController
@GetMapping("/")
public String homeLogin(@CookieValue(name="memberId", required = false) Long memberId, Model model){
// 1. 쿠키에 인가 데이터가 없는 경우
if(memberId == null){
return "logoutHome"; // 로그아웃된 홈페이지로 이동
}
// 2. 쿠키에 인가 데이터가 있는 경우
// 2-1) 인가데이터와 매핑되는 회원정보가 없는 경우
Member loginMember = memberRepository.findById(memberId);
if(loginMember == null){
return "logoutHome"; // 로그아웃된 홈페이지로 이동
}
// 2-2) 인가데이터와 매핑되는 회원정보가 있는 경우
model.addAttribute("member",loginMember);
return "loginHome"; // 로그인된 홈페이지로 이동
}
HomeController의 homeLogin 메소드가 호출되면, Request 객체에서 쿠키데이터부터 검사한다. @CookieValue는 Request에 포함된 쿠키데이터를 가져오는 어노테이션이다. name 속성에 설정된 쿠키명과 일치하는 쿠키데이터를 파라미터로 받는다. 쿠키데이터가 없거나 있어도 회원정보와 매핑되지 않으면 로그아웃된 홈페이지로 이동된다. 매핑되는 회원정보가 있다면 서버는 클라이언트에게 로그인된 홈페이지를 응답한다.
정리하면, 클라이언트가 서버에 회원정보를 전송하면 서버는 회원여부를 검사한다. 회원이면 쿠키를 생성하여 응답 객체에 담아 클라이언트에 전달한다. 클라이언트는 리다이렉트된 홈페이지로 이동하고 전달받은 쿠키로 홈페이지 접근에 성공한다. 홈페이지를 다시 접근해도 쿠키가 있기에 로그인을 다시 하지 않아도 된다. 이렇듯, 쿠키는 로그인 상태를 유지하도록 해주는 필수요소 중 하나이다.
그러나 보안상에 큰 문제가 있다.
쿠키는 파일형태로 클라이언트 로컬PC에 저장된다. 만약 누군가가 쿠키를 탈취한다면 어떻게 될까? ID와 비밀번호를 몰라도 로그인이 가능해지는 것이다. 탈취가 안되더라도 쿠키데이터가 예측가능하다면 누구나 쉽게 로그인 할 수 있다.
위 코드대로 생성한 쿠키정보이다. 쿠키명은 "memberId"이고 쿠키데이터는 1이다. 1은 누구나 쉽게 예측할 수 있다. 1이 있다면 2가 있고 2가 있다면 3이 있다. 1을 3으로 바꾸고 서버로 전송하면 3을 쿠키로 가지는 회원정보에 접근할 수 있게 된다.
그러므로
쿠키데이터는 아무도 예측할 수 없도록 랜덤해야 하고 탈취가 되더라도 사용하지 못하도록 특정시간이 만료되어 쿠키효력을 없애야 한다. 쿠키의 이런 문제를 해결하고자, 세션(Session)이 등장하였다.
다음 포스팅에서는 세션에 대해서 다루어보겠다.
참고자료
'SPRING > Spring MVC' 카테고리의 다른 글
[SpringMVC] 서블릿 세션(Servlet Session) 이용하기 (0) | 2023.08.30 |
---|---|
[SpringMVC] Session( 세션 )이란? (0) | 2023.08.30 |
[SpringMVC] 검증(Validation)(8) - Bean Validation ( 폼객체 ) (0) | 2023.08.30 |
[SpringMVC] 검증(Validation)(7) - Bean Validation ( groups ) (0) | 2023.08.30 |
[SpringMVC] 검증(Validation)(6) - Bean Validation ( 에러 코드 ) (0) | 2023.08.29 |