본문 바로가기
CS

쿠키, 세션, 토큰

by 감자b 2024. 12. 26.

쿠키

클라이언트의 상태 정보를 사용자의 브라우저에 저장하는 작은 텍스트 정보로, 웹 서버가 클라이언트에게 보내면 해당 클라이언트는 이를 쿠키 저장소에 저장 후 요청을 보낼 때 이 정보를 함께 전송하여 어떤 클라이언트의 요청인지 식별하는 역할을 한다.

  • Key-Value 쌍으로 구성
  • 쿠키 이름, 쿠키 값, 만료시간, 전송할 경로, 전송할 도메인 명, 보안 연결 여부, HttpOnly 여부로 구성
    • 예) Set-Cookie: sessionId=abcd1234; Expires=Wed, 21 Oct 2024 07:28:00 GMT; Domain=example.com; Path=/; Secure; HttpOnly
  • 하나의 쿠키는 4KB 정도의 크기 제한
  • 도메인 당 20개의 쿠키를 가질 수 있음
  • 클라이언트는 300개의 쿠키를 저장할 수 있음. 브라우저가 요청한 도메인, 경로가 쿠키의 도메인, 경로와 일치하면 해당 조건에 맞는 쿠키를 알아서 보내줌

쿠키 속성

1. Expires, max-age → 생명주기 관련

  • Set-Cookie: expires=Sat, 26-Dec-2020 04:39:21 GMT
  • 만료일이 되면 쿠키 삭제
  • Set-Cookie: max-age=3600 (3600초 동안 쿠키 유효)
    • 0이나 음수를 지정하면 쿠키 삭제
  • 세션 쿠키: 만료 날짜를 생략하면 브라우저 종료 시 까지만 유지
  • 영속 쿠키: 만료 날짜를 입력하면 해당 날짜까지 유지
    • 웹 브라우저가 종료되어도 시간이 남아있는 한 살아 있음 
    • 쿠키와 로그아웃
      • 만약 만료되지 않았는데 로그아웃 요청을 하면 서버에서 쿠키를 만료 처리시키고 세션을 삭제 시킴
      • 만료 날짜가 다 되면 요청 시 쿠키가 없는 상태로 요청, 서버는 로그인하지 않은 사용자로 인식

2. Domain - 도메인 관련.

  • 명시하면 명시한 문서 기준 도메인 + 서브 도메인에 쿠키 접근
    • 예) domain=example.org 를 지정해서 쿠키 생성
    • example.org, dev.example.org 모두 쿠키 접근
  • 생략 시에는 현재 문서 기준 도메인만 적용
    • 예) example.org 에서 쿠키를 생성하고 domain 생략
    • example.org 쿠키 접근, dev.example.org 쿠키 미접근

3. Path - 전송할 경로 지정

  • Path로 경로를 지정하면 해당 경로를 포함한 하위 경로 페이지만 쿠키를 전송하며 일반적으로는 path=/ 루트로 지정한다.
    • 예) path=/home 지정
    • /home, /home/level1/level2 … 쿠키 전송
    • /hello -> 쿠키 미전송

4. Secure, HttpOnly, SameSite - 보안 관련

  • 쿠키는 http, https를 구분하지 않고 전송하는데 Secure 적용 시 https일 때만 전송
  • HttpOnly → XSS 공격 방지로 자바스크립트에서 쿠키에 접근 불가해진다.(document.cookie)
  • SameSite → XSRF 공격 방지로 요청 도메인과 쿠키에 설정된 도메인이 같은 경우만 쿠키 전송

동작 방식

  1. 클라이언트가 로그인 요청 (POST /login)
  2. 로그인 검증이 성공적으로 이루어지면 서버가 응답 헤더로 Set-Cookie 헤더에 쿠키를 전달.
  3. 클라이언트는 이 쿠키를 브라우저 내 쿠키 저장소에 저장을 하고 이후 요청을 할 때 요청 헤더 Cookie에 해당 쿠키를 함께 전달
  4. 서버는 받은 쿠키 유효성을 확인하고 이를 통해 사용자 확인

쿠키 정보는 항상 서버에 전송되므로 네트워크 트래픽을 유발한다. 따라서 최소한의 정보(세션 id, 인증 토큰)만 쿠키에 담아야하며 민감한 정보는 담으면 안됨.


세션

세션(Session)은 웹 애플리케이션에서 사용자의 상태를 관리를 위해 사용하는 기술을 의미한다.

  • 각 사용자마다 고유한 세션 ID를 지니는데 클라이언트와 서버 간의 연결을 식별하는 데 사용.
  • 세션 ID는 일반적으로 쿠키를 통해 전달
  • 서버 측에 저장하므로 보안을 강화
  • 사용자가 로그아웃, 브라우저를 닫을 때 만료시키거나 서버에서 만료 시간을 설정하는 등 보안이 우수하지만
  • 세션 하이재킹과 같은 공격에 취약하므로 HTTPS 프로토콜을 통해 보호해야함
  • 저장하는데 제한이 없음 (서버 용량에 따라 결정)

동작 방식

  1. 클라이언트가 로그인 요청 (POST /login)
  2. 로그인 검증이 성공적으로 이루어지면 서버는 seesionid를 저장. 이를 응답 시 Set-Cookie 헤더에 쿠키를 전달.
  3. 클라이언트는 이 쿠키를 브라우저 내 쿠키 저장소에 저장을 하고 이후 요청을 할 때 요청 헤더 Cookie에 해당 쿠키를 함께 전달
  4. 서버는 받은 쿠키 유효성을 확인(세션 저장소에서 찾아봄), 이를 통해 사용자 확인

이러한 세션은 사용자가 많아질수록 서버의 메모리에 저장해야 하는 정보가 많아지므로 부하가 발생한다는 단점이 있다. 서버 자원에는 한계가 있으므로 쿠키와 세션을 적절하게 병행 사용하여 서버 자원의 낭비를 줄일 수 있다.


쿠키와 세션 사용 이유

이렇게 쿠키와 세션을 사용하는 이유는 HTTP가 무상태 프로토콜이기 때문이다.

 

HTTP는 Stateless, Connectionless 2가지의 특징을 지니는데 매 통신마다 서버는 클라이언트의 상태를 기억하지 않고 연결이 맺어지고 통신이 끝나면 바로 연결을 끊는다는 특징이 있다.

하지만 로그인을 지속하는 경우에는 어쩔 수 없이 상태를 유지를 해야하는데, 이런 경우 쿠키와 세션, 토큰 등을 사용해서 Stateless 프로토콜인 HTTP에서 상태를 유지할 수 있다.


토큰

위의 문제를 해결하기 위해 해시 알고리즘을 이용한 토큰이라는 것이 생기게 되었다.

세션 대신 토큰이라는 것을 부여하고 요청이 오면 해당 토큰을 수학적 방식으로 검증하여 서버측에서 발급한 토큰이 맞는지 확인 후 요청을 허가해주는 역할을 한다.

이러한 토큰은 대표적으로 JWT가 있다.

 

JWT (JSON Web Token)

JWT란JSON 객체를 이용해 당사자 간에 안전하게 정보를 전송하기 위해 만들어진 것으로, 인증에 필요한 정보를 암호화하여 담은 토큰을 의미한다. JWT의 구성 요소 Header{ "alg": "HS256", "typ": "JWT"}헤더

hbb-devlog.tistory.com

 

동작 방식

  1. 클라이언트가 로그인 요청 (POST /login)
  2. 토큰 생성 및 검증 알고리즘을 통해 토큰 생성 후 해당 토큰을 클라이언트에 전달
  3. 클라이언트는 해당 토큰을 매 요청 시 Authorization 헤더에 해당 토큰을 함께 전송
  4. 서버는 받은 토큰을 검증 알고리즘을 통해 확인

세션과는 다르게 세션 저장소에서 사용자를 확인하는 것이 아니라 수학적인 알고리즘을 통해 토큰을 검증하므로 따로 상태를 저장해 둘 필요가 없다는 장점이 있다. 하지만 로그인 제한을 위해 서버가 강제로 로그아웃을 시키는 등 토큰의 유효 기간이 지날 때 까지 통제할 수 없다는 단점이 있다. (만료 시간을 짧게하는 식의 방안 존재)

로그인 방식 세션 토큰
상태 기록 상태를 저장 상태를 저장하지 않음
통제 여부 서버가 사용자 상태 통제 가능 한 번 토큰이 발급되면 통제할 수 없음

세션, 쿠키 사용 방법

서블릿은 세션을 편리하게 사용할 수 있도록 HttpSession 기능을 제공한다. (서블릿 컨테이너에서 세션이 관리)

서블릿을 통해 세션을 생성하게 되면 쿠키 이름이 JSESSIONID, 값은 랜덤인 쿠키를 생성한다.

@RestController
@RequestMapping("/members")
public class MemberController {

    @PostMapping
    public String joinMember(@Valid @ModelAttribute JoinMemberDto memberDto, HttpServletRequest request) {

        // 서비스 계층에서 로그인 성공 후 저장된 멤버를 반환한다고 가정
        Member loginMember = getLoginMember(memberDto);

        //세션이 있다면 해당 세션 반환, 없다면 신규 세션을 생성
        HttpSession session = request.getSession();
        session.setAttribute(SessionConst.LOGIN_MEMBER, loginMember);

        return "성공!";
    }

    private static Member getLoginMember(JoinMemberDto memberDto) {
        return Member.builder()
                .id(memberDto.getId())
                .username(memberDto.getUsername())
                .nickname(memberDto.getNickname())
                .password(memberDto.getPassword())
                .age(memberDto.getAge())
                .build();
    }
}

위와 같이 request.getSession(boolean) 메서드를 통해 세션의 생성, 조회가 가능하다.

기본값은 getSession(true)이며 이는 세션이 존재하면 기존 세션을 반환, 없으면 새로운 세션을 생성 후 반환하고 getSession(false)의 경우 세션이 존재하는 경우는 동일하지만 없다면 생성하지 않고 null을 반환한다.

그리고 session.setAttribute()를 통해 하나의 세션에 여러 값을 저장할 수 있다.

(서버 내부에는 세션 저장소라는 것이 존재하고 HttpSession은 이 저장소에 보관되는 세션. 그리고 HttpSession 내부에는 데이터를 Map 형식으로 보관할 수 있는데 setAttribute() 메서드가 그 역할을 한다.)

 

요청을 보내면 아래와 같이 JSESSIONID라는 키의 쿠키가 있는 것을 확인할 수 있다.

 

참고로 세션의 유효 기간을 설정하려면 application.properties에 server.servlet.session.timeout=30 옵션을 추가한다. (서버에 최근 요청 시간을 기준으로 30분 정도 유지)

이미 로그인이 되어있는 사용자의 세션을 찾아서 사용하고 싶다면 다음과 같이 @SessionAttribute를 이용하면 된다.

@RestController
@RequestMapping("/")
public class HomeController {

    @GetMapping
    public String home(@SessionAttribute(name = "loginMember", required = false) Member loginMember) {
        if (loginMember == null) {
            throw new LoginRequiredException("로그인이 필요한 서비스입니다.");
        }
        return "home 화면";
    }
}

참고로 웹 브라우저로 로그인 시도 시 URL에 세션을 포함하고 있는 경우가 있다. ex) http://localhost:8080/;jsessionid=~~~~~~

이는 웹 브라우저가 쿠키를 지원하지 않을 때 URL에 값을 포함해서 전달하여 세션을 유지시켰던 방법으로 서버 입장에서는 웹 브라우저가 쿠키를 지원하는지 안하는지 판단할 수 없기 때문에 쿠키와, URL에 값을 함께 전달한다.

따라서 쿠키를 통해서만 세션을 유지하고 싶다면 아래와 같이 옵션을 추가한다.

server.servlet.session.tracking-modes=cookie

 

세션을 삭제하고 싶다면 session.invalidate() 메서드를 사용

@PostMapping("/logout")
public String logout(HttpServletRequest request) {
	HttpSession session = request.getSession(false);
	if (session != null) {
    		session.invalidate(); // 세션 삭제
	}
	return "로그아웃 성공";
}

마지막으로 위에서 세션에 데이터를 저장할 때 Member 데이터를 모두 세션에 담았지만 이는 사용자 수가 누적될 수록 메모리 사용량이 늘어난다는 문제가 있다.

따라서 세션에는 최소한의 데이터만 저장하도록 하자.

'CS' 카테고리의 다른 글

프로세스와 스레드  (1) 2024.12.26
웹 서버, WAS, 리버스 프록시  (0) 2024.12.26
SOLID 원칙  (0) 2024.12.26
포트포워딩  (0) 2024.12.26
[네트워크] 사설 IP, 공인 IP  (0) 2024.12.26