스프링 부트 - 로그인1: 쿠키, 세션

SeungTaek·2021년 8월 16일
3
post-thumbnail

본 게시물은 스스로의 공부를 위한 글입니다.
잘못된 내용이 있다면 댓글로 알려주세요!

쿠키, 세션의 기본 지식이 있어야 이해하기 쉽습니다!

📒 쿠키만을 이용한 로그인

📌 쿠키 생성

  • 쿠키 종류
    • 영속 쿠키: 만료 날짜를 입력하면 해당 날짜까지 유지
    • 세션 쿠키: 만료 날짜를 생략하면 브라우저 종료시 까지만 유지

쿠키만을 이용해 로그인 서비스를 만들자

  • Cookie 생성자: Cookie(name, value). 둘다 String이다.

  • 아이디와 패스워드가 일치한지 확인 후 다음과 같은 코드로 쿠키를 만들고, response에 담아준다.

  //쿠키에 시간 정보를 주지 않으면 세션 쿠키(브라우저 종료시 모두 종료)
   Cookie idCookie = new Cookie("memberId", String.valueOf(loginMember.getId()));
   response.addCookie(idCookie);
  • 이 다음부터 보내는 모든 요청에는 쿠키를 항상 담아서 준다.

📌 쿠키 꺼내오기

  • request에 담아져 오는 쿠키를 어떻게 꺼낼 수 있을까?
  • 여러가지 방법이 있지만.. 여기선 스프링이 제공하는 기능을 사용하고자 한다.
@GetMapping("/")
public String homeLogin(@CookieValue(name = "memberId", required = false) Long memberId,
Model model) {
 if (memberId == null) return "home";
 
 //로그인
 Member loginMember = memberRepository.findById(memberId);
 if (loginMember == null) return "home";
 model.addAttribute("member", loginMember);
 return "loginHome";
}
  • @cookieValue를 사용하면 name으로 설정되어 있는 쿠키를 꺼내 올 수 있다!
  • 쿠키값이 없는 회원도 일단 들어오게 하기 위해 required=false를 써줬다.

어? 쿠키 값은 String인데, 어떻게 Long 타입으로 받을 수 있지?

  • 스프링이 알아서 컨버팅해서 Long으로 바꿔주기 때문에 걱정하지 않아도 된다.

📌 로그 아웃

  • 로그아웃 하는 방법은 간단한데, 서버에서 해당 쿠키의 종료 날짜를 0으로 지정하면 된다.
@PostMapping("/logout")
public String logout(HttpServletResponse response) {
 expireCookie(response, "memberId");
 return "redirect:/";
}
private void expireCookie(HttpServletResponse response, String cookieName) {
 Cookie cookie = new Cookie(cookieName, null);
 cookie.setMaxAge(0);
 response.addCookie(cookie);
}

📌 쿠키만을 이용한 로그인의 보안 문제

  1. 쿠키 값은 임의로 변경할 수 있다.
    • 클라이언트가 쿠키를 강제로 변경하면 다른 사용자가 된다.
  2. 쿠키에 보관된 정보는 훔쳐갈 수 있다.
    • 이 정보가 웹 브라우저에도 보관되고, 네트워크 요청마다 계속 클라이언트에서 서버로 전달된다.
  3. 해커가 쿠키를 한번 훔쳐가면 평생 사용할 수 있다.
    • 해커가 쿠키를 훔쳐가서 그 쿠키로 악의적인 요청을 계속 시도할 수 있다.

대안점은?

  1. 쿠키에 예측 불가능한 임의의 토큰을 노출해야 한다.

    • 단, 서버에서는 해석이 가능하게 한다.
  2. 토큰의 만료시간을 짧게(예: 30분) 유지하게 한다.

  3. 해킹이 의심되는 경우에는 서버에서 해당 토큰을 컨트롤할 수 있어야 한다.

쿠키+세션 조합으로 위 보안 문제를 해결하자!


📒 쿠키와 세션 차이

  • 쿠키
    • 저장위치: 클라이언트
    • 사용 자원: 클라이언트의 리소스
    • 속도: 세션보다 빠르다.
    • 보안: 세션보다 취약
    • 저장형식: text
  • 세션
    • 저장위치: 서버
    • 사용 자원: 서버의 리소스
    • 속도: 쿠키보다 느림
    • 보안: 쿠키보다 좋음
    • 저장형식: Object

📒 세션을 이용한 로그인

과정

  1. 사용자가 로그인을 요청을 보낸다.
  2. 서버에서 정보 확인 후 세션ID를 생성한다.
    • 여기서 만든 세션 ID는 추정 불가능한 값을 사용해야한다.(UUID 등)
  3. 만든 세션 ID를 토큰으로 만들어 쿠키로 전달한다.
  4. 그 다음부터 클라이언트는 쿠키에 있는 세션 ID를 서버에 보내면, 서버는 세션을 대조를 해보고 올바른 정보인지 아닌지 확인한다.

📌 서블릿이 제공하는 세션 기능 사용하기

  • 서블릿은 세션을 위해 HttpSession이라는 기능을 제공해준다.

  • 서블릿을 통해 HttpSession을 생성하면 쿠키 이름이 JSESSIONID이고, 값은 추정 불가능한 랜덤 값으로 세팅된다.

    • Cookie: JSESSIONID=5B78E23B513F50164D6FDD8C97B0AD05
  • 아래 코드는 세션을 생성하고, 세션에 정보를 보관하는 기능이다.

//세션이 있으면 있는 세션 반환, 없으면 신규 세션 생성
 HttpSession session = request.getSession();
 //세션에 로그인 회원 정보 보관
 session.setAttribute('loginMember', loginMember);
  • request.getSession()에 인자로 true, false를 줄 수 있다.(디폴트=true)
    • true: 세션이 있으면 기존 세션 반환, 없으면 새로 생성 후 반환
    • false: 세션이 있으면 기존 세션 반환, 없으면 null 반환

😎 내가 헷갈렸던 부분
다음은 내가 공부하면서 헷갈렸던 부분을 다시 정리한 내용이다.

  • 세션은 개인 사용자마다 생성되고, 인식하는 아이디로 JSESSIONID를 사용한다.
  • 그리고 개인 사용자마다 생성된 세션에 여러 정보를 담을 수 있는데, 우리는 그 중 하나로 로그인 회원 정보를 담는 것이다.
  • DB로 예를 들자면 다음과 같다. (but 실제로 세션은 주로 메모리에 저장한다.)
    • 세션은 하나의 테이블이다.
    • 개인 사용자마다 테이블이 1개씩 할당되는데, 테이블 이름은 JSESSIONID로 구분한다.
    • 각 테이블에는 2개의 col로 이루어져 있는데, name, value이다.
    • seesion.setAttribute로 여러개의 row를 넣을 수 있는데, 이때 value값으론 Object를 넣을 수 있다.



  • 이제 기본적인 로그인 기능(로그인, 세션 조회, 로그아웃)을 만들어보자.
  • 아래 코드에서 requestHttpServletRequest request이다. (컨트롤러에서 인자로 받으면 된다.)

🎈 로그인 기능

//아이디, 패스워드를 비교 후 올바른 정보면 아래 코드 실행
//세션이 있으면 있는 세션 반환, 없으면 신규 세션 생성
 HttpSession session = request.getSession();
 //세션에 로그인 회원 정보 보관
 session.setAttribute("loginMember", loginMember);

🎈 세션 조회

 HttpSession session = request.getSession(false);
 if (session == null) return "home";

 Member loginMember = (Member) session.getAttribute("loginMember");
 //세션에 회원 데이터가 없으면 home
 if (loginMember == null) return "home";

Q) 그럼 접속자가 로그인을 했는지, 안했는지 확인을 하려면 계속 위와 같은 코드를 넣어야 하는거야? 너무 귀찮고, 코드도 길어지는데..

A) 아니! 필터나 인터셉터를 이용하면 쉽게 처리할 수 있지. 이건 다음 게시물에서 알아보자.


🎈 로그아웃(세션 지우기)

//세션을 삭제한다.
 HttpSession session = request.getSession(false);
 if (session != null) session.invalidate();

📌 세션 설정

  • 기본적으로 세션은 서버의 메모리에 올라간다.

  • 10만명이 접속해 세션을 만든다면? 그만큼의 정보가 메모리에 올가가게 된다!

  • 이를 그나마 해소하기 위한 방법이 2가지 있다.

🎈 세션 타임아웃 설정

  • 세션을 무한정 보관할 순 없으니, 세션에 유효시간을 설정한다.
  1. 글로벌 설정

    • application.propertiesserver.servlet.session.timeout=60 (초단위)
    • 디폴트는 1800초(30분)이다. 최소 시간은 60초(1분)이고, 분 단위로 설정해야 한다. (60, 120, ...)

  2. 특정 세션 단위로 시간 설정

    • 세션을 만들때 다음과 같은 코드를 넣으면 된다.
    • session.setMaxInactiveInterval(1800);

Q) 아니 그러면 30분이 지나면 무조건 다시 로그인해야 되는거 아니야?

정말 편리하게도.. 최근 세션 접근 시간을 기준으로 계속 타임을 초기화 시켜준다.

그니깐.. 아무것도 안하고 가만히 있으면 다시 로그인해야 하지만,

웹을 돌아다니며 세션을 계속 요청하면, 요청할때마다 타임은 초기화 돼서 다시 로그인할 필요가 없다.

아.. 그니깐 사용자가 얼마나 그 페이지에 머물지를 잘 예측해서 너무 짧지도 않고, 너무 길지도 않게 잘 설정해야 하겠구나!

🎈 세션은 메모리에 올라간다.

  • 세션은 value값으로 Object을 넣을 수 있다.
  • 즉, Object가 크면 그만큼 메모리를 차지한다는 이야기이다. (그래서 세션은 사실 비싼 자원이다..)
  • 따라서 세션에는 최소한의 정보만 남겨두는게 중요하다.

📒 더 나아가서..

📌 스프링이 제공하는 세션기능

  • 스프링이 세션을 더 편리하게 사용할 수 있도록 지원하는 @SessionAttribute를 사용해보자.
  • 이 기능은 이미 로그인 된 사용자를 찾을 때 사용한다.(세션 조회)
@GetMapping("/")
public String homeLoginV3Spring(
 @SessionAttribute(name = "loginMember", required = false)
Member loginMember) {
 //세션에 회원 데이터가 없으면 로그인 화면으로
 if (loginMember == null) return "login";

 //회원 정보가 확인되면 홈으로 이동
 return "home";
}

📌 TrackingModes

  • 로그인을 처음 시도하면 URL이 다음과 같이 jsessionid 를 포함하고 있는 것을 확인할 수 있다.

    http://localhost:8080/;jsessionid=F59911518B921DF62D09F0DF8F83F872

  • 이건 웹 프라우저가 쿠키를 지원하지 않을 상황을 대비하여 쿠키 대신 URL을 통해 세션을 유지하는건데... 사실 실제론 잘 사용하지 않는다.

  • 기능을 꺼버리자

    • application.properties에 다음 코드 추가
    • server.servlet.session.tracking-modes=cookie

📌 세션이 제공하는 정보

  • 세션에서 제공하는 여러가지 정보들을 확인할 수 있다.
//세션 데이터 출력
 session.getAttributeNames().asIterator(). forEachRemaining(name -> log.info("session name={}, value={}",
name, session.getAttribute(name)));
 log.info("sessionId={}", session.getId());
 log.info("세션의 유효시간(초)={}", session.getMaxInactiveInterval());
 log.info("세션 생성일시={}", new Date(session.getCreationTime()));
 log.info("최근 서버에 접근한 시간={}", new Date(session.getLastAccessedTime()));
 log.info("지금 만들어진 세션인가?={}", session.isNew());

위에서 언급했던것처럼 다음 게시물에선 필터와 인터셉터에 대해 알아보겠다.

2편 보러가기: 필터, 인터셉터

인프런의 '스프링 MVC 2편(김영한)'을 스스로 정리한 글입니다.
자세한 내용은 해당 강의를 참고해주세요.

profile
I Think So!

0개의 댓글