- 서버에서 해당 쿠키의 종료 날짜를 0으로 지정한다.
- 로그아웃도 응답 쿠키를 생성하는데 Response Headers를 확인해 보면 `Set-Cookie: Max-Age=0;` 를 확인할 수 있다.
- 또한 Application -> Storage -> Cookies 에 가봐도 쿠키가 삭제된 것을 확인할 수 있다.
쿠키와 보안 문제
위와 같은 구현방식은 엄청 심각한 보안 문제가 있다.
대표적인 문제들은 아래와 같다.
쿠키 값은 임의로 변경할 수 있다.
쿠키에 보관된 정보는 훔쳐갈 수 있다.
해커가 쿠키를 한번 훔쳐가면 평생 사용할 수 있다.
해결 방안
쿠키에 중요한 값을 노출하지 않고, 사용자 별로 예측 불가능한 임의의 토큰(랜덤 값)을 노출한다.
서버에서 토큰과 사용자 id를 매핑해서 인식하고 서버에서 토큰을 관리한다.
큰은 해커가 임의의 값을 넣어도 찾을 수 없도록 예상 불가능 해야 한다.
해커가 토큰을 털어가도 시간이 지나면 사용할 수 없도록 서버에서 해당 토큰의 만료시간을 짧게 유지한다.
서버는 클라이언트에 mySessionId 라는 이름으로 세션ID 만 쿠키에 담아서 전달한다.
클라이언트는 쿠키 저장소에 mySessionId 쿠키를 보관한다.
클라이언트는 요청시 항상 mySessionId 쿠키를 전달한다.
서버에서는 클라이언트가 전달한 mySessionId 쿠키 정보로 세션 저장소를 조회해서 로그인시 보관한 세션 정보를 사용한다.
이런식으로 구현하면 쿠키만 사용했을 때 발생할 수 있는 보안 문제를 해결할 수 있다.
하지만 세션에는 최소한의 데이터만 보관해야한다.
객체를 넣지말고, 아이디만 넣는식으로 메모리 관리는 필수이다.
HttpSession 을 통한 로그인 처리
서블릿은 세션을 위해 HttpSession 이라는 기능을 제공해 준다.
로그인 기능 구현
@PostMapping("/login")public String loginV3(@Valid@ModelAttribute LoginForm form, BindingResult bindingResult, HttpServletRequest request) {
if (bindingResult.hasErrors()) {
return"login/loginForm";
}
Member loginMember = loginService.login(form.getLoginId(), form.getPassword());
log.info("login? {}", loginMember);
if (loginMember == null) {
bindingResult.reject("loginFail", "아이디 또는 비밀번호가 맞지 않습니다.");
return"login/loginForm";
}
//로그인 성공 처리//세션이 있으면 있는 세션 반환, 없으면 신규 세션 생성
HttpSession session = request.getSession(); //세션에 로그인 회원 정보 보관
session.setAttribute(SessionConst.LOGIN_MEMBER, loginMember);
return"redirect:/";
}
- request.getSession(true)
- 세션이 있으면 기존 세션을 반환한다.
- 세션이 없으면 새로운 세션을 생성해서 반환한다.
- request.getSession(false)
- 세션이 있으면 기존 세션을 반환한다.
- 세션이 없으면 새로운 세션을 생성하지 않는다. -> null 을 반환한다.
- session.setAttribute(SessionConst.LOGIN_MEMBER, loginMember); 을 통해서 세션 정보를 저장한다.
@GetMapping("/")public String homeLoginV3(HttpServletRequest request, Model model) {
//세션이 없으면 home
HttpSession session = request.getSession(false);
if (session == null) {
return"home";
}
Member loginMember = (Member)
session.getAttribute(SessionConst.LOGIN_MEMBER);
//세션에 회원 데이터가 없으면 home if (loginMember == null) {
return"home";
}
//세션이 유지되면 로그인으로 이동
model.addAttribute("member", loginMember);
return"loginHome";
}
request.getSession(false) -> true 로 설정할 경우, 로그인 하지 않을 사용자도 의미없는 세션이 만들어진다.
session.getAttribute(SessionConst.LOGIN_MEMBER) -> 로그인 시점에 세션에 보관한 회원 객체를 찾는다.
위와 같은 기능을 스프링에서 제공하는 @SessionAttribute를 사용하면 더 간단하게 구현이 가능하다.
@GetMapping("/")public String homeLoginV3Spring( @SessionAttribute(name = SessionConst.LOGIN_MEMBER, required = false) Member loginMember,Model model) {
//세션에 회원 데이터가 없으면 home if (loginMember == null) {
return"home";
}
//세션이 유지되면 로그인으로 이동
model.addAttribute("member", loginMember);
return"loginHome";
}