{"meta":{"title":"验证 Webhook 交付","intro":"可以使用 Webhook 机密来验证 Webhook 传递是否来自 GitHub。","product":"Webhook","breadcrumbs":[{"href":"/zh/webhooks","title":"Webhook"},{"href":"/zh/webhooks/using-webhooks","title":"使用网络钩子（Webhook）"},{"href":"/zh/webhooks/using-webhooks/validating-webhook-deliveries","title":"验证交付"}],"documentType":"article"},"body":"# 验证 Webhook 交付\n\n可以使用 Webhook 机密来验证 Webhook 传递是否来自 GitHub。\n\n## 关于验证 Webhook 交付\n\n一旦服务器配置为接收有效负载，它将侦听发送到你配置的端点的任何交付。 为了确保服务器仅处理发送的 GitHub Webhook 传送，并确保传递不会被篡改，则应在进一步处理传递之前验证 Webhook 签名。 这有助于避免花费服务器时间来处理不是来自 GitHub 的交付，并有助于避免中间人攻击。\n\n若要实现此目的，需要：\n\n1. 为 Webhook 创建机密令牌。\n2. 将令牌安全存储在服务器上。\n3. 根据令牌验证传入的 Webhook 有效负载，以验证它们是否来自 GitHub 且未被篡改。\n\n## 创建机密令牌\n\n可以使用机密令牌来创建新的 Webhook，也可以为现有 Webhook 添加机密令牌。 在创建机密令牌时，应选择高熵的随机文本字符串。\n\n* *若要使用机密令牌创建新的 Webhook*，请参阅“[创建网络钩子](/zh/webhooks/using-webhooks/creating-webhooks)”。\n* *若要为现有 Webhook 添加机密令牌*，请编辑 Webhook 的设置。 在“机密”下，键入用作 `secret` 密钥的字符串。 有关详细信息，请参阅“[测试 Webhook](/zh/webhooks/using-webhooks/editing-webhooks)”。\n\n## 以安全的方式存储机密令牌\n\n创建机密令牌后，应将其存储在服务器能够访问的安全位置。 切勿将令牌硬编码到应用程序，或将令牌推送到任何存储库。 有关如何在代码中以安全的方式使用身份验证凭据的详细信息，请参阅“[确保 API 凭据安全](/zh/rest/authentication/keeping-your-api-credentials-secure#use-authentication-credentials-securely-in-your-code)”。\n\n## 验证 Webhook 交付\n\nGitHub 将使用你的机密令牌创建哈希签名，并随每个有效负载一并发送给你。 哈希签名将作为 `X-Hub-Signature-256` 标头的值出现在每个交付中。 有关详细信息，请参阅“[Webhook 事件和有效负载](/zh/webhooks/webhook-events-and-payloads#delivery-headers)”。\n\n在处理 Webhook 交付的代码中，应使用机密令牌计算哈希。 然后，将发送的 GitHub 哈希与计算的预期哈希进行比较，并确保它们匹配。 有关如何在各种编程语言中验证哈希的示例，请参阅[示例](#examples)。\n\n验证 Webhook 有效负载时，必须记住一些重要事项：\n\n* GitHub 使用 HMAC 十六进制摘要计算哈希。\n* 哈希签名始终以 `sha256=` 开头。\n* 哈希签名是使用您的 Webhook 的密钥令牌和有效负载内容生成的。\n* 如果你的语言和服务器实现指定了字符编码，请确保将有效负载处理为 UTF-8。 Webhook 有效负载可以包含 unicode 字符。\n* 切勿使用纯 `==` 运算符。 相反，请考虑使用 [`secure_compare`](https://www.rubydoc.info/gems/rack/Rack%2FUtils:secure_compare) 或 [`crypto.timingSafeEqual`](https://nodejs.org/api/crypto.html#cryptotimingsafeequala-b) 等方法，它们会执行“恒定时间”字符串比较，这有助于缓解针对常规相等运算符的某些定时攻击，或 JIT 优化语言中的常规循环。\n\n### 测试 Webhook 有效负载验证\n\n可以使用以下的 `secret` 和 `payload` 值来验证实现是否正确：\n\n* `secret`: `It's a Secret to Everybody`\n* `payload`: `Hello, World!`\n\n如果实现正确，则生成的签名应与以下签名值匹配：\n\n* 签名： `757107ea0eb2509fc211221cce984b8a37570b6d7586c22c46f4379c8b043e17` <!-- markdownlint-disable-line GHD034 -->\n* X-Hub-Signature-256： `sha256=757107ea0eb2509fc211221cce984b8a37570b6d7586c22c46f4379c8b043e17`\n\n### 示例\n\n可以使用你首选的编程语言，在代码中实现 HMAC 验证。 下面是一些示例，展示在各种编程语言中的实现。\n\n#### Ruby 示例\n\n例如，可以定义以下 `verify_signature` 函数：\n\n```ruby\ndef verify_signature(payload_body)\n  signature = 'sha256=' + OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), ENV['SECRET_TOKEN'], payload_body)\n  return halt 500, \"Signatures didn't match!\" unless Rack::Utils.secure_compare(signature, request.env['HTTP_X_HUB_SIGNATURE_256'])\nend\n```\n\n然后，可以在收到 Webhook 有效负载时调用它：\n\n```ruby\npost '/payload' do\n  request.body.rewind\n  payload_body = request.body.read\n  verify_signature(payload_body)\n  push = JSON.parse(payload_body)\n  \"I got some JSON: #{push.inspect}\"\nend\n```\n\n#### Python示例\n\n例如，可以定义以下 `verify_signature` 函数，并在收到 Webhook 有效负载时调用它：\n\n```python\nimport hashlib\nimport hmac\ndef verify_signature(payload_body, secret_token, signature_header):\n    \"\"\"Verify that the payload was sent from GitHub by validating SHA256.\n\n    Raise and return 403 if not authorized.\n\n    Args:\n        payload_body: original request body to verify (request.body())\n        secret_token: GitHub app webhook token (WEBHOOK_SECRET)\n        signature_header: header received from GitHub (x-hub-signature-256)\n    \"\"\"\n    if not signature_header:\n        raise HTTPException(status_code=403, detail=\"x-hub-signature-256 header is missing!\")\n    hash_object = hmac.new(secret_token.encode('utf-8'), msg=payload_body, digestmod=hashlib.sha256)\n    expected_signature = \"sha256=\" + hash_object.hexdigest()\n    if not hmac.compare_digest(expected_signature, signature_header):\n        raise HTTPException(status_code=403, detail=\"Request signatures didn't match!\")\n```\n\n#### JavaScript 示例\n\n例如，可以定义以下 `verifySignature` 函数，并在接收 Webhook 有效负载时在任何 JavaScript 环境中进行调用：\n\n```javascript\nlet encoder = new TextEncoder();\n\nasync function verifySignature(secret, header, payload) {\n    let parts = header.split(\"=\");\n    let sigHex = parts[1];\n\n    let algorithm = { name: \"HMAC\", hash: { name: 'SHA-256' } };\n\n    let keyBytes = encoder.encode(secret);\n    let extractable = false;\n    let key = await crypto.subtle.importKey(\n        \"raw\",\n        keyBytes,\n        algorithm,\n        extractable,\n        [ \"sign\", \"verify\" ],\n    );\n\n    let sigBytes = hexToBytes(sigHex);\n    let dataBytes = encoder.encode(payload);\n    let equal = await crypto.subtle.verify(\n        algorithm.name,\n        key,\n        sigBytes,\n        dataBytes,\n    );\n\n    return equal;\n}\n\nfunction hexToBytes(hex) {\n    let len = hex.length / 2;\n    let bytes = new Uint8Array(len);\n\n    let index = 0;\n    for (let i = 0; i < hex.length; i += 2) {\n        let c = hex.slice(i, i + 2);\n        let b = parseInt(c, 16);\n        bytes[index] = b;\n        index += 1;\n    }\n\n    return bytes;\n}\n```\n\n#### TypeScript 示例\n\n例如，可以定义以下 `verify_signature` 函数，并在收到 Webhook 有效负载时调用它：\n\n```javascript copy\nimport { Webhooks } from \"@octokit/webhooks\";\n\nconst webhooks = new Webhooks({\n  secret: process.env.WEBHOOK_SECRET,\n});\n\nconst handleWebhook = async (req, res) => {\n  const signature = req.headers[\"x-hub-signature-256\"];\n  const body = await req.text();\n\n  if (!(await webhooks.verify(body, signature))) {\n    res.status(401).send(\"Unauthorized\");\n    return;\n  }\n\n  // The rest of your logic here\n};\n```\n\n## 故障排除\n\n如果确定有效负载来自 GitHub 但签名验证失败：\n\n* 请确保已为 Webhook 配置机密。 如果尚未为 Webhook 配置机密，`X-Hub-Signature-256` 标头将不存在。 有关为 Webhook 配置机密的详细信息，请参阅“[测试 Webhook](/zh/webhooks/using-webhooks/editing-webhooks)”。\n* 请确保使用正确标头。 GitHub 建议使用 `X-Hub-Signature-256` 标头，该标头使用 HMAC-SHA256 算法。 `X-Hub-Signature` 标头使用 HMAC-SHA1 算法，仅用于旧用途。\n* 请确保使用正确算法。 如果使用 `X-Hub-Signature-256` 标头，则应使用 HMAC-SHA256 算法。\n* 请确保使用正确的 Webhook 机密。 如果不知道 Webhook 机密的值，可以更新 Webhook 机密。 有关详细信息，请参阅“[测试 Webhook](/zh/webhooks/using-webhooks/editing-webhooks)”。\n* 在验证之前，请确保不会修改有效负载和标头。 例如，如果使用代理或负载均衡器，请确保代理或负载均衡器不会修改有效负载或标头。\n* 如果你的语言和服务器实现指定了字符编码，请确保将有效负载处理为 UTF-8。 Webhook 有效负载可以包含 unicode 字符。\n\n## 其他阅读材料\n\n* [处理 Webhook 交付](/zh/webhooks/using-webhooks/handling-webhook-deliveries)\n* [使用 Webhook 的最佳做法](/zh/webhooks/using-webhooks/best-practices-for-using-webhooks)"}