본문 바로가기

Back-End/Spring-MVC

Cookie와 Session

쿠키

쿠키가 생긴 이유가 바로 Stateless 때문이다. 왜냐하면 무상태이기 때문에 회원이 로그인을 하고 나서 페이지에 접근해도 연결이 끊어져서 서버는 로그인을 한 사람의 정보를 알지못한다!

  • HTTP는 무상태(Stateless) 프로토콜이다.
  • 클라이언트와 서버가 요청과 응답을 주고 받으면 연결이 끊어진다.
  • 클라이언트가 다시 요청하면 서버는 이전 요청을 기억하지 못한다.
  • 클라이언트와 서버는 서로 상태를 유지하지 않는다.

그래서 그럼 모든 요청에 정보를 넘기면 되지 않냐? 하는데 이러면 해결될 수는 있지만 사이드 이팩트가 발생한다.

  1. 모든 요청에 사용자 정보가 포함되도록 개발 해야한다. - 개발자가 너무 힘듦
  2. 브라우저를 완전히 종료하고 다시 열면 어떻게 되는건가?

이것때문에 쿠키가 탄생했는데

웹 브라우저에서 유저가 POST /login HTTP/1.1  user = "정보"로 로그인을 하면
-> 서버는 HTTP/1.1 200 OK  Set-Cookie: user = "정보"로 응답을해서 웹 브라우저안에 있는 쿠키 저장소에 저장한다.
그래서 웹 브라우저는 지정한 URL에 대해서는 쿠키 저장소에 있는 값을 알아서 담아서 보내준다.

일단 쿠키에 대해서 좀 더 알아보자.
1. Cookie Header : 서버에서는 HTTP 응답 헤더에 Set-Cookie 속성을 사용해 생성하고 설정할 수 있다.

  • set-Cookie : 서버에서 클라이언트로 쿠키 전달(응답)
  • Cookie : 클라이언트가 서버에서 받은 쿠키를 저장하고, HTTP 요청시 서버로 전달
@PostMapping("/login")
public ResponseEntity<LoginResponseDto> loginUser(@RequestBody LoginRequestDto loginRequestDto,
                                                 HttpServletResponse response,
                                                 HttpServletRequest request) {
    // 로그인 유저 조회
    LoginResponseDto responseDto = userService.login(loginRequestDto.getEmail(), loginRequestDto.getPassword());

    if (responseDto.getId() == null) {
        // 로그인 실패 예외처리
        throw new NoSuchElementException("다시 로그인 해주세요");
    }

	// 로그인 성공 처리
    // 쿠키 생성
    Cookie cookie = new Cookie("userId", String.valueOf(responseDto.getId()));
    
    // 쿠키 값 세팅
    // Respone Set-Cookie : userId = 1 형태로 전달
    response.addCookie(cookie);

    return new ResponseEntity<>(responseDto, HttpStatus.OK);
}
  • 로그인 기능
    • 로그인에 성공하면 Cookie를 생성하고 HttpServletResponse 객체에 담는다.
      • Cookie 이름(Key)userId, 값(Value)유저의 고유 식별자 id 값을 담아둔다.
      • Set-Cookie : userId = 1

근데 쿠키 자체가 클라이언트(브라우저)에 데이터를 저장하는 방식이다. 그래서 서버에서 생성된 쿠키는 HTTP 응답을 통해 클라이언트에게 전달되게 되는데 그 과정에서 웹 브라우저에 있는 쿠키 저장소에 유저들의 민감한 정보(예를 들어 위의 로그인 기능에 userId 즉, 고유 식별자 값)도 다 같이 전달되는 것이다. 그래서 우리는 세션이라는 것을 추가해줄 것이다.


세션

Cookie를 사용한 방식은 여러가지 보안 문제가 있다. 결국 보안 문제를 해결하려면 중요한 정보는 모두 서버에 저장해야한다.
Client와 서버는 예측이 불가능한 임의의 값으로 연결해야 한다. 서버에서 중요한 정보를 보관하며 로그인 연결을 유지하는 방법을 Session이라고 함. Cookie는 중요한 정보를 Client 측에서 보관하고 있는 것이다.

세션의 생성 순서

  1. 로그인에 성공하면 Server에서 임의로 만든 Session ID를 생성한다.
    • Session ID는 예측이 불가능해야 한다.
    • UUID(Universal Unique ID)와 같은 값을 활용한다.
  2. 생성된 Session ID와 조회한 User 인스턴스를 서버의 Session 저장소에 저장한다.

로그인 예제를 통한 동작 순서

@PostMapping("/login")
public ResponseEntity<LoginResponseDto> loginUser(@RequestBody LoginRequestDto loginRequestDto,
                                                 HttpServletResponse response,
                                                 HttpServletRequest request) {
    //로그인 유저 조회
    LoginResponseDto responseDto = userService.login(loginRequestDto.getEmail(), loginRequestDto.getPassword());

    if (responseDto.getId() == null) {
        // 로그인 실패 예외처리
        throw new NoSuchElementException("다시 로그인 해주세요");
    }

    //로그인 성공처리
    //세션 생성 및 사용자 정보 저장
    HttpSession session = request.getSession();

    //회원 정보 조회
    UserResponseDto loginUser = userService.findByIdUser(responseDto.getId());

    // Session 에 로그인 회원 정보를 저장
    session.setAttribute(SessionConst.LOGIN_USER, loginUser);

    //세션 ID를 쿠키에 저장
    Cookie cookie = new Cookie("session_id", session.getId());
    //쿠키 값 세팅
    // Response Set-Cookie : session_id : UUID 형태로 전달
    response.addCookie(cookie);

    return new ResponseEntity<>(responseDto, HttpStatus.OK);
}
  1. 로그인 요청이 오면 유저 정보가 있는지 확인. 없으면 예외처리
  2. 로그인이 성공 되었으면 세션을 생성
    • request.getSession()
      • request.getSession(true)
        • 디폴트 설정
        • Request 객체 내에 Session이 존재한다면 기존 Session을 반환
        • Request 객체 내에 Session이 없으면 새로운 Session을 생성해서 반환
      • request.getSession(false)
        • Request 객체 내에 Session이 존재한다면 Session을 반환
        • Request 객체 내에 Session이 없으면 null 반환
  3. Session에 로그인 회원 정보를 저장한다.
  4. 그리고 클라이언트에서 관리하는 쿠키에는 Session이 만든 세션ID를 넘겨주고 저장한다.
  5. 클라이언트는 이후 요청에서 해당 쿠키를 자동으로 서버에 전달한다.
  6. 서버는 쿠키에 포함된 세션 ID를 사용해 세션 데이터를 조회함.

Q : SetAttribute에 들어가는 인자 값들은 어떤 식으로 관리 되는가?
A : setAttribute에 들어가는 인자 값들은 세션 내부의 키-값 형태로 저장된다.

키(Key) : 세션 내에서 데이터를 구분하는 고유 식별자. 주로 문자열을 설정하며, 상수로 관리하는 것이 일반적임.
값(Value) : 키와 매핑되는 데이터 객체. 어떤 객체든 저장 가능(String, DTO, LIST 등) 근데 Dto를 저장하는 것이 권장됨.

  • 저장 : session.setAttribute(SessionConst.LOGIN_USER, userDto);
  • 조회 : UserResponseDto userDto = (UserResponseDto) session.getAttribute(SessionConst.LOGIN_USER);
  • 삭제 : session.removeAttribute(SessionConst.LOGIN_USER);

Q : 근데 키는 세션 내에서 데이터를 구분하는 고유 식별자인데 상수로 관리되면 식별자인데 다 똑같은거 아닌가?
A : 세션 내부에서는 동일한 키를 재사용하면 값이 덮어써지므로, 키 자체는 고유해야함. 그리고 상수 이름이 동일하더라도, 다른 세션에서는 독립적으로 관리됨.
예) 여러 사용자의 세션

SessionA {
    "LOGIN_USER": UserA
    "CART_ITEMS": [Item1, Item2]
}

SessionB {
    "LOGIN_USER": UserB
    "CART_ITEMS": [Item3]
}

보시다 싶이 같은 키("LOGIN_USER", "CART_ITEMS")를 사용했지만, 세션 간에는 독립적으로 관리됨.


예제를 만들면서 궁금했던 점을 작성

Q : Cookie를 사용할 때는 HttpServletResponse를 사용했는데 왜 Session을 사용할 때는 HttpServletRequest를 사용하는가?

A : Cookie와 Session에서 HttpServletResponse와 HttpServletRequest의 사용 차이는 각각의 기능과 책임에서 비롯된다.

  1. Cookie는 클라이언트(브라우저)에 데이터를 저장하는 방식이다. 서버에서 생성된 쿠키는 HTTP 응답을 통해 클라이언트에 전달되며, 이후 요청마다 클라이언트가 쿠키를 포함하여 서버로 전달한다.
    • 쿠키는 서버가 생성 후 클라이언트에게 전달해야 하므로, HttpResponse 객체를 사용하여 쿠키를 설정한다.
  2. Session은 클라이언트가 아닌 서버에 데이터를 저장하는 방식이다. 세션은 클라이언트를 식별하기 위해 세션 ID를 생성하고, 이 세션 ID를 클라이언트에 전달하는데 쿠키를 사용한다.
    • 클라이언트가 서버에 세션과 관련된 요청을 할 때마다, 클라이언트로부터 세션 ID를 포함한 요청을 받아야 세션을 사용할 수 있다.
    • 그래서 Session은 클라이언트의 요청에서 세션 ID를 기반으로 서버에 저장된 데이터를 가져오는 구조이다.
기능 Cookie Session
데이터 저장 위치 클라이언트(브라우저) 서버
식별 방식 쿠키에 데이터 직접 저장 쿠키로 전달된 세션 ID로 식별
주로 사용하는 객체 HttpServletResponse(응답에 쿠키 설정) HttpServletRequest(요청에서 세션 접근)

Q : 그럼 Cookie와 Session을 같이 사용해야 하는거야 아니면 둘 중 하나만 사용하면 되는거야?

A : 따로 사용할 수 있지만 일반적으로는 Cookie와 Session이 함께 사용됨. 왜냐하면 세션ID는 클라이언트를 식별하는데 필요하며,
이 세션 ID를 통하여 쿠키에 저장해서 클라이언트와 서버 간에 전달하므로 보안성과 편의성을 모두 제공한다.

'Back-End > Spring-MVC' 카테고리의 다른 글

스프링 글로벌 API 예외 처리와 @Valid 검증 예외 처리  (1) 2024.12.31
Servlet Filter  (0) 2024.12.12
HTTP 요청 / 응답 조합 상황별 사용  (0) 2024.12.02
HTTP 응답 데이터  (1) 2024.12.02
HTTP 요청 데이터  (1) 2024.12.02