如何使用Cloudflare Worker验证服务端Node生成的预签名URL
报错根因说明
你遇到的subtle of undefined报错是因为Cloudflare Worker运行在专属的边缘运行环境,不兼容Node.js的require('crypto')语法,Web Crypto API的subtle接口直接挂载在全局crypto对象下,无需额外引入,本地调试请使用官方wrangler工具,不要用Node.js直接运行Worker代码。
完整适配方案
这套方案只需要对服务端做极小的兼容调整,Worker侧即可完成签名验证,全程符合你原有签名逻辑,也支持对接带宽联盟中转源站存储。
1. 服务端调整(可选,仅为规避域名匹配问题)
将原有签名逻辑中传入的完整URL改为相对路径,避免源站域名和Cloudflare调度域名不一致导致签名校验失败,调整后代码如下:
const util = require('util'); const crypto = require('crypto'); function expireDate() { const d = new Date(); d.setUTCHours(5, 0, 0, 0); // 5 am d.setUTCDate(d.getDate() + 2); // + 2 days return Math.floor(d.getTime() / 1000); } function generateSignedUrl(relativePath) { const expire_date = expireDate(); const hmac = crypto.createHmac('sha256', 'mystrongkey'); // 签名规则和之前完全一致,仅传入路径不带域名 const signStr = util.format('%s\n%d\n%s', 'GET', expire_date, relativePath); const tempUrlSig = hmac.update(signStr).digest('hex'); // 最终返回的预签名URL拼接你的Cloudflare调度域名 return `https://你的Cloudflare绑定域名${relativePath}?temp_url_sig=${tempUrlSig}&temp_url_expires=${expire_date}`; } const presigned_url = generateSignedUrl('/test.jpg')
2. Cloudflare Worker 验证代码
将密钥mystrongkey配置为Worker的环境变量SECRET_KEY(不要硬编码在代码中),完整代码如下:
// 加载预设的密钥环境变量 const SECRET_KEY = new TextEncoder().encode(SECRET_KEY) // 导入HMAC密钥 async function getHmacKey() { return crypto.subtle.importKey( "raw", SECRET_KEY, { name: "HMAC", hash: "SHA-256" }, false, ["verify"] ) } // 签名验证函数 async function verifySignature(signStr, expectedSig) { const key = await getHmacKey() const data = new TextEncoder().encode(signStr) const sigBuf = new Uint8Array(expectedSig.match(/.{1,2}/g).map(byte => parseInt(byte, 16))) return crypto.subtle.verify("HMAC", key, sigBuf, data) } export default { async fetch(request) { const url = new URL(request.url) // 提取签名和过期时间参数 const tempUrlSig = url.searchParams.get('temp_url_sig') const tempUrlExpires = url.searchParams.get('temp_url_expires') // 缺少参数直接返回403 if (!tempUrlSig || !tempUrlExpires) { return new Response('Missing signature parameters', { status: 403 }) } // 校验是否过期 const now = Math.floor(Date.now() / 1000) if (now > parseInt(tempUrlExpires)) { return new Response('URL expired', { status: 403 }) } // 按照和服务端完全一致的规则拼接待签名字符串 const signStr = `GET\n${tempUrlExpires}\n${url.pathname}` // 验证签名 const isValid = await verifySignature(signStr, tempUrlSig) if (!isValid) { return new Response('Invalid signature', { status: 403 }) } // 验证通过,转发请求到你的源站对象存储,自动走带宽联盟链路 const originUrl = new URL(url.pathname, 'https://你的源站存储域名') return fetch(originUrl.toString()) } }
额外说明
如果你需要直接验证AWS S3原生生成的预签名URL,只需要调整Worker侧的签名拼接规则,匹配AWS S3 V4签名的待签名字段即可,核心的Web Crypto调用逻辑无需修改。
内容的提问来源于stack exchange,提问作者klabak
相关产品推荐
相关产品推荐

