HMAC 驗證
HMAC 驗證是 FastOG 用來確認每個基於訂閱的 OG 圖片請求皆來自您自己伺服器的方式——簽署的 URL 能保護您的額度安全。請先註冊,然後選擇 Dynamic API 方案,並在儀表板中建立您的 API 金鑰與 HMAC 密鑰;付費 API 的渲染結果是乾淨的。本頁涵蓋動態 Open Graph 圖片背後的伺服器端規則,從標準查詢簽署到常數時間比較。
概述
/api/v1/og 端點已加入 HMAC-SHA256 簽章驗證,以防止 API 配額遭到未授權使用。此機制可確保只有來自合法網站的請求才能產生 OG 圖片。
架構
兩層驗證:
- HMAC 簽章(第一層)— 驗證請求確實來自受信任的來源(網站本身)
- API 金鑰(第二層)— 識別使用者帳號,用於渲染配額(render-quota)計量
單一中間件(ApiKeyAuth): 在一次處理中同時驗證簽章(查詢參數)並解析 API 金鑰以進行渲染配額計量 — 遇到無效簽章時立即失敗(fail fast)。
使用查詢參數,而非標頭: 由於 OG 圖片是由社群網路爬蟲(LinkedIn、Twitter、Facebook)擷取的,而這些爬蟲不會傳送自訂標頭,因此簽章是透過 URL 查詢參數傳遞:
GET /api/v1/og?title=Hello&key=sk_quick_123abc&s=<hmac>
實作
建立/修改的檔案
app/Http/Middleware/HmacSignature.php- 新的中介層(middleware),驗證 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 欄位的遷移(migration)resources/js/Pages/Dashboard/ApiKeys.svelte- 向使用者顯示 HMAC 密鑰(僅一次)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 是正規查詢(canonical query)的一部分,因此簽章與 API 金鑰綁定。變更任何參數(標題、範本、圖片等)或金鑰本身,都會改變正規查詢 → 簽章不符 → 401 → 不渲染、不計配額。參數相同 → 簽章相同 → URL 相同 → 可在邊緣快取(edge)永久儲存,重播時不計次數。
安全功能
- 內容定址簽章 - 任何參數變更 → 401 → 不渲染、不計配額(URL 本身就是證明)
- 無時間戳 - URL 嵌入公開 HTML 並永久快取;必須能存活超過 5 分鐘
- 常數時間比較(
hash_equals()) - 防止計時攻擊(timing attack) - 訊息中包含 API 金鑰 - 簽章與金鑰綁定;更換金鑰即失效
- 路徑限定 - 簽章僅對確切路徑有效
- 方法限定 - GET 簽章無法用於 POST
- 每個使用者各自的密鑰 - 每個 API 金鑰有獨立的 HMAC 密鑰,可獨立撤銷(輪換=已外洩 URL 的緊急停止開關)
設定
產生密鑰(自動)
在儀表板中建立 API 金鑰時,系統會自動產生 HMAC 密鑰。該密鑰會與 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 項測試皆通過:
- ✅ 沒有簽章的請求會被拒絕
- ✅ 缺少 key 參數的請求會被拒絕
- ✅ 內容參數遭竄改(title 被更改)會被拒絕
- ✅ 新增參數會被拒絕
- ✅ 無效簽章會被拒絕
- ✅ 有效簽章通過 HMAC 檢查
- ✅ 簽章與路徑相關
- ✅ 簽章與方法相關
- ✅ 不同的 API 金鑰有不同的密鑰
效能影響
- 可忽略不計 — HMAC-SHA256 計算每次請求約 0.1 毫秒
- 無額外網路呼叫 — 驗證在本地端進行
- 快速失敗 — 無效簽章會在 API 金鑰查詢前即被拒絕
安全考量
- 切勿在客戶端程式碼中暴露密鑰 — 僅在伺服器端簽章
- 使用 HTTPS — HMAC 防止未授權請求,HTTPS 防止中間人攻擊(MITM)
- 定期輪換密鑰 — 特別是在密鑰可能遭洩漏時(需重新產生 API 金鑰)
- 監控簽章失敗 — 重複失敗可能表示遭受攻擊
- 依使用者隔離 — 每個 API 金鑰擁有各自的 HMAC 密鑰,單一密鑰遭洩漏不會影響其他使用者