ความปลอดภัยและลายเซ็น HMAC
ทุกคำขอเว็บฮุกที่ Savebase ส่งจะลงลายเซ็นด้วย HMAC-SHA256 เพื่อให้คุณยืนยันได้ว่ามาจาก Savebase จริงและไม่ถูกแก้ไขระหว่างส่ง คุณควรตรวจสอบลายเซ็นนี้ทุกครั้งก่อนประมวลผลเหตุการณ์เว็บฮุก
การลงลายเซ็นทำงานอย่างไร
Savebase คำนวณลายเซ็นโดยใช้ HMAC-SHA256 กับเนื้อหาคำขอดิบ โดยใช้ค่าของเว็บฮุกคือ คีย์ลับลงลายเซ็น เป็นคีย์ ผลลัพธ์จะถูกส่งใน X-Savebase-Signature เฮดเดอร์ โดยอยู่ในรูปแบบ sha256=<hex-digest>.
ใช้เนื้อหาคำขอดิบ
คำนวณ HMAC จากสิ่งนี้เสมอ: ไบต์ของเนื้อหาคำขอแบบดิบที่ยังไม่แก้ไข — ไม่ใช่ออบเจ็กต์ JSON ที่แปลงกลับเป็นข้อความใหม่ การแยกวิเคราะห์และแปลง JSON กลับเป็นข้อความอาจเปลี่ยนช่องว่างและลำดับคีย์ ทำให้การตรวจสอบลายเซ็นล้มเหลว
| ส่วนหัว | รูปแบบค่า |
|---|---|
X-Savebase-Signature | sha256=abcdef0123... |
X-Savebase-Event | post.saved |
User-Agent | Savebase-Webhook/1.0 |
Node.js / Express
bash
const crypto = require('crypto');
function verifySignature(rawBody, signatureHeader, secret) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected, 'utf8'),
Buffer.from(signatureHeader, 'utf8')
);
}
app.post(
'/savebase-webhook',
express.raw({ type: 'application/json' }),
(req, res) => {
const sig = req.headers['x-savebase-signature'];
if (!sig || !verifySignature(req.body, sig, process.env.SAVEBASE_WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(req.body);
console.log('Event:', event.event, event.data.id);
res.sendStatus(200);
}
);Python / Flask
bash
import hmac, hashlib, os
from flask import Flask, request, abort
app = Flask(__name__)
WEBHOOK_SECRET = os.environ['SAVEBASE_WEBHOOK_SECRET']
@app.route('/savebase-webhook', methods=['POST'])
def handle_webhook():
sig_header = request.headers.get('X-Savebase-Signature', '')
raw_body = request.get_data()
expected = 'sha256=' + hmac.new(
WEBHOOK_SECRET.encode('utf-8'),
raw_body,
hashlib.sha256
).hexdigest()
if not hmac.compare_digest(expected, sig_header):
abort(401)
payload = request.get_json()
print('Received event:', payload['event'])
return '', 200PHP
bash
<?php
$secret = getenv('SAVEBASE_WEBHOOK_SECRET');
$rawBody = file_get_contents('php://input');
$sigHeader = $_SERVER['HTTP_X_SAVEBASE_SIGNATURE'] ?? '';
$expected = 'sha256=' . hash_hmac('sha256', $rawBody, $secret);
if (!hash_equals($expected, $sigHeader)) {
http_response_code(401);
exit('Invalid signature');
}
$event = json_decode($rawBody, true);
error_log('Received: ' . $event['event']);
http_response_code(200);แนวปฏิบัติด้านความปลอดภัย
- ตอบอย่างรวดเร็ว ส่งคืน
200 OKภายใน 10 วินาที Savebase จะหมดเวลาหลัง 10 วินาทีและระบุว่าการส่งล้มเหลว แยกงานหนักไปทำในงานเบื้องหลัง - อย่าบันทึกความลับสำหรับลงนามลงในบันทึกระบบ ดูแลเหมือนรหัสผ่าน เปลี่ยนได้ที่
Settings → Webhooksหากคุณเชื่อว่ามีผู้อื่นล่วงรู้คีย์แล้ว - ใช้ HTTPS เท่านั้น Savebase ปฏิเสธปลายทาง HTTP ที่ไม่เข้ารหัส
- ใช้การเปรียบเทียบที่ต้านการโจมตีจากเวลา ใช้สิ่งนี้เสมอ:
crypto.timingSafeEqual(Node),hmac.compare_digest(Python) หรือhash_equals(PHP) — ห้ามใช้เพียง===. - จัดการความล้มเหลวด้วยตัวเอง Savebase บันทึกผลการส่ง แต่ระบบปัจจุบันไม่ลองใหม่หรือเล่นคำขอที่ล้มเหลวซ้ำโดยอัตโนมัติ