이 사이트의 실습은 자바스크립트를 켜야 움직입니다. 아래는 같은 내용을 글로 적은 것입니다.
공부 › 네트워크 › HTTP
쿠키
기억하지 못하는 서버에게 매번 이름표를 내민다
HTTP는 요청과 요청을 이어 주지 않습니다. 서버는 앞서 온 요청이 같은 브라우저에서 왔는지 알 수 없습니다. 그래서 로그인에 성공하면 서버가 응답에 작은 값(쿠키)을 실어 보내고, 브라우저는 그것을 보관했다가 같은 사이트로 가는 요청마다 자동으로 붙여 보냅니다.
일부만 실행으로 확인 · 브라우저와 시험 서버로 확인(브라우저 종료 시 삭제는 미확인)
이 실습에 나오는 개념
- 쿠키
- 서버가 응답에 실어 보내면 브라우저가 저장해 두었다가 같은 사이트에 요청할 때마다 자동으로 함께 보내는 작은 데이터다. HTTP는 요청 사이의 상태를 기억하지 않으므로, 로그인 상태처럼 이어져야 하는 정보를 유지하는 데 쓰인다. 사용자의 브라우저에 저장되므로 내용을 보거나 바꿀 수 있다는 점을 전제로 사용해야 한다.
- HTTP와 무상태(Stateless)
- HTTP는 클라이언트가 요청을 보내면 서버가 응답하는 방식의 통신 규약이다. 무상태라는 것은 서버가 이전 요청의 내용을 기억하지 않아 각 요청이 그 자체로 처리에 필요한 정보를 모두 담고 있어야 한다는 뜻이다. 서버가 상태를 기억하지 않으므로 어느 서버가 요청을 받아도 되어 서버를 늘리기 쉽다. 로그인 상태처럼 이어져야 하는 정보는 쿠키, 세션, 토큰으로 유지한다.
- HttpOnly, Secure, SameSite
- 쿠키를 보호하는 속성들이다. HttpOnly는 자바스크립트가 쿠키를 읽지 못하게 해 스크립트 삽입 공격으로 쿠키가 탈취되는 것을 막는다. Secure는 HTTPS 연결에서만 쿠키를 전송하게 한다. SameSite는 다른 사이트에서 시작된 요청에 쿠키가 함께 전송되는 것을 제한해 CSRF 공격을 줄인다.
- 세션
- 사용자의 상태를 서버 쪽에 저장하고, 클라이언트에는 그 저장소를 가리키는 식별자(세션 ID)만 쿠키로 전달하는 방식이다. 실제 정보가 서버에 있으므로 클라이언트가 내용을 고칠 수 없다(세션 ID를 빼앗기는 것은 다른 문제다). 서버가 여러 대일 때는 어느 서버로 요청이 가도 같은 세션을 찾을 수 있도록 Redis 같은 공용 저장소에 세션을 두는 구성이 일반적이다.
이렇게 바꿔 보세요
- 처음 설정으로 — 쿠키가 같은 사이트의 요청마다 자동으로 붙습니다.
- 쿠키가 없다면 — 로그인 직후의 요청도 서버는 누구인지 모릅니다.
- 경로를 /shop으로 좁히면 — /orders 요청에는 붙지 않습니다.
- HttpOnly를 끄면 — 페이지의 자바스크립트가 쿠키 값을 읽을 수 있습니다.
- 수명 3초, 10초 뒤 요청 — 만료된 쿠키는 브라우저가 지웁니다.
- 브라우저를 닫았다 열면 — 수명을 지정하지 않은 쿠키는 사라집니다. 1시간으로 바꿔 비교해 보세요.
처음 설정으로 실행하면
- 로그인 요청입니다. 첫 요청이라 브라우저에는 이 사이트의 쿠키가 없습니다. 서버는 본문의 아이디와 비밀번호를 확인합니다.
- 서버가 응답에 Set-Cookie: sid=abc123; Path=/; HttpOnly 헤더를 실어 보냅니다. 브라우저는 이 값을 shop.example의 쿠키로 보관합니다. 서버는 "abc123은 회원 7"이라고 자기 쪽에 적어 둡니다.
- shop.example/orders 요청입니다. 브라우저가 Cookie 헤더에 sid를 자동으로 붙였습니다. 코드에서 따로 넣어 주지 않아도 붙습니다. 서버는 abc123을 찾아 회원 7의 요청임을 압니다.
- shop.example/shop/cart 요청입니다. 이 경로는 Path=/에 해당하므로 쿠키가 붙습니다.
- 다른 사이트 other.example로 가는 요청입니다. shop.example이 준 쿠키는 붙지 않습니다. 쿠키는 그것을 내려 준 호스트(shop.example)로 가는 요청에만 붙습니다(Domain 속성을 쓰면 범위가 넓어집니다).
- shop.example/shop/cart 페이지에서 자바스크립트가 document.cookie를 읽습니다. HttpOnly 쿠키는 스크립트에 보이지 않습니다. 그래도 요청에는 계속 자동으로 붙습니다.
실행 결과
- 수명을 지정하지 않은 쿠키는 브라우저를 닫을 때 지워집니다. "로그인 유지"는 Max-Age나 Expires로 수명을 지정한 쿠키입니다.
- 쿠키는 사용자의 브라우저에 저장되므로 사용자가 내용을 보거나 바꿀 수 있습니다. 그래서 쿠키에는 회원 번호 같은 값을 직접 넣지 않고, 추측할 수 없는 식별자(세션 ID)나 서명된 토큰을 넣습니다. 그 내용은 "세션과 JWT" 실습에서 이어집니다.
코드
@PostMapping("/login")
ResponseEntity<Void> login(@RequestBody LoginRequest req) {
Member m = memberService.check(req.id(), req.password());
String sid = sessionStore.create(m.getId()); // "abc123" → 회원 7
ResponseCookie cookie = ResponseCookie.from("sid", sid)
.path("/")
.httpOnly(true)
.build();
return ResponseEntity.ok()
.header(HttpHeaders.SET_COOKIE, cookie.toString())
.build();
}
// 응답 헤더: Set-Cookie: sid=abc123; Path=/; HttpOnly
이 모형의 전제
Path에 따라 붙는 경로, HttpOnly 쿠키가 document.cookie에 보이지 않는 것, Max-Age가 지난 쿠키가 사라지는 것, 호스트가 다른 주소로는 쿠키가 가지 않는 것은 실제 브라우저와 시험용 서버로 확인한 결과와 같습니다. 브라우저를 닫을 때 세션 쿠키가 지워지는 것은 표준 동작을 따른 것이며 직접 실행해 확인하지는 않았습니다. "이전 탭 복원" 기능을 켠 브라우저는 세션 쿠키를 되살리기도 합니다. Secure와 SameSite, Domain 속성은 이 실습에서 다루지 않았습니다.
앞 실습: HTTP/1.1, 2, 3 · 다음 실습: HTTP 캐시와 ETag