본문 바로가기
C.W.K.
Stream
Lesson 02 of 06 · published

Middleware — 모든 요청이 검사받는 곳

~15 min · middleware, fastapi, starlette

Level 0Greenhorn
0 XP0/53 lessons0/14 achievements
0/100 XP to next level100 XP to go0% complete

Middleware 가 모든 요청의 진입점. 라우트마다 decorator 를 다는 대신 여기에 auth 를 두면 새 라우트에 빼먹을 수가 없어. 새 엔드포인트는 기본으로 게이트를 물려받고, public 으로 열려면 따로 표시해야 해.

체크 순서가 중요해

Public 이 먼저야 (싸고 DB 도 안 건드려). 그다음 killswitch (PIN 이 꺼져 있나), bypass (싸), blacklist (DB 한 번, 걸리면 바로 return), session check (제일 느림), 마지막이 login redirect. 이미 알려진 나쁜 IP 한테 403 을 주는 게 session lookup 보다 싸거든.

진짜 client IP 얻기

reverse proxy 뒤에 있으면 request.client.host 는 진짜 client 가 아니라 proxy 의 IP 를 보여줘. X-Forwarded-For 헤더를 봐야 하는데, 그것도 네가 아는 proxy 에서 온 것만 믿어야 해.

Code

FastAPI / Starlette PIN auth middleware·python
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.responses import RedirectResponse, Response

PUBLIC_PREFIXES = ("/login", "/static", "/health")
LOCAL_BYPASS    = {"127.0.0.1", "::1"}      # trusted LAN IP 추가 가능

class PinAuthMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request, call_next):
        path = request.url.path
        ip   = client_ip(request)

        # 1. Public 라우트 — 항상 허용
        if any(path.startswith(p) for p in PUBLIC_PREFIXES):
            return await call_next(request)

        # 2. PIN 전역 비활성? 전부 bypass (조심해서 사용!)
        if not config_pin_enabled():
            return await call_next(request)

        # 3. Loopback / 명시적 trusted local IP 는 auth bypass
        if ip in LOCAL_BYPASS:
            return await call_next(request)

        # 4. Hard block: blacklisted IP 는 403, attempt 기록 안 함
        if is_blacklisted(ip):
            return Response("Forbidden", status_code=403)

        # 5. Valid session 쿠키? user attach, 통과
        token = request.cookies.get("session")
        if token and session_valid(token, ip):
            request.state.authed = True
            return await call_next(request)

        # 6. 인증 안 됨 — login 으로 (HTML) 또는 401 (API)
        if request.headers.get("accept", "").startswith("text/html"):
            return RedirectResponse("/login", status_code=302)
        return Response("Unauthorized", status_code=401)
trusted proxy 한테서만 진짜 client IP·python
TRUSTED_PROXIES = {"127.0.0.1", "::1"}

def client_ip(request) -> str:
    direct = request.client.host
    if direct in TRUSTED_PROXIES:
        xff = request.headers.get("x-forwarded-for", "")
        if xff:
            return xff.split(",")[0].strip()
    return direct

External links

Exercise

(헬퍼 구현 전에) PinAuthMiddleware 를 public 라우트 /health 와 보호된 라우트 / 가진 작은 FastAPI 앱에 넣어. 확인: GET /health 가 200, GET / 가 /login 으로 302. auth 헬퍼가 없어도 control flow 는 통과해야 해 (헬퍼는 다음에 붙일 거야).

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.