본문 바로가기
에러일지

419 Page Expired 해결법 (feat. Laravel CSRF)

by 코딩하는 노씨 2026. 9. 23.
반응형

 

파이썬 requests 라이브러리를 활용해 웹 자동화(스크래핑, 티켓팅 봇 등) 스크립트를 작성하다 보면, 에러 코드를 하나 만나게 됩니다. 바로 HTTP 419 Page Expired 입니다.

 

분명히 HTML에서 숨겨진 _token 값도 잘 파싱했고, 헤더도 브라우저와 똑같이 맞췄는데 왜 서버는 자꾸 페이지가 만료되었다며 요청을 뱉어내는 걸까요?

 

오늘 이 글에서는 주로 라라벨(Laravel) 프레임워크 기반 웹사이트에서 발생하는 419 에러의 정확한 원인을 파이썬 코드에서 해결하는 법을 정리 해봅니다.

 

1. HTTP 419 에러란 무엇인가?

419 Page Expired는 공식적인 HTTP 표준 상태 코드가 아닙니다. 주로 라라벨(Laravel) 프레임워크에서 CSRF(Cross-Site Request Forgery) 토큰 검증에 실패했거나 세션이 만료되었을 때 반환하는 고유한 에러 코드입니다.

브라우저에서 이 에러가 났다면 단순히 "새로고침 하세요"로 끝나지만, 파이썬 봇 입장에서는 스크립트가 완전히 막혀버리는 치명적인 벽이 됩니다.

 

2. 왜 내 파이썬 스크립트에서만 419가 발생할까? (세션과 토큰의 불일치)

가장 흔한 실패 시나리오는 다음과 같습니다.

  1. session.get()으로 폼 페이지에 접속해 HTML을 받아온다.
  2. BeautifulSoup으로 `` 값을 추출해 변수에 저장한다.
  3. POST 요청을 보낼 때 payload에 _token을 담아 보낸다.
  4. 결과: 419 Page Expired 💣

이 시나리오가 실패하는 주된 원인은 리다이렉트(302 Found) 과정에서 발생하는 세션 쿠키와 CSRF 토큰의 엇갈림(Desync) 때문입니다.

서버에 트래픽이 몰리거나 특정 보안 로직을 거칠 때, 서버는 302 Redirect를 주면서 새로운 세션 쿠키(Set-Cookie)를 발급하는 경우가 있습니다. 이때 파이썬 requests의 세션 객체는 새로 발급된 쿠키를 자동으로 낼름 받아먹고 업데이트합니다. 하지만 여러분의 파이썬 코드 변수에 저장된 _token은 과거 HTML에서 파싱했던 옛날 토큰 그대로입니다.

결국 서버에 POST 요청을 보낼 때 "새 쿠키 + 옛날 토큰" 이라는 짝이 안 맞는 조합이 전송되고, 서버는 이를 위조된 요청으로 판단해 419 에러를 뱉어내는 것입니다.

 

3. 해결책: XSRF-TOKEN 쿠키 활용하기

이 문제를 해결하기 위해 리다이렉트를 일일이 쫓아가며 HTML을 다시 파싱하는 것은 코드가 지저분해지고 에러 발생 확률(예: 대기열 페이지로 빠지는 경우)을 높입니다.

해결책은 라라벨 프레임워크의 특성을 역이용하는 것입니다. 라라벨은 HTML 안에 토큰을 심어주는 것 외에도, 매 응답마다 암호화된 XSRF-TOKEN 쿠키를 항상 구워줍니다. 이 쿠키값을 읽어 API 통신용 헤더인 X-XSRF-TOKEN에 담아 보내면, 서버는 HTML 폼 데이터(_token)가 없어도 CSRF 검증을 통과시켜 줍니다.

 

💡 파이썬 적용 코드

핵심은 발사(POST) 직전에 현재 세션 쿠키에 들어있는 가장 최신의 XSRF-TOKEN을 빼와서 헤더에 덮어씌우는 것입니다. 쿠키 값은 URL 인코딩이 되어 있으므로 urllib.parse.unquote로 디코딩을 해줘야 합니다.

반응형
import requests
from urllib.parse import unquote

# 1. 세션 생성 및 초기 접속 (쿠키 확보)
session = requests.Session()
response = session.get("https://example-laravel-site.com/form")

# 2. POST 요청을 위한 헤더 셋팅
headers = {
    "User-Agent": "Mozilla/5.0 ...",
    "Referer": "https://example-laravel-site.com/form",
    "Content-Type": "application/x-www-form-urlencoded"
}

# 🔥 [핵심 방어 로직] 세션 쿠키에서 XSRF-TOKEN을 추출해 헤더에 주입 🔥
xsrf_cookie = session.cookies.get("XSRF-TOKEN")
if xsrf_cookie:
    headers["X-XSRF-TOKEN"] = unquote(xsrf_cookie)

# 3. Payload 작성 (여기서는 굳이 HTML에서 파싱한 _token이 없어도 무방함)
payload = {
    "name": "홍길동",
    "email": "test@test.com"
}

# 4. POST 발사
post_response = session.post(
    "https://example-laravel-site.com/submit", 
    headers=headers, 
    data=payload
)

print(f"응답 상태 코드: {post_response.status_code}") # 200 OK!
 

4. 요약

  • 419 에러의 정체: 라라벨 기반 서버에서 발생하는 세션/CSRF 토큰 불일치 에러.
  • 발생 원인: 리다이렉트 등으로 인해 세션 쿠키는 갱신되었으나, 코드가 쥐고 있는 토큰(문자열)은 갱신되지 않았을 때 발생.
  • 해결 방법: HTML 파싱에 의존하지 말고, session.cookies에서 XSRF-TOKEN을 꺼내어 X-XSRF-TOKEN 헤더에 디코딩하여 주입할 것.

 

웹 자동화 스크립트를 짤 때 이 원리만 이해하고 있으면, 트래픽이 몰려 서버가 세션을 마구잡이로 갱신하는 험난한 환경(티켓팅, 수강신청 등)에서도 결코 419 에러로 튕겨나가는 일은 없을 것입니다.

 

반응형