在 Cloudflare 中, 若是有一組自己的 API, 想要簡單地透過 Cloudflare 來保護, 有很多方法, 不過大多需要是付費的版本才能提供, 像是 API Shield.
若是想要快速而輕量地達成這個 API的保護, 可以透過 Worker 來檢查 x-signature 的 header 來實現.
在 Cloudflare 的 Workers & Pages 介面, Create application, 然後給定一個名稱如 api-check, 然後在右上角的 Edit Code 進入程式編輯器.
輸入以下程式碼:
const EXPIRY_SECONDS = 300;
export default {
async fetch(request, env, ctx) {
// 1. 將您的秘密金鑰設定在環境變數 (安全起見建議後續設定為 Secret)
const SECRET_KEY = env.API_SECRET_KEY;
const url = new URL(request.url);
const apiPath = url.pathname;
const host = url.host;
// 2. 擷取外部夥伴傳入的 X-Signature 標頭
const xSignature = request.headers.get("X-Signature");
if (!xSignature) {
return new Response(JSON.stringify({ error: "Unauthorized: Missing X-Signature header" }), {
status: 401,
headers: { "Content-Type": "application/json" }
});
}
// 3. 解析標頭格式 (預期格式: token-timestamp)
const parts = xSignature.split("-");
if (parts.length !== 2) {
return new Response(JSON.stringify({ error: "Unauthorized: Invalid signature format" }), {
status: 401,
headers: { "Content-Type": "application/json" }
});
}
const [clientToken, timestampStr] = parts;
const clientTimestamp = parseInt(timestampStr, 10);
// 4. 驗證時間戳記是否超時 (防止重放攻擊)
const currentTimestamp = Math.floor(Date.now() / 1000);
if (Math.abs(currentTimestamp - clientTimestamp) > EXPIRY_SECONDS) {
return new Response(JSON.stringify({ error: "Unauthorized: Signature expired" }), {
status: 401,
headers: { "Content-Type": "application/json" }
});
}
// 5. 使用 Web Crypto API 在本端重新計算 HMAC-SHA256
// 簽章組合公式:路徑 + 網域 + 時間戳記 (符合原 WAF v0 規範)
const messageStr = `${apiPath}${host}${clientTimestamp}`;
try {
const encoder = new TextEncoder();
const keyData = encoder.encode(SECRET_KEY);
const messageData = encoder.encode(messageStr);
const cryptoKey = await crypto.subtle.importKey(
"raw",
keyData,
{ name: "HMAC", hash: "SHA-256" },
false,
["sign"]
);
const signatureBuffer = await crypto.subtle.sign("HMAC", cryptoKey, messageData);
// 將計算結果轉為十六進位字串 (Hex)
const serverToken = Array.from(new Uint8Array(signatureBuffer))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
// 6. 比對客戶端與伺服器端的 Token 是否一致
if (clientToken !== serverToken) {
return new Response(JSON.stringify({ error: "Unauthorized: Signature mismatch" }), {
status: 401,
headers: { "Content-Type": "application/json" }
});
}
} catch (err) {
return new Response(JSON.stringify({ error: "Internal Server Error during verification" }), {
status: 500,
headers: { "Content-Type": "application/json" }
});
}
// 7. 驗證通過,將請求原封不動轉發 (Proxy) 給後端的真實源站
// 註:fetch(request) 會保留原始的 URL、Header 與 Body
return fetch(request);
}
};
其中的 EXPIRY_SECONDS 就是限制這個 x-signature 效期只有 300 秒, 你可以依實際的狀況增加或減少這個過期秒數設定, 完成後按下 deploy.
然後我們要將這個程式中用到的 API_SECRET_KEY 放在 Workers & Pages 的 Setting 中的 Variables, 所以按下左上角回到 Workers & Pages 頁面, 切換到 Setting 頁籤, 在最上面的 Variables & Secrets 中, add 一個 Secret 的變數, 內容請自訂, 如 x-123-456-789, 一樣要 deploy 即可, 到這裡完成 Workers 的程式準備.
接下來要透過 Domains 來綁定對應的 API 服務, 若你原本的 API 服務是在 myapi.example.com/api/v1/xxxx 這樣, 你可以在 Workers & Pages 下的 Domains 中, Add Domain, 設定對應的主域名, 然後選擇 Route Pattern, 指定 myapi.example.com/api/* 這樣的路徑, 按下 add route 完成設定, 到這裡就把 server 端在 Cloudflare 上的配置設定完成了.
接下來就是 Client 的部分了, 由於有自定義了 x-signature header, 所以我們就是利用自己的程式來實作這個部分, 把 x-signature header 做出來, 再進行訪問即可, Python 程式如下:
import hmac
import hashlib
import time
import requests
from urllib.parse import urlparse
# 1. 設定基本資料
SECRET_KEY = "x-123-456-789"
api_url = "https://myapi.example.com/api/v1/users"
parsed_url = urlparse(api_url)
api_path = parsed_url.path
host = parsed_url.hostname
# 2. 取得當前 Unix 時間戳記 (秒)
current_time = int(time.time())
# 3. 依照 Cloudflare 規範組合簽章訊息 (路徑 + 時間戳記)
# message = f"{api_path}{current_time}"
message = f"{api_path}{host}{current_time}"
# 4. 使用 HMAC-SHA256 進行雜湊,並轉為十六進位字串
token = hmac.new(
SECRET_KEY.encode('utf-8'),
message.encode('utf-8'),
hashlib.sha256
).hexdigest()
# 5. 將「Token」與「時間戳記」用格式 `token-timestamp` 組合起來
# 這是 Cloudflare is_timed_hmac_valid_v1 函數要求的標準格式
x_signature = f"{token}-{current_time}"
# 6. 帶入 Header 發出請求
headers = {
"X-Signature": x_signature
}
response = requests.get(api_url, headers=headers)
print(response.json())
其中的 SECRET_KEY 與前面 Cloudflare 中設的 API_SECRET_KEY 設定為一樣的就可以了, 這樣就可以在不傳遞 shared key, 又可以有效地透過這個簡易的 x-signature header 來訪問 API了.