# 签署要求
您可以实现通用的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)
}
~~~
- 关于本翻译文档
- 快速开始
- 模版库
- 讲解
- Workers页面
- 从0开始
- 从已有页面开始
- 从已有Worder开始
- 工具
- Cli工具 wrangler
- 安装
- 指令
- 配置
- 环境
- Webpack
- 密钥
- KV
- 网站
- Playground
- ServerLess插件
- Terraform
- REST API
- Making Requests
- Scripts
- Bindings
- Routes
- Integrations
- 相关
- 工作原理
- 安全
- 使用缓存
- 价格
- Routes
- Limits
- 提示
- 调试技巧
- 调试header
- FetchEvent生命周期
- 请求上下文
- 请求sign
- 参考
- runtime API
- Web Standards
- fetch
- fetchEvent
- Response
- Request
- KV
- Environment Variables
- Streams
- Encoding
- Web Crypto
- Cache API
- HTMLRewriter
- Workers KV
- Use cases
- Namespaces