모든 API에 같은 제한을 걸지 않았습니다

7월 27일부터 29일까지 외부 API 보호와 HTTPS 구성을 점검했습니다. 로그인과 토큰 갱신은 반복 호출을 제한할 필요가 있지만, 일반 조회나 CORS preflight까지 같은 제한을 적용하면 정상 클라이언트의 동작이 달라집니다. Cloudflare Workers에서 메서드와 경로를 먼저 선택하고 Rate Limiting binding을 호출하는 방식으로 구현했습니다.

아래 코드는 실제 정책 선택 구조를 로그인 한 경로로 줄인 예제입니다. /example/login과 LOGIN_LIMITER는 예제용 이름이며 binding의 한도와 기간은 Wrangler 설정에서 별도로 지정합니다.

JAVASCRIPT
export default {
  async fetch(request, env) {
    const url = new URL(request.url);
    const isLogin = request.method === "POST" &&
      url.pathname === "/example/login";
    if (!isLogin) return fetch(request);

    const ip = request.headers.get("CF-Connecting-IP") || "unknown";
    const { success } = await env.LOGIN_LIMITER.limit({
      key: `login:${ip}`,
    });
    if (!success) {
      return new Response("Too many requests", {
        status: 429,
        headers: {
          "Cache-Control": "no-store",
          "Retry-After": "60",
        },
      });
    }
    return fetch(request);
  },
};

핵심은 경로별 scope를 IP 앞에 붙여 서로 다른 인증 동작의 한도를 분리하는 점입니다. CF-Connecting-IP는 Cloudflare가 처리하는 Worker 요청이라는 경계 안에서 사용합니다. 임의의 외부 서버에서 클라이언트가 보낸 동일한 이름의 헤더를 그대로 신뢰하는 구현으로 옮기지 않습니다.

429, 401, TLS 실패를 다른 결과로 처리합니다

429 응답은 캐시하지 않고 재시도 대기 시간을 제공합니다. 예제의 60초는 설명용 값이며 실제 limiter 기간과 클라이언트의 재시도 정책에 맞춰야 합니다. 제한을 통과한 요청도 원본 애플리케이션에서 계정 인증과 권한 검사를 수행합니다. 같은 공인 IP를 공유하는 사용자가 있으므로 IP 제한만으로 계정별 남용 방지를 끝내지 않습니다.

검증에서는 선택한 POST가 binding을 호출하는지, OPTIONS와 일반 조회는 원본으로 전달되는지, 제한 초과 시 429와 no-store가 반환되는지 확인했습니다. 원본의 401은 인증 실패이고 원본 연결의 TLS 오류는 전송 장애이므로 같은 복구 정책으로 처리하지 않았습니다.

원본 연결까지 확인하고 HTTPS를 강화합니다

브라우저와 엣지 사이의 HTTPS와 엣지에서 원본으로 연결하는 TLS는 별도 구간입니다. 원본 인증서 검증이 가능한 대상을 확인한 뒤 Full (strict) 적용 범위를 정리했습니다. 사용 중인 요금제에서 제공하지 않는 WAF 기능을 활성화한 것으로 기록하지 않았습니다.

Workers limiter의 호출 형태와 분산된 한도의 특성은 Cloudflare Rate Limiting 문서를 참고합니다. 이 구성은 엣지에서 반복 요청을 줄이는 장치이며 정확한 전역 트랜잭션 한도를 구현하는 장치는 아닙니다.