파이썬 requests 세션 쿠키 유지 안 될 때 확인하는 순서

파이썬 requests에서 첫 요청의 쿠키가 다음 요청에 전달되지 않는다면, 먼저 같은 requests.Session 인스턴스를 재사용했는지 확인해야 합니다. 요청마다 cookies를 넘긴 것만으로는 그 쿠키가 Session에 자동 저장되지 않으며, 수동으로 PreparedRequest를 만들었다면 Session.prepare_request()를 사용했는지도 확인해야 합니다.

먼저 구분할 세 가지

겉으로는 모두 “세션이 끊겼다”처럼 보이지만 확인할 지점은 다릅니다.

  • requests.get()이나 requests.post()를 매번 새로 호출해 Session 자체를 재사용하지 않은 경우
  • 요청 한 번에만 적용되는 cookies 인자를 다음 요청에도 자동으로 남을 것이라고 기대한 경우
  • Request.prepare()로 요청을 만들어 Session의 쿠키와 설정이 빠진 경우

이 순서를 나누어 보면 401 응답이나 로그인 페이지가 나왔을 때 쿠키 문제와 서버 권한 문제를 섞어 판단하는 일을 줄일 수 있습니다. 다만 서버가 반환한 401, 로그인 페이지, 빈 결과의 원인을 쿠키만으로 확정할 수는 없으므로 실제 전송 헤더를 확인해야 합니다.

1. 연속 요청이면 Session을 재사용합니다

일회성 요청은 requests.get() 또는 requests.post()로 충분할 수 있습니다. 반면 로그인 뒤 이어지는 API 호출처럼 같은 상태를 이어 가야 한다면 Session을 만들고 그 객체로 요청을 계속 보내는 편이 맞습니다. Requests 공식 문서는 동일 Session 인스턴스의 요청 사이에서 쿠키가 유지된다고 설명합니다.

아래 주소와 필드 이름은 예시이므로 실제 서비스의 문서에 맞게 바꿔야 합니다.

import os
import requests

login_url = "https://example.com/login"
profile_url = "https://example.com/api/profile"

with requests.Session() as session:
    login_response = session.post(
        login_url,
        data={"username": os.environ["APP_USERNAME"], "password": os.environ["APP_PASSWORD"]},
        timeout=10,
    )
    login_response.raise_for_status()

    profile_response = session.get(profile_url, timeout=10)
    profile_response.raise_for_status()
    print(profile_response.status_code)
    for cookie in session.cookies:
        print(cookie.name, cookie.domain, cookie.path)

이 예제의 핵심은 with 문 자체가 아니라 두 요청에 같은 session 객체를 사용한다는 점입니다. 비밀번호나 쿠키 값을 출력하지 않고, 인증 정보는 코드에 직접 넣지 않는 방식으로 다루어야 합니다.

위 예제처럼 응답에서 받은 쿠키가 Session에 들어갔는지 확인할 때 값 대신 이름과 적용 도메인 같은 메타데이터만 살펴볼 수 있습니다.

운영 로그에 쿠키 값, Authorization 헤더, 비밀번호를 남기지 않도록 주의합니다. 쿠키 이름만으로도 민감한 서비스 구조가 드러날 수 있다면 이름 역시 제한적으로 기록하는 편이 안전합니다.

2. 요청별 cookies는 다음 요청에 자동 저장되지 않습니다

다음처럼 요청 메서드에 cookies를 전달하면 해당 요청에 보낼 쿠키를 지정할 수 있습니다.

import requests

response = requests.get(
    "https://example.com/api/first",
    cookies={"session_hint": "비밀값"},
    timeout=10,
)

이 방식은 한 번의 요청에 쿠키를 붙이는 용도입니다. Requests 공식 문서에 따르면 메서드에 넘긴 요청별 쿠키는 Session 쿠키 저장소에 자동으로 남지 않으므로, 그 다음 요청에서 같은 쿠키가 필요하다면 명시적으로 다시 전달하거나 Session의 쿠키 저장소를 사용해야 합니다.

import requests

with requests.Session() as session:
    session.cookies.set("session_hint", "비밀값", domain="example.com", path="/")
    first = session.get("https://example.com/api/first", timeout=10)
    second = session.get("https://example.com/api/second", timeout=10)

다만 쿠키를 직접 복사하기 전에 서버가 첫 응답의 Set-Cookie를 통해 쿠키를 발급하는 흐름인지 확인해야 합니다. 쿠키를 임의로 조합하거나 다른 서비스의 인증 쿠키를 재사용하는 방법은 이 글의 범위가 아닙니다.

3. PreparedRequest에서는 Session 상태를 반영합니다

요청을 보내기 전에 URL이나 헤더를 확인하려고 PreparedRequest를 직접 만들 때 문제가 생길 수 있습니다. Request.prepare()는 Session 수준의 쿠키를 적용하지 않습니다. Session의 설정과 쿠키를 병합해야 한다면 Session.prepare_request()를 사용해야 합니다.

import requests

with requests.Session() as session:
    session.cookies.set("session_hint", "비밀값", domain="example.com", path="/")

    request = requests.Request(
        "GET",
        "https://example.com/api/profile",
    )
    prepared = session.prepare_request(request)
    response = session.send(prepared, timeout=10)
    response.raise_for_status()

Requests API 문서에서 Session.prepare_request(request)는 Request와 Session 설정을 병합한 PreparedRequest를 만든다고 설명합니다. 반대로 request.prepare()만 사용하면 Session에 있던 상태를 기대한 대로 적용하지 못할 수 있습니다.

4. 실제 전달 여부를 값 없이 확인합니다

Session에 쿠키가 있다고 해서 현재 요청에 반드시 원하는 쿠키가 포함됐다고 단정하지는 않습니다. 도메인과 경로가 맞지 않거나, 요청을 만든 경로에서 Session이 빠졌을 수 있기 때문입니다.

응답의 request는 실제 사용된 PreparedRequest이므로, 응답을 받은 뒤 전송 헤더의 존재 여부를 확인할 수 있습니다.

cookie_header = response.request.headers.get("Cookie")
print("Cookie 헤더 전송 여부:", cookie_header is not None)

이 결과가 참이어도 원하는 특정 쿠키가 포함됐는지까지 확인되는 것은 아닙니다. 값 자체를 출력하지 않고 존재 여부만 기록하는 것이 기본입니다. 디버깅상 쿠키 이름을 구분해야 한다면 값은 마스킹하고, 운영 환경에서는 로그 보존 범위도 함께 확인해야 합니다. Session.cookies에서도 값 대신 쿠키 객체의 이름·도메인·경로 같은 필요한 메타데이터만 제한적으로 확인합니다.

적용 판단 순서

다음 순서로 코드를 좁혀 가면 됩니다.

  1. 로그인과 후속 요청이 같은 Session 객체를 사용하는지 확인합니다.
  2. 요청 메서드의 cookies 인자를 썼다면 그 쿠키가 한 번의 요청에만 필요한지, Session에 넣어야 하는지 구분합니다.
  3. PreparedRequest를 직접 만들었다면 Request.prepare() 대신 Session.prepare_request()가 필요한 흐름인지 확인합니다.
  4. response.request.headers에서 Cookie 헤더가 실제 전송됐는지 값 없이 확인합니다.
  5. 쿠키가 전송됐는데도 401·403이 계속되면 만료, 권한, CSRF, 서버 측 인증 정책 등 별도 원인을 확인합니다.

마지막 단계가 중요한 이유는 쿠키가 실제로 전달됐다는 사실과 서버가 요청을 허용한다는 사실이 같지 않기 때문입니다. 이 글의 근거가 된 세부 동작은 Requests 공식 Advanced Usage 문서와 Requests 공식 API 문서에서 확인할 수 있습니다.

정리

연속 요청의 상태를 유지하려면 같은 requests.Session을 재사용하고, 요청별 cookies는 다음 요청에 자동으로 남지 않는다는 점을 기억해야 합니다. PreparedRequest를 직접 다룰 때는 Session.prepare_request()로 Session 상태를 반영하고, 마지막에는 쿠키 값을 노출하지 않는 방식으로 실제 전송 헤더를 확인하면 됩니다.

다음으로 확인할 질문은 Session을 언제 닫을지, 쿠키가 전달됐는데도 발생하는 401·403을 어떻게 구분할지, 여러 API 호출에 공통 timeout과 헤더를 어떻게 적용할지입니다.

Leave a Comment