[FastAPI - CORS 마스터 2부] 쿠키 인증(Credential) 이슈 해결과 보안을 위한 정교한 허용 전략

2025. 12. 11. 20:52·ServerDev/FastAPI

[FastAPI - CORS 마스터 2부] 쿠키 인증(Credential) 이슈 해결과 보안을 위한 정교한 허용 전략

지난 1부에서 우리는 allow_origins=["*"]라는 만능열쇠를 사용하여 네트워크 에러라는 급한 불을 껐습니다. 브라우저와 서버 사이의 장벽이 허물어졌고, API 통신은 원활해 보였습니다. 하지만 프로젝트가 고도화되면서 '사용자 인증' 기능을 붙여야 하는 순간에는 추가적인 인증을 넣었어야 합니다.

 

서비스의 핵심인 로그인 기능을 구현하기 시작했습니다. 보안성을 높이기 위해 단순한 토큰 전달 방식 대신, HttpOnly 쿠키에 세션 ID나 JWT를 담아 전달하는 방식을 채택했습니다. 프론트엔드 코드도 이에 맞춰 수정되었습니다. Axios와 같은 HTTP 클라이언트가 요청을 보낼 때 쿠키를 함께 실어 보내도록 withCredentials: true (또는 fetch의 credentials: 'include') 옵션을 활성화했습니다.

 

모든 준비를 마치고 로그인을 시도했습니다. 그런데, 또다시 에러 메시지가 콘솔 창을 덮쳤습니다. 이번에는 메시지가 조금 달랐습니다.

 

Access to XMLHttpRequest at 'http://localhost:8000/login' from origin 'http://localhost:3000' has been blocked by CORS policy: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.

 

에러 메시지는  "요청에 인증 정보(Credential)가 포함되어 있다면, 응답 헤더의 Access-Control-Allow-Origin은 절대로 와일드카드(*)여서는 안 된다"는 것이었습니다.

 

만약 인증 정보가 포함된 요청에 대해 서버가 와일드카드(*)로 응답을 허용한다면 어떤 일이 벌어질까요? 사용자가 악의적인 사이트에 접속했을 때, 그 사이트가 사용자의 브라우저에 저장된 쿠키를 실어서 제 서버로 요청을 보내면, 서버는 "누구든 환영(*)"이라며 응답을 내어주게 됩니다. 이는 곧 사용자의 개인정보나 세션 정보가 해커의 손에 넘어가는 심각한 보안 사고(CSRF 등)로 이어질 수 있습니다.

 

따라서 브라우저는 인증 정보(쿠키, 인증 헤더 등)를 다룰 때는 서버가 명시적으로 "나는 정확히 이 출처(http://localhost:3000)를 신뢰한다"라고 콕 집어서 응답해주기를 요구합니다. 그래야만 브라우저는 안심하고 데이터를 넘겨줍니다.

 

결국 저는 1부에서 작성했던 CORSMiddleware 설정을 수정해야만 했습니다. 와일드카드를 제거하고, 실제로 요청을 보내는 프론트엔드의 주소를 명시적으로 적어주어야 했습니다.

 

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

# 명시적인 출처 목록 정의
origins = [
    "http://localhost:3000", # React 로컬 주소
    "http://127.0.0.1:3000", # 가끔 localhost 대신 IP로 접근할 때 대비
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,      # '*' 대신 구체적인 리스트를 사용
    allow_credentials=True,     # 인증 정보 포함 허용 (이제 에러가 나지 않음)
    allow_methods=["*"],
    allow_headers=["*"],
)

 

이렇게 설정을 변경하자 로그인 기능이 정상적으로 작동했습니다. 서버는 이제 요청이 들어오면 Origin 헤더를 확인하고, 그 값이 origins 리스트에 있을 때만 해당 출처를 그대로 Access-Control-Allow-Origin 헤더에 담아 응답합니다. 브라우저는 요청한 출처와 허용된 출처가 정확히 일치하는 것을 확인하고 통신을 허가합니다.

 

하지만 개발을 진행하다 보니 또 다른 난관이 기다리고 있었습니다. 바로 '동적 도메인' 문제였습니다.

 

저희 팀은 프론트엔드 배포를 위해 Vercel이나 Netlify 같은 서비스를 사용하고 있었습니다. 이 서비스들은 Pull Request를 올릴 때마다 미리보기(Preview) 배포를 생성해주는데, 이때 생성되는 URL이 매번 달라졌습니다. 예를 들어 git-feature-login-projectname.vercel.app 처럼 브랜치 이름과 해시값이 섞인 도메인이 생성되는 것이었습니다.

이 수많은, 예측 불가능한 도메인을 origins 리스트에 일일이 하드코딩하는 것은 불가능했습니다. 그렇다고 다시 와일드카드(*)를 쓸 수도 없는 노릇이었습니다. 인증 정보가 필요했으니까요.

이때 FastAPI의 의 정규 표현식을 사용했습니다. CORSMiddleware는 단순한 리스트 매칭뿐만 아니라, 정규 표현식(Regex)을 지원한다는 사실을 알게 되었습니다. allow_origins 대신 allow_origin_regex 파라미터를 사용하면 패턴 매칭을 통해 유연하게 출처를 제어할 수 있습니다.

저는 설정을 다음과 같이 변경했습니다. 

app.add_middleware(
    CORSMiddleware,
    allow_origin_regex="https://.*\.vercel\.app", # vercel.app으로 끝나는 모든 서브도메인 허용
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

이 설정 덕분에 Vercel에서 생성되는 어떤 프리뷰 도메인이라도, HTTPS 프로토콜을 사용하고 .vercel.app으로 끝난다면 자동으로 CORS 허용 목록에 포함되었습니다. 물론 로컬 개발을 위해 http://localhost:3000 도 함께 허용해야 한다면, 정규식 패턴을 조금 더 정교하게 수정하거나, 개발 환경에 따라 설정을 분리하는 전략이 필요했습니다.

이 과정에서 배운 점은, CORS 설정이 단순히 에러를 끄는 스위치가 아니라 서버의 보안 대문을 관리하는 경비원과 같다는 사실입니다. allow_credentials=True 옵션은 "신분증(쿠키)을 확인하겠다"는 의미이며, 이때는 반드시 "출입 가능한 방문객 명단(구체적인 Origin)"이 짝으로 존재해야 한다는 원칙을 확실히 이해하게 되었습니다.

아래 다이어그램은 인증 정보가 포함되었을 때의 CORS 처리 흐름을 보여줍니다. 와일드카드일 때 왜 차단되는지, 그리고 명시적 지정일 때 어떻게 통과되는지를 비교해 보았습니다.

 

이제 로컬 개발 환경에서의 문제는 완벽하게 해결되었습니다. 인증도 되고, API 호출도 잘 됩니다. 하지만 이 코드를 그대로 운영 서버(Production)에 배포해도 될까요? 로컬호스트 주소가 운영 서버 설정에 남아있어도 괜찮을까요?

 

다음 3부에서는 개발(Local) 환경과 운영(Production) 환경의 설정을 우아하게 분리하는 방법, 그리고 배포 과정에서 자주 실수하는 프로토콜(http vs https) 문제와 같은 실전 트러블 슈팅 경험을 공유하겠습니다. 환경 변수를 이용해 코드를 깔끔하게 정리하는 'Pydantic Settings' 활용법도 함께 다룰 예정입니다.

'ServerDev > FastAPI' 카테고리의 다른 글

[FastAPI] BackgroundTasks 심층 분석: 0.1초 응답의 비밀과 치명적 한계 (vs Celery, Custom Loop)  (0) 2025.12.12
[FastAPI - CORS 마스터 3부] 개발(Local) vs 운영(Prod) 환경 분리와 실전 트러블 슈팅  (0) 2025.12.12
FastAPI - CORS 마스터 1부] 프론트엔드 연동 첫걸음, CORS 에러  (0) 2025.12.10
[FastAPI] 웹 프레임워크가 아니라 'AI 모델 서빙기'로 사용하기 (feat. Spring Boot)  (0) 2025.12.09
[FastAPI - DI 마스터 3부] 의존성 오버라이드(Override)와 고급 패턴  (0) 2025.12.09
'ServerDev/FastAPI' 카테고리의 다른 글
  • [FastAPI] BackgroundTasks 심층 분석: 0.1초 응답의 비밀과 치명적 한계 (vs Celery, Custom Loop)
  • [FastAPI - CORS 마스터 3부] 개발(Local) vs 운영(Prod) 환경 분리와 실전 트러블 슈팅
  • FastAPI - CORS 마스터 1부] 프론트엔드 연동 첫걸음, CORS 에러
  • [FastAPI] 웹 프레임워크가 아니라 'AI 모델 서빙기'로 사용하기 (feat. Spring Boot)
yeseul-kim01
yeseul-kim01
  • yeseul-kim01
    슬 개발일지
    yeseul-kim01
  • 전체
    오늘
    어제
    • 분류 전체보기 (79)
      • 자격증 (1)
        • 정보보안기사 (0)
      • DevOps (17)
        • Docker (6)
        • Kubernetes (1)
        • GitHub Actions (0)
        • AWS (4)
        • Monitoring (1)
        • Nginx (1)
        • GCP (3)
      • ServerDev (34)
        • SpringBoot (13)
        • DJango (5)
        • FastAPI (14)
        • Next (0)
        • Flask (0)
        • Database (2)
      • Algorithm (2)
        • BFS (0)
        • DFS (1)
        • 다익스트라 (0)
      • CS (8)
      • Data Engineering (1)
      • AI&MLOps (2)
      • Architecture (6)
      • Software Engineering (0)
        • Library Packaging (0)
      • Project (5)
        • docx-generator (0)
        • speak-note (2)
        • ms-serving (1)
        • keyshield (2)
      • ProgrammingLanguages (3)
        • Python (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    docker
    멀티모듈
    아키텍처설계
    rag
    KServe
    동시성제어
    grpc
    비동기처리
    실무일기-인프라편
    STT
    FastAPI
    아키텍처
    프로젝트기록-KeyShield
    Django
    MLops
    SpringBoot
    multipartfile
    하이브리드아키텍처
    실시간시스템
    프로젝트기록-speaknote
    백엔드
    트러블슈팅
    실무일기-백엔드편
    SpeakNote
    NLP부트캠프
    di
    KeyShield
    FastAPI - CORS 마스터
    Kubernetes
    depends
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
yeseul-kim01
[FastAPI - CORS 마스터 2부] 쿠키 인증(Credential) 이슈 해결과 보안을 위한 정교한 허용 전략
상단으로

티스토리툴바