🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
# 签署要求 您可以实现通用的URL身份验证方法,使用[Web Crypto API](https://developers.cloudflare.com/workers/reference/runtime/apis/web-crypto)*要求*在Workers应用中*签名*。[](https://developers.cloudflare.com/workers/reference/runtime/apis/web-crypto) 本示例使用基于哈希的消息认证码(HMAC)和SHA-256摘要算法来认证URL路径和随附的到期时间戳。用户代理必须提供正确的路径,到期时间戳记和HMAC,才能使用查询参数成功获取经过身份验证的资源。如果这三个参数中的任何一个都不正确,则请求将失败。请注意,到期时间戳的真实性已由HMAC覆盖,因此,如果HMAC正确,则可以假定用户提供的时间戳正确。这也给您URL过期时间,并允许您确定URL是否过期。 ## 验证已签名的请求 本示例验证路径名称以开头的任何请求URL的HMAC`/verify/`。 为了调试,如果URL或HMAC无效或URL过期,则此代码返回错误代码403。您可以选择返回404页面。 ### 验证HMAC并返回403错误的示例工作程序脚本: ~~~javascript // We'll need some super-secret data to use as a symmetric key. const encoder = new TextEncoder() const secretKeyData = encoder.encode('my secret symmetric key') addEventListener('fetch', event => { event.respondWith(verifyAndFetch(event.request)) }) async function verifyAndFetch(request) { const url = new URL(request.url) // If the path doesn't begin with our protected prefix, just pass the request // through. if (!url.pathname.startsWith('/verify/')) { return fetch(request) } // Make sure we have the minimum necessary query parameters. if (!url.searchParams.has('mac') || !url.searchParams.has('expiry')) { return new Response('Missing query parameter', { status: 403 }) } const key = await crypto.subtle.importKey( 'raw', secretKeyData, { name: 'HMAC', hash: 'SHA-256' }, false, ['verify'], ) // Extract the query parameters we need and run the HMAC algorithm on the // parts of the request we're authenticating: the path and the expiration // timestamp. const expiry = Number(url.searchParams.get('expiry')) const dataToAuthenticate = url.pathname + expiry // The received MAC is Base64-encoded, so we have to go to some trouble to // get it into a buffer type that crypto.subtle.verify() can read. const receivedMacBase64 = url.searchParams.get('mac') const receivedMac = byteStringToUint8Array(atob(receivedMacBase64)) // Use crypto.subtle.verify() to guard against timing attacks. Since HMACs use // symmetric keys, we could implement this by calling crypto.subtle.sign() and // then doing a string comparison -- this is insecure, as string comparisons // bail out on the first mismatch, which leaks information to potential // attackers. const verified = await crypto.subtle.verify( 'HMAC', key, receivedMac, encoder.encode(dataToAuthenticate), ) if (!verified) { const body = 'Invalid MAC' return new Response(body, { status: 403 }) } if (Date.now() > expiry) { const body = `URL expired at ${new Date(expiry)}` return new Response(body, { status: 403 }) } // We've verified the MAC and expiration time; we're good to pass the request // through. return fetch(request) } // Convert a ByteString (a string whose code units are all in the range // [0, 255]), to a Uint8Array. If you pass in a string with code units larger // than 255, their values will overflow! function byteStringToUint8Array(byteString) { const ui = new Uint8Array(byteString.length) for (let i = 0; i < byteString.length; ++i) { ui[i] = byteString.charCodeAt(i) } return ui } ~~~ ## 生成签名的请求 通常,已签名的请求以某种带外方式(例如电子邮件)传递给用户,或者由用户自己生成(如果他们拥有对称密钥)。您还可以在Workers应用中生成签名的请求。 **注意:**签名的请求将在一分钟后过期。我们建议根据路径或查询参数动态选择有效期限。 对于以开头的请求URL`/generate/`,我们将替换`/generate/`为`/verify/`,使用时间戳记对结果路径进行签名,然后在响应正文中返回完整的,经过签名的URL。 ### 示例工作者脚本签名的请求: ~~~javascript addEventListener('fetch', event => { const url = new URL(event.request.url) const prefix = '/generate/' if (url.pathname.startsWith(prefix)) { // Replace the "/generate/" path prefix with "/verify/", which we // use in the first example to recognize authenticated paths. url.pathname = `/verify/${url.pathname.slice(prefix.length)}` event.respondWith(generateSignedUrl(url)) } else { event.respondWith(fetch(event.request)) } }) async function generateSignedUrl(url) { // We'll need some super-secret data to use as a symmetric key. const encoder = new TextEncoder() const secretKeyData = encoder.encode('my secret symmetric key') const key = await crypto.subtle.importKey( 'raw', secretKeyData, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign'], ) // Signed requests expire after one minute. Note that you could choose // expiration durations dynamically, depending on, e.g. the path or a query // parameter. const expirationMs = 60000 const expiry = Date.now() + expirationMs const dataToAuthenticate = url.pathname + expiry const mac = await crypto.subtle.sign('HMAC', key, encoder.encode(dataToAuthenticate)) // `mac` is an ArrayBuffer, so we need to jump through a couple of hoops to get // it into a ByteString, and then a Base64-encoded string. const base64Mac = btoa(String.fromCharCode(...new Uint8Array(mac))) url.searchParams.set('mac', base64Mac) url.searchParams.set('expiry', expiry) return new Response(url) } ~~~