주제

HMAC 검증

HMAC 검증은 FastOG가 모든 구독 기반 OG 이미지 요청이 사용자 본인의 서버에서 왔는지 확인하는 방식입니다. 서명된 URL은 크레딧을 안전하게 보호합니다. 가입 후 Dynamic API 요금제를 선택하고 대시보드에서 API 키와 HMAC 시크릿을 생성하세요. 유료 API 렌더링은 깨끗합니다. 이 페이지는 동적 Open Graph 이미지 뒤에 있는 서버 측 규칙을 다룹니다. 정식 쿼리 서명부터 상수 시간 비교까지 설명합니다.

개요

/api/v1/og 엔드포인트에 HMAC-SHA256 서명 검증이 추가되어 API 할당량의 무단 사용을 방지합니다. 이를 통해 합법적인 웹사이트에서만 OG 이미지를 생성할 수 있습니다.

아키텍처

2계층 인증(Two-layer authentication):

  1. HMAC 서명(HMAC Signature) (첫 번째) - 요청이 신뢰할 수 있는 소스(웹사이트)에서 발생했는지 검증
  2. API 키(API Key) (두 번째) - 렌더링 할당량 계산을 위해 사용자 계정 식별

단일 미들웨어(ApiKeyAuth): 서명(쿼리 파라미터)을 검증하고 렌더링 할당량 계산을 위한 API 키를 한 번에 해석합니다. 잘못된 서명은 즉시 실패합니다.

헤더가 아닌 쿼리 파라미터: OG 이미지는 사용자 지정 헤더를 보내지 않는 소셜 네트워크 크롤러(LinkedIn, Twitter, Facebook)가 가져오므로, 서명은 URL 쿼리 파라미터로 전달됩니다.

GET /api/v1/og?title=Hello&key=sk_quick_123abc&s=<hmac>

구현

생성/수정된 파일

  • app/Http/Middleware/HmacSignature.php - HMAC 서명(쿼리 매개변수)을 검증하는 새 미들웨어
  • routes/api.php - OG 라우트에 HMAC 미들웨어 추가
  • bootstrap/app.php - hmac 미들웨어 별칭 등록
  • app/Models/ApiKey.php - hmac_secret 필드 추가(사용자별 비밀키)
  • app/Http/Controllers/DashboardController.php - API 키 생성 시 HMAC 비밀키 생성
  • database/migrations/2026_08_03_214931_add_hmac_secret_to_api_keys_table.php - hmac_secret 열에 대한 마이그레이션
  • resources/js/Pages/Dashboard/ApiKeys.svelte - HMAC 비밀키를 사용자에게 표시(1회)
  • docs/hmac-signature-guide.md - 클라이언트 구현 가이드
  • tests/Feature/HmacSignatureTest.php - 통과 테스트 8개

서명 알고리즘

method = HTTP method (e.g., "GET")
path  = request path (e.g., "/api/v1/og")
query = the request's DECODED query params

canonical = every param except `s`, keys sorted (array values sorted too),
            each `key=value` RFC-3986 encoded (unreserved-only), joined with "&"
message   = method + "/" + path + "\n" + canonical
signature = HMAC-SHA256(message, hmac_secret)

Query parameters:
  key=<apiKey>
  s=<signature>
  (no timestamp)

key은 정규 쿼리의 일부이므로 서명은 API 키에 바인딩됩니다. 매개변수(제목, 템플릿, 이미지 등)나 키 자체를 변경하면 정규 쿼리가 변경되어 서명 불일치 → 401 → 렌더링 없음, 할당량 차감 없음이 발생합니다. 동일한 매개변수 → 동일한 서명 → 동일한 URL → 엣지에서 영구적으로 캐시 가능하며, 차감 없이 재생됩니다.

보안 기능

  • 콘텐츠 주소 지정 서명 - 매개변수 변경 시 → 401 → 렌더링 없음, 할당량 차감 없음(URL 자체가 증명)
  • 타임스탬프 없음 - URL은 공개 HTML에 포함되고 영구적으로 캐시되므로 5분을 초과하여 유효해야 함
  • 상수 시간 비교(hash_equals()) - 타이밍 공격 방지
  • 메시지 내 API 키 - 서명이 키에 바인딩되어 있어 키 교체 시 실패
  • 경로별 지정 - 서명은 정확한 경로에 대해서만 유효
  • 메서드별 지정 - GET 서명은 POST에 사용할 수 없음
  • 사용자별 비밀키 - 각 API 키는 자체 HMAC 비밀키를 가지며 독립적으로 폐기 가능(회전 = 유출된 URL에 대한 킬 스위치)

구성

시크릿 생성 (자동)

HMAC 시크릿은 대시보드에서 API 키를 생성할 때 자동으로 생성됩니다. API 키와 함께 한 번만 표시되며, 이후에는 다시 표시되지 않습니다.

수동 생성 (필요한 경우):

bash
openssl rand -hex 32

데이터베이스 스키마

sql
ALTER TABLE api_keys ADD COLUMN hmac_secret VARCHAR(64)
  COMMENT 'HMAC-SHA256 secret for signing OG image URLs (hex, 64 chars)';

환경 변수

전역 환경 변수는 필요하지 않습니다. 시크릿은 api_keys 테이블에 사용자별로 저장됩니다.

사용 예시 (웹사이트 백엔드)

javascript
// Node.js example - generate signed OG image URL
const crypto = require("crypto");

// RFC-3986 encode (encodeURIComponent leaves !'()* unencoded).
const enc = (s) =>
    encodeURIComponent(s).replace(
        /[!'()*]/g,
        (c) => "%" + c.charCodeAt(0).toString(16).toUpperCase(),
    );

function generateOgImageUrl(baseUrl, params, apiKey, hmacSecret) {
    const all = { ...params, key: apiKey };

    // Canonical: every param (except s), sorted, RFC-3986 encoded, joined with "&".
    const pairs = [];
    for (const [k, v] of Object.entries(all)) {
        for (const vv of Array.isArray(v) ? [...v].sort() : [v])
            pairs.push(`${enc(k)}=${enc(vv)}`);
    }
    pairs.sort();
    const canonical = pairs.join("&");

    const message = `GET/api/v1/og\n${canonical}`;
    const signature = crypto
        .createHmac("sha256", hmacSecret)
        .update(message)
        .digest("hex");

    const url = new URL(baseUrl);
    for (const [k, v] of Object.entries(all)) {
        const isArray = Array.isArray(v);
        for (const vv of isArray ? v : [v])
            url.searchParams.append(isArray ? `${k}[]` : k, vv);
    }
    url.searchParams.set("s", signature);
    return url.toString();
}

// Usage in meta tag
const ogUrl = generateOgImageUrl(
    "https://fastog.com/api/v1/og",
    { title: "Como criar um SaaS", template: "blog" },
    "sk_quick_123abc",
    process.env.HMAC_SECRET,
);

// <meta property="og:image" content="{ogUrl}" />

오류 응답

상태 코드 설명
401 missing_signature key 또는 s 쿼리 매개변수가 누락되었습니다.
401 invalid_signature 서명이 표준 쿼리와 일치하지 않습니다(변조되었거나 잘못된 비밀 키).

테스팅

테스트 실행:

bash
php artisan test --filter HmacSignatureTest

8개 테스트 모두 통과:

  • ✅ 서명이 없는 요청은 거부됨
  • ✅ 키 매개변수가 누락된 요청은 거부됨
  • ✅ 콘텐츠 매개변수(제목 변경)가 변조된 요청은 거부됨
  • ✅ 추가된 매개변수는 거부됨
  • ✅ 잘못된 서명은 거부됨
  • ✅ 유효한 서명은 HMAC 검사를 통과함
  • ✅ 서명은 경로별로 고유함
  • ✅ 서명은 메서드별로 고유함
  • ✅ 서로 다른 API 키는 서로 다른 시크릿을 가짐

성능 영향

  • 무시할 수 있음 - HMAC-SHA256 계산은 요청당 약 0.1ms입니다.
  • 추가 네트워크 호출 없음 - 검증은 로컬에서 수행됩니다.
  • 빠른 실패 - 잘못된 서명은 API 키 조회 전에 거부됩니다.

보안 고려 사항

  1. 클라이언트 측 코드에 비밀 키를 절대 노출하지 마세요 - 서버에서만 서명하세요
  2. HTTPS를 사용하세요 - HMAC는 무단 요청을 방지하고, HTTPS는 중간자 공격(MITM)을 방지합니다
  3. 비밀 키를 주기적으로 교체하세요 - 특히 유출된 경우에는 반드시 교체해야 합니다(API 키 재생성 필요)
  4. 실패한 서명을 모니터링하세요 - 반복적인 실패는 공격을 나타낼 수 있습니다
  5. 사용자별 격리 - 각 API 키는 고유한 HMAC 비밀 키를 가지므로, 하나가 유출되어도 다른 키에는 영향을 미치지 않습니다

참고 자료

전체 API 참조

무료로 체험해 보세요 — 가입 불필요

몇 초 만에 동적 OG 이미지를 미리 보고 테스트하세요.

무료 OG 이미지 테스터 열기

A BeCaSimulations site