文档中心 Edge Application 开发者指南 请求签名与合法性校验

请求签名与合法性校验

更新时间:2024-09-03 17:00:38

本示例展示如何利用 Edge Cloud Apps 函数Web Crypto API对特定网站资源进行安全访问控制,以宠物商店的目标URL:http://ecatest.cdnetworks.com/ECA-test/pet-shop-website-template/about.html为例。访问该页面需要一个有效的签名,该签名使用HMACSHA-256算法生成。Edge Cloud Apps 函数可以代表客户端对请求进行签名,也可以验证传入的签名请求,充当安全守门员的角色。未经授权的请求将收到错误页面。

优势

  • 增强安全性: 使用加密签名可以有效防止未经授权的访问,保护敏感的网站资源。
  • 边缘加密: 利用 Edge Cloud Apps 函数和Web Crypto API在边缘节点进行加密操作,降低延迟,提高效率。
  • 灵活控制: 开发者可以根据需求自定义签名算法和验证逻辑,实现灵活的访问控制策略。

详细代码

const SECRET_KEY = "my secret symmetric key";

// 处理请求,根据请求方法进行签名或验证
async function handleRequest(request) {
    if (request.method === "POST") {
        return signRequest(request);
    } else {
        return verifyRequest(request);
    } 
}

// 验证请求签名
async function verifyRequest(request) {
    const url = new URL(request.url);

    // 确保请求包含必要的查询参数
    if (!url.searchParams.has("mac") || !url.searchParams.has("expiry")) {
        return failPage("缺少查询参数");
    }

    const encoder = new TextEncoder();
    const secretKeyData = encoder.encode(SECRET_KEY);

    // 导入密钥,用于验证签名
    const key = await crypto.subtle.importKey(
        "raw",
        secretKeyData,
        { name: "HMAC", hash: "SHA-256" },
        false,
        ["verify"],
    );

    // 提取需要验证的数据:路径和过期时间戳
    const expiry = Number(url.searchParams.get("expiry"));
    const dataToAuthenticate = url.pathname + expiry;

    // 将接收到的 Base64 编码的 MAC 转换为 Uint8Array 类型
    const receivedMacBase64 = url.searchParams.get("mac");
    const receivedMac = byteStringToUint8Array(atob(receivedMacBase64));

    // 使用 crypto.subtle.verify()  验证签名,防止计时攻击
    const verified = await crypto.subtle.verify(
        "HMAC",
        key,
        receivedMac,
        encoder.encode(dataToAuthenticate),
    );

    // 验证失败,返回错误页面
    if (!verified) {
        return failPage("无效的 MAC");
    }

    // 检查 URL 是否过期
    if (Date.now() > expiry) {
        return failPage(`URL 已过期:${new Date(expiry)}`);
    }

    // 验证通过,允许访问资源
    return fetch(request);
}

// 将 ByteString 转换为 Uint8Array
function byteStringToUint8Array(byteString) {
    const ui = new Uint8Array(byteString.length);
    for (let i = 0; i < byteString.length; ++i) {
        ui[i] = byteString.charCodeAt(i);
    }
    return ui;
}

// 对请求进行签名
async function signRequest(request) {
    const url = new URL(request.url);
    return generateSignedUrl(url);
}

// 生成签名 URL
async function generateSignedUrl(url) {
    // 导入密钥,用于生成签名
    const encoder = new TextEncoder();
    const secretKeyData = encoder.encode(SECRET_KEY);
    const key = await crypto.subtle.importKey(
      "raw",
      secretKeyData,
      { name: "HMAC", hash: "SHA-256" },
      false,
      ["sign"],
    );
  
    // 设置 URL 过期时间
    const expirationMs = 18000; // 18 秒
    const expiry = Date.now() + expirationMs;
    const dataToAuthenticate = url.pathname + expiry;
  
    // 生成签名
    const mac = await crypto.subtle.sign("HMAC", key, encoder.encode(dataToAuthenticate));
  
    // 将签名转换为 Base64 编码的字符串
    const base64Mac = btoa(String.fromCharCode(...new Uint8Array(mac)));
  
    // 将签名和过期时间添加到 URL 查询参数中
    url.searchParams.set("mac", base64Mac);
    url.searchParams.set("expiry", expiry);
  
    return new Response(url);
  }


// 生成错误页面
function failPage(msg) {
    let page = FAILPAGE.replace('{{error-msg}}', msg);
    return new Response(page, {headers: {"Content-Type": "text/html; charset=utf-8"}});
}

// 错误页面 HTML 模板
const FAILPAGE = `
<!DOCTYPE html>
<html>
<head>
<title>错误提示页面</title>
<style>
// ... 页面样式 ... 
</style>
</head>
<body>
<div class="container">
<h1>错误提示</h1>
<p>当前访问的URL没有经过正确的签名: {{error-msg}}</p>
<div class="url-section">
    <div class="url-wrapper">
        <p><span class="url-display" id="current-url" style="display: block;"></span></p>
    </div>
    <div class="button-wrapper">
        <button class="url-button" type="button" onclick="getSignedUrl()">进行签名</button>
    </div>
    <div class="url-wrapper" id="signed-url-section">
        <p><strong>签名URL将在18秒后过期</strong></p>
        <p><span class="url-display" id="signed-url"></span></p>
    </div>
</div>
</div>

<script>
// ... 页面脚本 ... 
</script>
</body>
</html>
`;
                    
addEventListener("fetch", event => {
    return event.respondWith(handleRequest(event.request));
});

说明

Edge Cloud Apps 函数完整支持Web Crypto API,可以满足您在边缘节点上进行加解密的需求。您可以使用 Edge Cloud Apps 函数代替客户端对请求进行签名,也可以将其用于防盗链校验。

本篇文档内容对您是否有帮助?
有帮助
我要反馈
提交成功!非常感谢您的反馈,我们会继续努力做到更好!