[FastAPI 시리즈 16편] CORS와 기본 보안 레이어 갖추기

English version

API가 실제 웹 프런트엔드와 만나는 순간 가장 먼저 부딪히는 장벽은 CORS(Cross-Origin Resource Sharing)입니다. CORS는 "어떤 도메인에서 내 API를 호출할 수 있는가"를 브라우저가 판단하는 규칙입니다. 기본 보안 헤더는 응답마다 붙는 짧은 문자열이지만 클릭재킹, MIME 스니핑 같은 공격을 예방합니다. 이번 편에서는 브라우저와 안전하게 통신하기 위한 설정과 기본 보안 헤더, 속도 제한 아이디어를 정리합니다.

이번 글에서 새로 나오는 용어

  1. CORS: 브라우저가 다른 도메인(Origin)에서 API를 호출할 수 있는지 판별하는 규칙으로, 허용하지 않으면 요청 자체가 차단됩니다.
  2. Origin: 프로토콜 + 도메인 + 포트로 이루어진 출처 개념으로, CORS 허용 목록을 만들 때 기준이 되는 문자열입니다.
  3. CORSMiddleware: FastAPI/Starlette가 제공하는 미들웨어로, 허용 Origin·메서드·헤더를 선언하면 프리플라이트 응답을 자동으로 처리합니다.
  4. 보안 헤더: X-Frame-Options, X-Content-Type-Options 같은 짧은 응답 헤더로, 클릭재킹·MIME 스니핑 등 웹 공격을 선제적으로 막습니다.
  5. 속도 제한(Rate Limiting): 동일 사용자·IP가 일정 시간 내 호출할 수 있는 횟수를 제한하는 정책으로, 남용을 막기 위해 slowapi나 API Gateway에서 구현합니다.

실습 카드

  • 예상 소요 시간: 50분 (핵심) / +25분 (선택 확장)
  • 사전 준비: 15편 테스트 코드, 브라우저 개발자 도구 사용
  • 실습 목표: 필요한 도메인만 허용하는 CORS와 기본 보안 헤더를 적용한 뒤, 여유가 있으면 속도 제한/프록시 설정을 실험한다

핵심 실습: CORS + 보안 헤더

필수 구간은 1시간 내에 끝낼 수 있도록 CORS와 기본 보안 헤더 설정만 다룹니다. 나머지 심화 주제는 아래 "선택 확장" 섹션으로 넘깁니다.

CORS 미들웨어

FastAPI는 Starlette의 CORSMiddleware를 그대로 사용할 수 있습니다.

from fastapi.middleware.cors import CORSMiddleware

origins = [
    "https://app.example.com",
    "https://admin.example.com",
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
    allow_headers=["Authorization", "Content-Type"],
    allow_credentials=True,
)

allow_credentials=True는 JWT 쿠키 전달에 필요합니다. 단, 와일드카드(*)와 함께 쓸 수 없으니 환경 변수로 도메인을 관리합니다. 허용 목록이 자주 바뀌므로 APP_ALLOWED_ORIGINS 같은 설정으로 분리해 두면 배포 때도 안전합니다.

프리플라이트 이해하기

브라우저가 실제 요청 전에 보내는 OPTIONS 요청을 프리플라이트라고 부릅니다. 서버는 허용된 메서드와 헤더를 Access-Control-Allow-* 헤더로 응답해야 합니다. 미들웨어가 이를 처리하니 라우터별로 별도 코드를 작성할 필요는 없습니다. 필요한 경우 DevTools 네트워크 탭에서 프리플라이트 응답을 확인해 보세요.

기본 보안 헤더

보안 헤더는 공격 표면을 줄이는 첫 방어선입니다. Starlette의 SecurityMiddleware를 사용하거나 커스텀 미들웨어로 헤더를 추가할 수 있습니다.

from starlette.middleware import Middleware
from starlette.middleware.httpsredirect import HTTPSRedirectMiddleware
from starlette.middleware.trustedhost import TrustedHostMiddleware

middleware = [
    Middleware(HTTPSRedirectMiddleware),
    Middleware(TrustedHostMiddleware, allowed_hosts=["example.com", "*.example.com"]),
]

app = FastAPI(middleware=middleware)

@app.middleware("http")
async def add_security_headers(request, call_next):
    response = await call_next(request)
    response.headers["X-Content-Type-Options"] = "nosniff"
    response.headers["X-Frame-Options"] = "DENY"
    response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
    return response

HTTPSRedirectMiddleware는 HTTP 요청을 HTTPS로 리다이렉트합니다. TrustedHostMiddleware는 호스트 헤더 스푸핑을 막습니다. 헤더를 직접 추가하면 브라우저가 클릭재킹 프레임을 막고, MIME 타입 추측을 차단합니다.

선택 확장: 속도 제한과 프록시

핵심 실습으로 시간이 부족했다면 이 섹션은 다음 세션으로 미뤄도 괜찮습니다. 1~2시간 세션에 여유가 있을 때만 아래 내용을 더합니다.

속도 제한(Rate Limiting) 개념

속도 제한은 동일 IP 또는 토큰이 일정 시간 동안 호출할 수 있는 횟수를 제한합니다. Starlette/FastAPI에는 기본 제공되지 않지만, slowapi 같은 라이브러리나 API Gateway 레벨에서 구현할 수 있습니다.

from slowapi import Limiter
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded

limiter = Limiter(key_func=get_remote_address)
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, limiter._rate_limit_exceeded_handler)

@app.get("/search")
@limiter.limit("10/minute")
async def search(q: str):
    return {"results": []}

10/minute 표현은 분당 10회 호출 허용을 의미합니다. 속도 제한 오류가 발생하면 429(Too Many Requests)를 돌려주고 Retry-After 헤더로 재시도 시점을 안내합니다. 간단한 캐싱 레이어나 API Gateway와 조합하면 더 안정적으로 동작합니다.

D2: 클라이언트-프록시-API 흐름

BrowserCDNAPI GatewayFastAPIDB cached static

실무에서는 CDN과 API Gateway가 앞단에서 CORS, 속도 제한, 인증 일부를 처리하고 FastAPI는 세부 도메인 로직에 집중합니다. 그림처럼 흐름을 이해하면 어디서 어떤 책임을 가져가는지 판단하기 쉽습니다.

uvicorn/프록시 설정

HTTPS가 필요한 환경에서는 보통 리버스 프록시(Nginx, Traefik)가 TLS 종료를 담당하고 FastAPI/uvicorn은 내부 네트워크에서 동작합니다. uvicorn app.main:app --proxy-headers --forwarded-allow-ips='*' 옵션을 사용하면 X-Forwarded-* 헤더를 신뢰해 클라이언트 IP를 제대로 파악할 수 있습니다. 프록시 단계에서 IP를 잃어버리면 속도 제한과 감사 로그가 무용지물이 됩니다. CORS와 보안 헤더는 프런트엔드와 API가 안전하게 대화하기 위한 필수 설정입니다. 다음 편에서는 환경 설정과 비밀 관리 전략을 살펴봅니다.

실습

  • 따라 하기: CORSMiddlewareSecurityMiddleware(혹은 커스텀 헤더 미들웨어)를 적용해 허용 도메인과 기본 보안 헤더를 설정한다.
  • 확장하기: 선택 구간에서 slowapi나 API Gateway를 흉내 내는 속도 제한 로직을 붙이고 429 응답을 재현한다.
  • 디버깅: 브라우저 DevTools 네트워크 탭과 curl -H "Origin" 요청으로 허용/차단 케이스를 확인하고 --proxy-headers 옵션을 켠 뒤 로드밸런서 뒤에서도 IP가 올바른지 확인한다.
  • 완료 기준: 허용된 Origin에서는 정상 응답, 차단된 Origin에서는 CORS 오류가 발생하며 최소 한 번은 속도 제한이나 프록시 설정을 점검했다.

마무리

허용 도메인을 명확히 선언하고 보안 헤더를 기본값으로 붙여 두면 초반 구조에서도 브라우저 연동 문제가 크게 줄어듭니다. 속도 제한과 프록시 설정은 선택이지만 언제든 확장할 수 있도록 개념을 미리 익혀 두세요.

💬 댓글

이 글에 대한 의견을 남겨주세요