主题

HMAC 验证

HMAC 验证是 FastOG 用来确认每个基于订阅的 OG 图片请求均来自您自己服务器的方式——签名 URL 可确保您的额度安全。请先注册,然后在仪表板中选择动态 API 套餐并创建您的 API 密钥和 HMAC 密钥;付费 API 渲染是干净的。本页涵盖动态 Open Graph 图片背后的服务器端规则,从规范查询签名到恒定时间比较。

概述

/api/v1/og 端点已加入 HMAC-SHA256 签名验证,以防止 API 配额被未经授权使用。这可确保只有来自合法网站的请求才能生成 OG 图片。

架构

两层认证:

  1. HMAC 签名(第一层)——验证请求是否来自可信来源(网站)
  2. API 密钥(第二层)——识别用户账户以进行渲染配额统计

单一中间件(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 密钥(仅一次)
  • 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 的紧急开关)

配置

生成密钥(自动)

在仪表盘中创建 API 密钥时,HMAC 密钥会自动生成。它仅与 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 项测试均通过:

  • ✅ 无签名的请求被拒绝
  • ✅ 缺少 key 参数的请求被拒绝
  • ✅ 内容参数被篡改(标题已更改)的请求被拒绝
  • ✅ 添加参数的请求被拒绝
  • ✅ 无效签名被拒绝
  • ✅ 有效签名通过 HMAC 校验
  • ✅ 签名与路径相关
  • ✅ 签名与请求方法相关
  • ✅ 不同的 API 密钥具有不同的密钥值

性能影响

  • 可忽略不计 - HMAC-SHA256 计算每次请求约 0.1 毫秒
  • 无额外网络调用 - 验证在本地完成
  • 快速失败 - 无效签名在 API 密钥查找之前即被拒绝

安全注意事项

  1. 切勿在客户端代码中暴露密钥 - 仅在服务器端进行签名
  2. 使用 HTTPS - HMAC 可防止未经授权的请求,HTTPS 可防止中间人攻击(MITM)
  3. 定期轮换密钥 - 尤其是在密钥泄露的情况下(需要重新生成 API 密钥)
  4. 监控签名失败情况 - 反复失败可能表明存在攻击
  5. 按用户隔离 - 每个 API 密钥拥有独立的 HMAC 密钥,单个密钥泄露不会影响其他用户

参考资料

完整 API 参考

免费试用——无需注册

在几秒钟内预览和测试动态 OG 图片。

打开免费的 OG 图片测试器

A BeCaSimulations site