Webhook 认证 —— 保护您的端点
finlight 提供多种认证方法来保护您的 webhook 投递。默认情况下,每个 webhook 都包含签名验证,并可选择附加的认证层以增强安全性。
finlight 支持四种认证方法,可单独使用或组合使用:
无
除默认的签名验证之外,没有附加认证。
何时使用:
- 测试和开发环境
- 位于安全网络之后的内部端点
- 当签名验证已能提供足够的安全性时
注意: 无论此设置如何,每个 webhook 请求仍会包含签名验证。
X-Finlight-Key 标头
在每个 webhook 请求中,通过自定义的 X-Finlight-Key 标头发送您的 API 密钥。
配置:
- 在 webhook 设置期间提供您的 API 密钥
- 该密钥将被包含在
X-Finlight-Key标头中
实现: 您的端点应验证传入的标头:
const finlightKey = req.headers['x-finlight-key']
if (finlightKey !== 'your-expected-api-key') {
return res.status(401).send('Invalid API key')
}
发送的标头:
X-Finlight-Key: your-api-key-value
X-Webhook-Signature: sha256=signature
X-Webhook-Timestamp: 2024-01-15T10:30:00.000Z
基本认证
使用用户名/密码凭据的 HTTP 基本认证。
配置:
- 在 webhook 设置期间设置用户名和密码
- 凭据经过 base64 编码后在
Authorization标头中发送
实现: 您的端点会收到标准的 HTTP 基本认证:
const auth = req.headers.authorization
if (!auth || !auth.startsWith('Basic ')) {
return res.status(401).send('Missing Basic Auth')
}
const credentials = Buffer.from(auth.slice(6), 'base64').toString()
const [username, password] = credentials.split(':')
if (username !== 'expected-user' || password !== 'expected-pass') {
return res.status(401).send('Invalid credentials')
}
发送的标头:
Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
X-Webhook-Signature: sha256=signature
X-Webhook-Timestamp: 2024-01-15T10:30:00.000Z
签名验证
自动安全保护: 无论您选择哪种认证方法,每个 webhook 请求都包含签名验证。
工作原理:
- finlight 在发送 webhook 时生成一个时间戳
- 通过拼接创建一条消息:
timestamp + '.' + payload - 使用您 webhook 的密钥,通过 HMAC-SHA256 对该消息进行签名
- 在标头中同时发送签名和时间戳
包含的标头:
X-Webhook-Signature: sha256=computed-signature
X-Webhook-Timestamp: 2024-01-15T10:30:00.000Z
签名算法:
message = timestamp + '.' + raw_request_body
signature = HMAC-SHA256(message, webhook_secret)
当不存在 X-Webhook-Timestamp 头时,消息就是原始请求体本身。请以常量时间比较结果,并拒绝时间戳超过五分钟的投递。
请求体必须是原始的、未经解析的请求字节。 任何在您读取之前解码并重新编码 JSON 的中间件都会改变字节序列并使签名失效 —— 这是验证失败最常见的原因。请将请求体读取为原始字符串或缓冲区,先验证,然后再解析。
使用官方客户端验证
每个客户端库都提供了一个辅助方法,为您完成全部检查:它接受签名上的 sha256= 前缀,以常量时间比较,强制执行五分钟的重放窗口,并返回解析后的文章。验证失败时它会抛出异常或返回错误 —— 请始终以 4xx 响应,切勿处理该负载。
验证 webhook
import express from 'express'
import { WebhookService } from 'finlight-client'
const app = express()
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
try {
const article = WebhookService.constructEvent(
req.body.toString(),
req.headers['x-webhook-signature'] as string,
process.env.WEBHOOK_SECRET!,
req.headers['x-webhook-timestamp'] as string,
)
console.log('New article:', article.title)
res.sendStatus(200)
} catch (err) {
console.error('Webhook verification failed:', err)
res.sendStatus(400)
}
})
安全最佳实践
时间戳验证
上面的 constructEvent 辅助方法已经强制执行五分钟的重放窗口,因此仅当您手动验证签名时才需要这样做:
function isTimestampValid(timestamp, toleranceSeconds = 300) {
const now = Date.now()
const requestTime = new Date(timestamp).getTime()
const difference = Math.abs(now - requestTime) / 1000
return difference <= toleranceSeconds
}
安全地存储凭据
- 环境变量:将所有密钥存储在环境变量中
- 密钥管理:使用 AWS Secrets Manager、HashiCorp Vault 或类似工具
- 切勿硬编码:切勿将密钥提交到版本控制
- 定期轮换:定期更新 webhook 密钥
有关 webhook 设置指南,请参阅 webhooks 主文档。有关全面的测试,请查看 webhook 测试指南。