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 만들려면 명시적 opt-in.

체크 순서가 중요해

Public 먼저 (싸, DB hit 없음), 그다음 killswitch (PIN 비활성), 그다음 bypass (싸), 그다음 blacklist (DB hit 한 번, hit 시 즉시 return), 그다음 session check (가장 느림), 그다음 login redirect. 알려진 bad 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 기록 X
        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 통과해야 해 (다음에 헬퍼 wire).

Progress

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

댓글 0

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

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