[TIL] #6. 로그인 처리1 - 쿠키, 세션 ②

kiteB·2021년 10월 1일
0

TIL-Spring4

목록 보기
12/17
post-thumbnail

로그인 처리하기 - 세션 동작 방식

앞서 쿠키를 사용했을 때의 문제점을 해결하기 위해서는

  • 중요한 정보는 모두 서버에 저장해야 하며,
  • 클라이언트와 서버는 추정 불가능한 임의의 식별자 값으로 연결해야 한다고 했다.

이렇게 서버에 중요한 정보를 보관하고 연결을 유지하는 방법세션이라고 한다!

세션 동작 방식

1. 로그인

사용자가 loginId, password 정보를 전달하면 서버에서 해당 사용자가 맞는지 확인한다.


2. 세션 생성

  • 추정 불가능한 세션 ID를 생성해야 한다.
  • 생성된 세션 ID와 세션에 보관할 값(memberA)을 서버의 세션 저장소에 보관한다.

3. 세션id를 응답 쿠키로 전달

클라이언트와 서버는 결국 쿠키로 연결되어야 한다.

  • 서버는 클라이언트에 mySessionId라는 이름으로 세션ID만 쿠키에 담아서 전달한다.
  • 클라이언트는 쿠키 저장소에 mySessionId 쿠키를 보관한다.

⭐ 회원과 관련된 정보는 전혀 클라이언트에 전달하지 않는다!
오직 추정 불가능한 세션 ID만 쿠키를 통해 클라이언트에 전달한다.


4. 클라이언트의 세션id 쿠키 전달

  • 클라이언트는 요청 시 항상 mySessionId 쿠키를 전달한다.
  • 서버에서는 클라이언트가 전달한 mySessionId 쿠키 정보로 세션 저장소를 조회해서 로그인 시 보관한 세션 정보를 사용한다.

정리

세션을 사용해서 서버에서 중요한 정보를 관리할 수 있으며, 다음과 같은 보안 문제들을 해결할 수 있게 되었다!

  • 쿠키 값을 변조 가능
    예상 불가능한 복잡한 세션Id를 사용한다.
  • 쿠키에 보관하는 정보는 클라이언트 해킹 시 털릴 가능성이 있다.
    세션Id가 털려도 여기에는 중요한 정보가 없다.
  • 쿠키 탈취 후 사용
    서버에서 세션의 만료시간을 짧게(예: 30분) 유지한다.
    또는 해킹이 의심되는 경우 서버에서 해당 세션을 강제로 제거한다.

로그인 처리하기 - 세션 직접 만들기

세션을 직접 개발해서 적용해보자!

📌 세션 관리

  • 세션 생성
    • sessionId 생성 (임의의 추정 불가능한 랜덤 값)
    • 세션 저장소에 sessionId와 보관할 값 저장
    • sessionId로 응답 쿠키를 생성해서 클라이언트에 전달
  • 세션 조회
    • 클라이언트가 요청한 sessionId 쿠키의 값으로 세션 저장소에 보관한 값 조회
  • 세션 만료
    • 클라이언트가 요청한 sessionId 쿠키의 값으로 세션 저장소에 보관한 sessionId와 값 제거

코드로 확인하기

🔗 전체 코드

SessionManager

  • HashMap은 동시 요청에 안전하지 않아서, 동시 요청에 안전한 ConcurrentHashMap을 사용하였다.

SessionManagerTest

  • HttpServletRequest, HttpServletResponse 객체를 직접 사용할 수 없기 때문에,
    비슷한 역할을 해주는 가짜 MockHttpServletRequest, MockHttpServletResponse를 사용했다.

실행 결과


로그인 처리하기 - 직접 만든 세션 적용

지금까지 개발한 세션 관리 기능을 실제 프로젝트에 적용해보자!

LoginController - loginV2()

@PostMapping("/login")
public String loginV2(@Valid @ModelAttribute LoginForm form, BindingResult bindingResult, HttpServletResponse response) {

   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";
   }

   //로그인 성공 처리
   //세션 관리자를 통해 세션을 생성하고, 회원 데이터 보관
   sessionManager.createSession(loginMember, response);

   return "redirect:/";
}

sessionManager.createSession(loginMember, response);

  • 로그인 성공 시 세션을 등록한다.
  • 세션에 loginMember를 저장해두고, 쿠키도 함께 발행한다.

LoginController - logoutV2()

@PostMapping("/logout")
public String logoutV2(HttpServletRequest request) {
   sessionManager.expire(request);
   return "redirect:/";
}

로그아웃해당 세션의 정보를 제거한다.


실행 결과

→ 로그인할 때마다 다른 Value가 저장되는 것을 확인할 수 있다!

🔗 전체 코드 확인하기


로그인 처리하기 - 서블릿 HTTP 세션 1

서블릿은 세션을 위해 HttpSession이라는 기능을 제공하는데, 지금까지 나온 문제들을 해결해준다!😃

HttpSession 소개

서블릿이 제공하는 HttpSession도 결국 우리가 직접 만든 SessionManager와 같은 방식으로 동작한다.

서블릿을 통해 HttpSession을 생성하면 이름이 JSESSIONID이고, 추정 불가능한 랜덤 값을 가진 쿠키가 생성된다!
Cookie: JSESSIONID=5B78E23B513F50164D6FDD8C97B0AD05


HttpSession 사용하기

✔ 세션 생성과 조회

세션을 생성하려면 request.getSession(true)를 사용하면 된다. 이때 truecreate 옵션 값 중 하나이다.

📌 create 옵션

  • request.getSession(true)
    • 세션이 있으면 기존 세션을 반환한다.
    • 세션이 없으면 새로운 세션을 생성해서 반환한다.
  • request.getSession(false)
    • 세션이 있으면 기존 세션을 반환한다.
    • 세션이 없으면 새로운 세션을 생성하지 않는다. null을 반환한다.

✔ 세션에 로그인 회원 정보 보관

session.setAttribute(SessionConst.LOGIN_MEMBER, loginMember);

세션에 데이터를 보관하는 방법은 request.setAttribute(...)와 비슷하다.
하나의 세션에 여러 값을 저장할 수 있다.


코드 확인하기

🔗 전체 코드 확인하기

실행 결과

로그인할 때마다 JSESSIONID가 변경된다!


로그인 처리하기 - 서블릿 HTTP 세션 2

@SessionAttribute

스프링은 세션을 더 편리하게 사용할 수 있도록 @SessionAttribute를 제공한다.

이미 로그인 된 사용자를 찾을 때는 다음과 같이 사용하면 된다. 이 기능은 세션을 생성하지 않는다.

@SessionAttribute(name = "loginMember", required = false) Member loginMember

TrackingModes

로그인을 처음 시도하면 URL이 다음과 같이 jsessionid를 포함한다.

이것은 웹 브라우저가 쿠키를 지원하지 않을 때, 쿠키 대신 URL을 통해서 세션을 유지하는 방법!
이 방법을 사용하려면 URL에 이 값을 계속 포함해서 전달해야 한다.

만약 URL 전달 방식을 끄고 항상 쿠키를 통해서만 세션을 유지하고 싶으면 application.properties에 다음 코드를 추가해주면 된다.

server.servlet.session.tracking-modes=cookie

이렇게하면 URL에 jsessionid가 노출되지 않는다.


세션 정보와 타임아웃 설정

세션 정보

  • sessionId: 세션Id, JSESSIONID의 값
  • maxInactiveInterval: 세션의 유효 시간
  • creationTime: 세션 생성일시
  • lastAccessedTime: 세션과 연결된 사용자가 최근에 서버에 접근한 시간.
    • 클라이언트에서 서버로 sessionId(JSESSIONID)를 요청한 경우에 갱신됨.
  • isNew: 새로 생성된 세션인지, 아니면 이미 과거에 만들어져서
    클라이언트에서 서버로 sessionId(JSESSIONID)를 요청해서 조회된 세션인지 여부

세션 타임아웃 설정

세션은 사용자가 로그아웃을 직접 호출해서 session.invalidate()가 호출되는 경우에 삭제된다.

그런데 대부분 로그아웃을 선택하지 않고, 그냥 웹 브라우저를 종료한다.
문제는 서버 입장에서는 해당 사용자가 웹 브라우저를 종료한 것인지 아닌지를 인식할 수 없다.
(HTTP가 비연결성(ConnectionLess)이기 때문!)
그래서 서버에서 세션 데이터를 언제 삭제해야 하는지 판단하기가 어렵다😥

이 경우 남아있는 세션을 무한정 보관하면 다음과 같은 문제가 발생할 수 있다.

  • 세션과 관련된 쿠키(JSESSIONID)를 탈취당했을 경우,
    오랜 시간이 지나도 해당 쿠키로 악의적인 요청을 할 수 있다.
  • 세션은 기본적으로 메모리에 생성되는데 메모리의 크기는 유한하기 때문에 사용되지 않은 세션으로 인해 성능 저하가 발생한다!

이런 이유로, 세션은 타임아웃을 설정해야 한다!


세션의 종료 시점

사용자가 서버에 최근에 요청한 시간을 기준으로 30분 정도를
유지
해주자!

이렇게 하면 사용자가 서비스를 사용하고 있으면, 세션의 생존 시간이 30분으로 계속 늘어나게 되어서 30분 마다 로그인해야 하는 번거로움이 사라진다. HttpSession은 이 방식을 사용한다!

세션 타임아웃 설정

스프링 부트에서는 application.properties에 글로벌 설정을 해 줄 수 있다.

session.setMaxInactiveInterval(1800); //1800초
profile
🚧 https://coji.tistory.com/ 🏠

0개의 댓글