HMAC 검증
HMAC 검증은 FastOG가 모든 구독 기반 OG 이미지 요청이 사용자 본인의 서버에서 왔는지 확인하는 방식입니다. 서명된 URL은 크레딧을 안전하게 보호합니다. 가입 후 Dynamic API 요금제를 선택하고 대시보드에서 API 키와 HMAC 시크릿을 생성하세요. 유료 API 렌더링은 깨끗합니다. 이 페이지는 동적 Open Graph 이미지 뒤에 있는 서버 측 규칙을 다룹니다. 정식 쿼리 서명부터 상수 시간 비교까지 설명합니다.
개요
/api/v1/og 엔드포인트에 HMAC-SHA256 서명 검증이 추가되어 API 할당량의 무단 사용을 방지합니다. 이를 통해 합법적인 웹사이트에서만 OG 이미지를 생성할 수 있습니다.
아키텍처
2계층 인증(Two-layer authentication):
- HMAC 서명(HMAC Signature) (첫 번째) - 요청이 신뢰할 수 있는 소스(웹사이트)에서 발생했는지 검증
- 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 키와 함께 한 번만 표시되며, 이후에는 다시 표시되지 않습니다.
수동 생성 (필요한 경우):
openssl rand -hex 32
데이터베이스 스키마
ALTER TABLE api_keys ADD COLUMN hmac_secret VARCHAR(64)
COMMENT 'HMAC-SHA256 secret for signing OG image URLs (hex, 64 chars)';
환경 변수
전역 환경 변수는 필요하지 않습니다. 시크릿은 api_keys 테이블에 사용자별로 저장됩니다.
사용 예시 (웹사이트 백엔드)
// 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 |
서명이 표준 쿼리와 일치하지 않습니다(변조되었거나 잘못된 비밀 키). |
테스팅
테스트 실행:
php artisan test --filter HmacSignatureTest
8개 테스트 모두 통과:
- ✅ 서명이 없는 요청은 거부됨
- ✅ 키 매개변수가 누락된 요청은 거부됨
- ✅ 콘텐츠 매개변수(제목 변경)가 변조된 요청은 거부됨
- ✅ 추가된 매개변수는 거부됨
- ✅ 잘못된 서명은 거부됨
- ✅ 유효한 서명은 HMAC 검사를 통과함
- ✅ 서명은 경로별로 고유함
- ✅ 서명은 메서드별로 고유함
- ✅ 서로 다른 API 키는 서로 다른 시크릿을 가짐
성능 영향
- 무시할 수 있음 - HMAC-SHA256 계산은 요청당 약 0.1ms입니다.
- 추가 네트워크 호출 없음 - 검증은 로컬에서 수행됩니다.
- 빠른 실패 - 잘못된 서명은 API 키 조회 전에 거부됩니다.
보안 고려 사항
- 클라이언트 측 코드에 비밀 키를 절대 노출하지 마세요 - 서버에서만 서명하세요
- HTTPS를 사용하세요 - HMAC는 무단 요청을 방지하고, HTTPS는 중간자 공격(MITM)을 방지합니다
- 비밀 키를 주기적으로 교체하세요 - 특히 유출된 경우에는 반드시 교체해야 합니다(API 키 재생성 필요)
- 실패한 서명을 모니터링하세요 - 반복적인 실패는 공격을 나타낼 수 있습니다
- 사용자별 격리 - 각 API 키는 고유한 HMAC 비밀 키를 가지므로, 하나가 유출되어도 다른 키에는 영향을 미치지 않습니다