LabHub
Get started
배우기 러닝패스 코스

Sessions and Tokens — From the Browser to the Mesh

Issue a __Host- session cookie and delete it properly

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

목표

127.0.0.1:8301 에 로그인 서버를 세워 __Host-sid 세션 쿠키를 발급·검사·폐기하고, 쿠키가 브라우저(curl) 저장소에 실제로 받아들여지고 지워지는지까지 확인한다.

왜 중요한가

쿠키 문제는 서버 로그로는 보이지 않습니다. 서버는 Set-Cookie 를 정상으로 내보냈는데 브라우저가 접두 규칙이나 Secure 조건에 걸려 조용히 버리면, 증상은 "가끔 로그인이 안 된다" 로만 나타납니다. 그래서 이 실습은 응답 헤더와 함께 curl 쿠키 저장소를 봅니다. curl 8.5 도 __Host-·__Secure- 규칙을 어긴 쿠키를 저장하지 않으므로, 저장소에 남았는지가 곧 받아들여졌는지의 근사입니다. 로그아웃도 같은 눈으로 봅니다. 쿠키를 지우는 것은 브라우저에 하는 부탁일 뿐이고, 값을 베껴 둔 사람에게는 서버 기록이 지워졌는지만 의미가 있습니다.

단계

  1. /root/st/cookie/app.py 를 127.0.0.1:8301 에 띄운다. GET /healthz 가 200 과 {"ok":true} 를 준다.
  2. POST /login 에 JSON {"user":"alice","password":"wonderland"} 를 보내면 200 과 {"user":"alice"} 를 주고 __Host-sid 쿠키를 발급한다. 값은 secrets 로 만든 128비트 이상 무작위(base64url 22자 이상)이고 로그인할 때마다 달라야 하며, 사용자 이름이 들어가면 안 된다. bob/builder 도 로그인된다.
  3. 세션 쿠키에 Secure, HttpOnly, SameSite=Lax, Path=/ 를 붙이고 DomainMax-Age/Expires 는 붙이지 않는다(호스트 전용 세션 쿠키). alice 로 로그인하며 curl -c /root/st/cookie/jar.txt 로 쿠키 저장소를 남긴다. 그 파일의 __Host-sid 줄은 #HttpOnly_127.0.0.1 로 시작하고 경로 /, Secure TRUE, 만료 0 이어야 한다.
  4. GET /me 는 유효한 __Host-sid 가 있으면 200 과 {"user":"<사용자>"} 를 주고, 쿠키가 없거나 서버가 발급한 적 없는 값이면 401 을 준다.
  5. 틀린 비밀번호(alice/wrongpass)와 없는 사용자(mallory)의 POST /login 은 401 이고 Set-Cookie 가 한 줄도 없어야 한다.
  6. POST /logout 은 200 을 주고 서버 쪽 세션을 지운다. 응답에는 __Host-sid 를 지우는 Set-Cookie(Max-Age=0)가 있어야 하고, curl 저장소가 그 삭제를 실제로 받아들여 쿠키가 사라져야 한다. 로그아웃 전에 복사해 둔 쿠키 값으로 GET /me 를 부르면 401 이어야 한다. 3번의 jar.txt 는 덮어쓰지 말고 다른 저장소 파일로 시험한다.
  7. https://app.example.com/login 의 응답으로 아래 Set-Cookie 값이 왔을 때 브라우저가 쿠키를 저장하는지 판정해 /root/st/cookie/prefix.txt번호=accept 또는 번호=reject 로 한 줄씩 적는다.
    • 1 __Host-sid=a1; Secure; HttpOnly; Path=/; SameSite=Lax
    • 2 __Host-sid=a2; HttpOnly; Path=/; SameSite=Lax
    • 3 __Host-sid=a3; Secure; Path=/; Domain=app.example.com
    • 4 __Host-sid=a4; Secure; Path=/account
    • 5 __Secure-theme=dark; Secure; Path=/account; Domain=example.com
    • 6 __Secure-theme=dark; Path=/
    • 7 __host-sid=a7; Path=/
    • 8 theme=dark; Path=/; Domain=example.com
  8. /root/st/cookie/e2e.sh 로 로그인, 쿠키로 /me, 쿠키 없이 /me, 틀린 비밀번호, 로그아웃, 옛 쿠키 재사용을 차례로 시험하고, 로그아웃 뒤 저장소에 남은 __Host-sid 줄 수를 세어 /root/st/cookie/e2e.outlogin=200 me=200 no_cookie=401 bad_password=401 logout=200 replay=401 jar_after_logout=0 을 한 줄로 적는다.

참고

로그인 서버 띄우기

/root/st/cookie/app.py 를 127.0.0.1:8301 에 띄운다. GET /healthz 가 200 과 {"ok":true} 를 준다.

표준 라이브러리 http.server 로 충분합니다. 서버는 백그라운드로 띄우고 /healthz 가 답할 때까지 기다렸다가 다음으로 넘어가세요.

로그인에 세션 쿠키 발급하기

POST /login 에 JSON {"user":"alice","password":"wonderland"} 를 보내면 200 과 {"user":"alice"} 를 주고 __Host-sid 쿠키를 발급한다. 값은 secrets 로 만든 128비트 이상 무작위(base64url 22자 이상)이고 로그인할 때마다 달라야 하며, 사용자 이름이 들어가면 안 된다. bob/builder 도 로그인된다.

세션 값은 의미를 담지 않는 열쇠입니다. random 모듈은 예측할 수 있으니 암호학적 난수를 쓰는 secrets 모듈에서 URL 에 안전한 문자열을 만드는 함수를 찾아보세요. 누구의 세션인지는 서버가 dict 에 기록합니다.

쿠키 속성과 저장소 확인하기

세션 쿠키에 Secure, HttpOnly, SameSite=Lax, Path=/ 를 붙이고 DomainMax-Age/Expires 는 붙이지 않는다(호스트 전용 세션 쿠키). alice 로 로그인하며 curl -c /root/st/cookie/jar.txt 로 쿠키 저장소를 남긴다. 그 파일의 __Host-sid 줄은 #HttpOnly_127.0.0.1 로 시작하고 경로 /, Secure TRUE, 만료 0 이어야 한다.

Domain 은 적을수록 넓어집니다. 생략해야 보낸 호스트에만 갑니다. 만료를 적지 않으면 브라우저를 닫을 때 사라지는 세션 쿠키가 됩니다. curl 저장소 파일은 탭으로 나뉜 일곱 열이고, HttpOnly 쿠키는 첫 열 앞에 표시가 붙습니다.

쿠키로 사용자 알아보기

GET /me 는 유효한 __Host-sid 가 있으면 200 과 {"user":"<사용자>"} 를 주고, 쿠키가 없거나 서버가 발급한 적 없는 값이면 401 을 준다.

쿠키가 붙어 왔다는 사실만으로는 아무것도 증명되지 않습니다. 그 값으로 서버의 세션 기록을 찾아 사용자를 꺼내야 합니다.

실패한 로그인에는 쿠키를 주지 않기

틀린 비밀번호(alice/wrongpass)와 없는 사용자(mallory)의 POST /login 은 401 이고 Set-Cookie 가 한 줄도 없어야 한다.

쿠키를 만드는 코드가 비밀번호 검사보다 앞에 있으면 실패에도 쿠키가 나갑니다. 비밀번호 비교는 시간 차가 새지 않는 함수를 쓰는 편이 좋습니다.

로그아웃을 진짜로 만들기

POST /logout 은 200 을 주고 서버 쪽 세션을 지운다. 응답에는 __Host-sid 를 지우는 Set-Cookie(Max-Age=0)가 있어야 하고, curl 저장소가 그 삭제를 실제로 받아들여 쿠키가 사라져야 한다. 로그아웃 전에 복사해 둔 쿠키 값으로 GET /me 를 부르면 401 이어야 한다. 3번의 jar.txt 는 덮어쓰지 말고 다른 저장소 파일로 시험한다.

쿠키 삭제는 부탁일 뿐입니다. 값을 베껴 둔 사람에게는 서버 기록이 지워졌는지만 의미가 있습니다. 그리고 삭제 쿠키도 쿠키라서 __Host- 접두의 조건을 다시 통과해야 브라우저가 받아들입니다.

접두 규칙 판정하기

https://app.example.com/login 의 응답으로 아래 Set-Cookie 값이 왔을 때 브라우저가 쿠키를 저장하는지 판정해 /root/st/cookie/prefix.txt번호=accept 또는 번호=reject 로 한 줄씩 적는다. 1 __Host-sid=a1; Secure; HttpOnly; Path=/; SameSite=Lax · 2 __Host-sid=a2; HttpOnly; Path=/; SameSite=Lax · 3 __Host-sid=a3; Secure; Path=/; Domain=app.example.com · 4 __Host-sid=a4; Secure; Path=/account · 5 __Secure-theme=dark; Secure; Path=/account; Domain=example.com · 6 __Secure-theme=dark; Path=/ · 7 __host-sid=a7; Path=/ · 8 theme=dark; Path=/; Domain=example.com

접두마다 요구하는 조건 목록을 먼저 적어 두고 한 줄씩 대조하세요. __Host- 는 세 가지, __Secure- 는 한 가지입니다. 브라우저가 접두를 대소문자를 구분해 대조하는지도 규격에서 확인해 보세요. # 으로 시작하는 줄은 주석으로 둘 수 있습니다.

전체 흐름을 한 번에 증명하기

/root/st/cookie/e2e.sh 로 로그인, 쿠키로 /me, 쿠키 없이 /me, 틀린 비밀번호, 로그아웃, 옛 쿠키 재사용을 차례로 시험하고, 로그아웃 뒤 저장소에 남은 __Host-sid 줄 수를 세어 /root/st/cookie/e2e.outlogin=200 me=200 no_cookie=401 bad_password=401 logout=200 replay=401 jar_after_logout=0 을 한 줄로 적는다.

앞 스텝의 확인을 하나의 쿠키 저장소로 이어서 돌리면 됩니다. 재사용 시험에 쓸 옛 값은 로그아웃하기 전에 저장소에서 꺼내 두어야 합니다.