API 保護¶
ApiProtection 子系統為 Cloudflare Workers 上的 PDF 生成端點提供雙層防護:API 金鑰驗證確保只有授權客戶端可存取,KV-based 速率限制防止端點被過度呼叫或濫用。
PHP Compatibility
This example uses PHP 8.5 syntax. If your environment runs PHP 8.1 or 7.4, use NextPDF Backport for a backward-compatible build.
API 金鑰驗證¶
設定 API 金鑰驗證器¶
<?php
declare(strict_types=1);
use NextPDF\Cloudflare\ApiProtection\ApiKeyValidator;
use NextPDF\Cloudflare\ApiProtection\Config\ApiKeyConfig;
$validator = ApiKeyValidator::create(
config: ApiKeyConfig::create(
headerName: 'X-NextPDF-API-Key', // 客戶端傳遞金鑰的 Header 名稱
kvNamespace: $env['KV_API_KEYS'], // Cloudflare KV Namespace binding
keyPrefix: 'api_key:', // KV 中的鍵名前綴
hashAlgorithm: 'sha256', // 儲存雜湊值,非明文
cacheSeconds: 60, // 本地快取驗證結果(秒)
),
);
在 Worker Handler 中驗證¶
function handle(array $request, array $env): array
{
$validator = ApiKeyValidator::create(/* ... */);
// 驗證 API 金鑰;失敗時拋出 ApiKeyInvalidException
$apiKeyContext = $validator->validate($request['headers']['x-nextpdf-api-key'] ?? '');
// $apiKeyContext 包含客戶端識別資訊(用於速率限制)
$clientId = $apiKeyContext->clientId(); // non-empty-string
// ... 繼續 PDF 生成
}
API 金鑰管理¶
API 金鑰以 SHA-256 雜湊值儲存於 Cloudflare KV,使用 Wrangler CLI 管理:
# 生成新 API 金鑰
openssl rand -hex 32
# 計算雜湊值並存入 KV
echo -n "your-api-key-here" | sha256sum
wrangler kv:key put --binding=KV_API_KEYS \
"api_key:a1b2c3d4..." \
'{"clientId":"customer-42","plan":"pro","enabled":true}'
# 停用金鑰
wrangler kv:key put --binding=KV_API_KEYS \
"api_key:a1b2c3d4..." \
'{"clientId":"customer-42","plan":"pro","enabled":false}'
KV 中儲存的金鑰元資料格式:
{
"clientId": "customer-42",
"plan": "pro",
"enabled": true,
"rateLimit": {
"requestsPerMinute": 60,
"requestsPerDay": 1000
},
"createdAt": "2026-03-04T00:00:00Z"
}
速率限制¶
設定速率限制器¶
use NextPDF\Cloudflare\ApiProtection\RateLimiter;
use NextPDF\Cloudflare\ApiProtection\Config\RateLimitConfig;
$rateLimiter = RateLimiter::create(
config: RateLimitConfig::create(
kvNamespace: $env['KV_RATE_LIMITS'], // 速率計數器 KV Namespace
windowSeconds: 60, // 滑動視窗大小(秒)
maxRequests: 60, // 視窗內最大請求數
keyPrefix: 'rate:', // KV 計數器鍵前綴
algorithm: 'sliding_window', // 演算法:sliding_window 或 fixed_window
),
);
執行速率限制¶
use NextPDF\Cloudflare\ApiProtection\RateLimitResult;
$result = $rateLimiter->check(
clientId: $apiKeyContext->clientId(),
cost: 1, // 此請求消耗的配額單位(大型 PDF 可設為較高值)
);
if ($result->exceeded()) {
return [
'status' => 429,
'headers' => [
'Content-Type' => 'application/json',
'Retry-After' => (string) $result->retryAfterSeconds(),
'X-RateLimit-Limit' => (string) $result->limit(),
'X-RateLimit-Remaining' => '0',
'X-RateLimit-Reset' => (string) $result->resetAt()->getTimestamp(),
],
'body' => json_encode(['error' => 'rate_limit_exceeded', 'retryAfter' => $result->retryAfterSeconds()]),
];
}
組合使用(完整保護流程)¶
<?php
declare(strict_types=1);
use NextPDF\Cloudflare\ApiProtection\ApiKeyValidator;
use NextPDF\Cloudflare\ApiProtection\RateLimiter;
use NextPDF\Cloudflare\EdgeRenderer;
use NextPDF\Cloudflare\Http\WorkerResponse;
use NextPDF\Cloudflare\ApiProtection\Exception\ApiKeyInvalidException;
use NextPDF\Cloudflare\ApiProtection\Exception\RateLimitExceededException;
function handle(array $request, array $env): array
{
$validator = ApiKeyValidator::create(/* config */);
$rateLimiter = RateLimiter::create(/* config */);
$renderer = EdgeRenderer::create(/* config */);
try {
// 第一層:驗證 API 金鑰
$ctx = $validator->validate($request['headers']['x-nextpdf-api-key'] ?? '');
// 第二層:速率限制
$rateLimiter->enforce(clientId: $ctx->clientId(), cost: 1);
// 生成 PDF
$pdfBytes = $renderer->render(function (\NextPDF\Core\Document $doc): void {
$doc->addPage()->text('Protected PDF', x: 20, y: 30);
});
return WorkerResponse::pdf(bytes: $pdfBytes, filename: 'document.pdf');
} catch (ApiKeyInvalidException) {
return WorkerResponse::error(status: 401, message: 'invalid_api_key');
} catch (RateLimitExceededException $e) {
return WorkerResponse::error(status: 429, message: 'rate_limit_exceeded', headers: [
'Retry-After' => (string) $e->retryAfterSeconds(),
]);
}
}
CORS 設定¶
對於需要從瀏覽器直接呼叫的端點:
use NextPDF\Cloudflare\ApiProtection\CorsConfig;
use NextPDF\Cloudflare\Http\WorkerResponse;
$cors = CorsConfig::create(
allowedOrigins: ['https://app.example.com'],
allowedMethods: ['POST'],
allowedHeaders: ['X-NextPDF-API-Key', 'Content-Type'],
maxAge: 86400,
);
// 處理 OPTIONS preflight 請求
if ($request['method'] === 'OPTIONS') {
return WorkerResponse::cors($cors);
}
參見¶
- Cloudflare 套件總覽 — 套件架構概覽
- Edge 渲染 — PDF 生成
- R2 封存 — 生成後的 PDF 儲存