【玩转 EdgeOne】在边缘函数实现腾讯云API 3.0签名

2023-11-02 08:00:31 浏览数 (1)

前言

边缘函数是EdgeOne的一个特色功能,可以通过它在EdgeOne的边缘节点运行JavaScript函数。

但它暂时没办法安装依赖或者导入腾讯云官方SDK,在调用腾讯云API时的签名会比较麻烦。

解决办法

用JS纯手写一个API 3.0的签名函数使用

目录

  1. 给出写好的签名代码
  2. 讲解使用方法
  3. 获取轻量应用服务器可用区列表举例,演示如何使用(需要Demo修改的可以直接跳到这)

一、签名代码

相关的边缘函数API及其参考文档

  1. Fetch:https://cloud.tencent.com/document/product/1552/81897
  2. Response:https://cloud.tencent.com/document/product/1552/81917
  3. Web Crypto:https://cloud.tencent.com/document/product/1552/83933

完整的签名代码

代码语言:JavaScript复制
// 将字符串编码为ArrayBuffer
function stringToArrayBuffer(str) {
    const encoder = new TextEncoder();
    return encoder.encode(str);
  }
  
  // 将ArrayBuffer转换为十六进制字符串
  function arrayBufferToHexString(arrayBuffer) {
    const byteArray = new Uint8Array(arrayBuffer);
    const hexCodes = [...byteArray].map(value => value.toString(16).padStart(2, '0'));
    return hexCodes.join('');
  }
  
  async function hmacSHA256(key, data) {
    const importedKey = await crypto.subtle.importKey(
      'raw',
      key,
      { name: 'HMAC', hash: 'SHA-256' },
      false,
      ['sign']
    );
  
    const msgBuffer = stringToArrayBuffer(data);
    const signatureBuffer = await crypto.subtle.sign('HMAC', importedKey, msgBuffer);
  
    return signatureBuffer;
  }
  
  function uint8ArrayToHex(array) {
    return Array.from(array).map(byte => byte.toString(16).padStart(2, '0')).join('');
  }
  
  // 签名算法
  async function qcloud_v3_post(SecretId,SecretKey,Service,bodyString,headersOper) {
    const HTTPRequestMethod = "POST"
    const CanonicalURI = "/"
    const CanonicalQueryString = ""
  
    // 将 JSON 对象中的键按 ASCII 升序进行排序
    let sortedheadersOper = Object.keys(headersOper).filter(key => (key.toLowerCase() !== "x-tc-version")).sort();
    // 遍历排序后的键并拼接
    let SignedHeaders = sortedheadersOper.map(key => key.toLowerCase()).join(";");
    let CanonicalHeaders = sortedheadersOper.map(key => key.toLowerCase()   ":"   headersOper[key].toLowerCase()).join("n");
    CanonicalHeaders = CanonicalHeaders   "n"
  
    let HashedRequestPayload = await sha256(bodyString)
  
    const CanonicalRequest =
      HTTPRequestMethod   'n'  
      CanonicalURI   'n'  
      CanonicalQueryString   'n'  
      CanonicalHeaders   'n'  
      SignedHeaders   'n'  
      HashedRequestPayload
  
    const currentDate = new Date();
    const year = currentDate.getUTCFullYear();
    const month = (currentDate.getUTCMonth()   1).toString().padStart(2, '0');
    const day = currentDate.getUTCDate().toString().padStart(2, '0');
    const formattedDate = `${year}-${month}-${day}`;
  
  
    const Algorithm = "TC3-HMAC-SHA256"
    // 获取当前秒级时间戳
    const RequestTimestamp = Math.floor(Date.now() / 1000).toString();
    // const RequestTimestamp = "1688025007"
    const CredentialScope = formattedDate   "/"   Service   "/tc3_request"
    const HashedCanonicalRequest = await sha256(CanonicalRequest)
  
    const StringToSign =
      Algorithm   'n'  
      RequestTimestamp   'n'  
      CredentialScope   'n'  
      HashedCanonicalRequest
    
    const SecretDate = await hmacSHA256(new Uint8Array([...stringToArrayBuffer("TC3"), ...new Uint8Array(stringToArrayBuffer(SecretKey))]), formattedDate);
    const SecretService = await hmacSHA256(SecretDate, Service);
    const SecretSigning = await hmacSHA256(SecretService, "tc3_request");
  
    const Signature = arrayBufferToHexString(await hmacSHA256(SecretSigning, StringToSign));
  
    const Authorization =
      Algorithm   ' '  
      'Credential='   SecretId   '/'   CredentialScope   ', '  
      'SignedHeaders='   SignedHeaders   ', '  
      'Signature='   Signature

      headersOper["X-TC-Timestamp"] = RequestTimestamp;
      headersOper["Authorization"] = Authorization;
    
      return headersOper
  }

  // sha256 签名摘要
  async function sha256(message) {
    const msgBuffer = new TextEncoder().encode(message);
    const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
  
  
    return uint8ArrayToHex(new Uint8Array(hashBuffer));
  }

二、如何使用

签名函数输入参数:

  1. SecretId:API密钥,进控制台访问密钥获取
  2. SecretKey:API密钥,进控制台访问密钥获取
  3. Service:API的服务名,参考对应的API文档
  4. bodyString:API请求参数,参考对应的API文档,注意不包含公共参数
  5. headersPending:API请求头,参考对应的 API文档,包含公共参数

签名函数输出:

输出包含签名的请求头headers,使用该请求头直接调用API即可

示例代码

代码语言:JavaScript复制
    // 填写账户的SecretId和SecretKey,以及API的Service名
    const SecretId = "";
    const SecretKey = "";
    const Service = "";

    // 填写API的调用地址
    const apiurl = '';

    // 签名前的请求头,填写API调用地址的Host、API的Action以及版本Version、和目标地域Region
    const headersPending = {
      'Host': '',
      'Content-Type': 'application/json',
      'X-TC-Action': '',
      'X-TC-Version': '',
      'X-TC-Region': '',
    };

    这里是API调用时的输入参数,不包含公共参数
    const apiBodyJson = {
      "": ""
    }
    
    const bodyString = JSON.stringify(apiBodyJson)
    
    // 调用签名函数,会输出包含签名的请求头,后续直接用这个请求头请求API
    const headers = await qcloud_v3_post(SecretId,SecretKey,Service,bodyString,headersPending)
    
    // 请求API
    let qcloud_api_data;
        await fetch(apiurl, {
        method: 'POST',
        headers: headers,
        body: bodyString
    })
    .then(response => response.json())
    .then(data => qcloud_api_data = data)
    .catch(error => qcloud_api_data = error);

三、案例演示(通过边缘函数调用API获取轻量应用服务器可用区列表)

获取基本信息

打开API文档:轻量应用服务器 查询可用区列表-地域相关接口-API 中心-腾讯云

需要关注的点有6个:

  1. 接口域名:lighthouse.tencentcloudapi.com
  2. 接口名称:DescribeZones
  3. 版本号:2020-03-24
  4. 请求参数:OrderField和Order
  5. 地域列表:Region
  6. Service:就是接口域名的主机名,这里为**lighthouse**
API文档API文档

其中,地域列表具体内容可以在公共参数页面找到

公共参数文档:链接

地域列表地域列表

整理信息编写函数

根据上面的文档,我们可以得出如下内容

假设我们需要获取广州的轻量应用服务器可用区列表

注:OrderField和Order为可选参数(也就是不传也可以),这里我拿Order举例,使用ASC-升序排列

代码语言:txt复制
    const SecretId = "";
    const SecretKey = "";
    const Service = "lighthouse";
    const apiurl = 'https://lighthouse.tencentcloudapi.com/';

    const headersPending = {
      'Host': 'lighthouse.tencentcloudapi.com',
      'Content-Type': 'application/json',
      'X-TC-Action': 'DescribeZones',
      'X-TC-Version': '2020-03-24',
      'X-TC-Region': 'ap-guangzhou',
    };

    const apiBodyJson = {
      "Order": "ASC"
    }

部署运行的结果 (代码在末尾)

打开浏览器,访问边缘函数的默认访问域名

浏览器访问结果浏览器访问结果

对比轻量控制台的购买页面

轻量服务器购买页轻量服务器购买页

本次演示使用的完整代码

代码语言:JavaScript复制
// 将字符串编码为ArrayBuffer
function stringToArrayBuffer(str) {
    const encoder = new TextEncoder();
    return encoder.encode(str);
  }
  
  // 将ArrayBuffer转换为十六进制字符串
  function arrayBufferToHexString(arrayBuffer) {
    const byteArray = new Uint8Array(arrayBuffer);
    const hexCodes = [...byteArray].map(value => value.toString(16).padStart(2, '0'));
    return hexCodes.join('');
  }
  
  async function hmacSHA256(key, data) {
    const importedKey = await crypto.subtle.importKey(
      'raw',
      key,
      { name: 'HMAC', hash: 'SHA-256' },
      false,
      ['sign']
    );
  
    const msgBuffer = stringToArrayBuffer(data);
    const signatureBuffer = await crypto.subtle.sign('HMAC', importedKey, msgBuffer);
  
    return signatureBuffer;
  }
  
  function uint8ArrayToHex(array) {
    return Array.from(array).map(byte => byte.toString(16).padStart(2, '0')).join('');
  }
  
  // 签名算法
  async function qcloud_v3_post(SecretId,SecretKey,Service,bodyString,headersOper) {
    const HTTPRequestMethod = "POST"
    const CanonicalURI = "/"
    const CanonicalQueryString = ""
  
    // 将 JSON 对象中的键按 ASCII 升序进行排序
    let sortedheadersOper = Object.keys(headersOper).filter(key => (key.toLowerCase() !== "x-tc-version")).sort();
    // 遍历排序后的键并拼接
    let SignedHeaders = sortedheadersOper.map(key => key.toLowerCase()).join(";");
    let CanonicalHeaders = sortedheadersOper.map(key => key.toLowerCase()   ":"   headersOper[key].toLowerCase()).join("n");
    CanonicalHeaders = CanonicalHeaders   "n"
  
    let HashedRequestPayload = await sha256(bodyString)
  
    const CanonicalRequest =
      HTTPRequestMethod   'n'  
      CanonicalURI   'n'  
      CanonicalQueryString   'n'  
      CanonicalHeaders   'n'  
      SignedHeaders   'n'  
      HashedRequestPayload
  
    const currentDate = new Date();
    const year = currentDate.getUTCFullYear();
    const month = (currentDate.getUTCMonth()   1).toString().padStart(2, '0');
    const day = currentDate.getUTCDate().toString().padStart(2, '0');
    const formattedDate = `${year}-${month}-${day}`;
  
  
    const Algorithm = "TC3-HMAC-SHA256"
    // 获取当前秒级时间戳
    const RequestTimestamp = Math.floor(Date.now() / 1000).toString();
    // const RequestTimestamp = "1688025007"
    const CredentialScope = formattedDate   "/"   Service   "/tc3_request"
    const HashedCanonicalRequest = await sha256(CanonicalRequest)
  
    const StringToSign =
      Algorithm   'n'  
      RequestTimestamp   'n'  
      CredentialScope   'n'  
      HashedCanonicalRequest
    
    const SecretDate = await hmacSHA256(new Uint8Array([...stringToArrayBuffer("TC3"), ...new Uint8Array(stringToArrayBuffer(SecretKey))]), formattedDate);
    const SecretService = await hmacSHA256(SecretDate, Service);
    const SecretSigning = await hmacSHA256(SecretService, "tc3_request");
  
    const Signature = arrayBufferToHexString(await hmacSHA256(SecretSigning, StringToSign));
  
    const Authorization =
      Algorithm   ' '  
      'Credential='   SecretId   '/'   CredentialScope   ', '  
      'SignedHeaders='   SignedHeaders   ', '  
      'Signature='   Signature

      headersOper["X-TC-Timestamp"] = RequestTimestamp;
      headersOper["Authorization"] = Authorization;
    
      return headersOper
  }

  // sha256 签名摘要
  async function sha256(message) {
    const msgBuffer = new TextEncoder().encode(message);
    const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
  
  
    return uint8ArrayToHex(new Uint8Array(hashBuffer));
  }
  
  // 密钥填写位置
  const SecretId = "";
  const SecretKey = "";
  const Service = "lighthouse";
  
  async function handleRequest(request) {
  
    const headersPending = {
      'Host': 'lighthouse.tencentcloudapi.com',
      'Content-Type': 'application/json',
      'X-TC-Action': 'DescribeZones',
      'X-TC-Version': '2020-03-24',
      'X-TC-Region': 'ap-guangzhou',
    };

    const apiBodyJson = {
      "Order": "ASC"
    }
  
    const bodyString = JSON.stringify(apiBodyJson)
  
    const headers = await qcloud_v3_post(SecretId,SecretKey,Service,bodyString,headersPending)
  
    const apiurl = 'https://lighthouse.tencentcloudapi.com/';
  
  let qcloud_api_data;
  await fetch(apiurl, {
    method: 'POST',
    headers: headers,
    body: bodyString
  })
  .then(response => response.json())
  .then(data => qcloud_api_data = data)
  .catch(error => qcloud_api_data = error);
  
    return new Response(JSON.stringify(qcloud_api_data, null, 2), { 
        headers: { 'Content-Type': 'application/json',
        'Access-Control-Allow-Headers': 'Content-Type',
        'Access-Control-Allow-Methods': 'POST, OPTIONS',
        'Access-Control-Max-Age': '86400',
        'Access-Control-Allow-Origin': '*' },
        status: 200 
      })
  }
  
  addEventListener('fetch', (event) => {
    if (event.request.method === 'OPTIONS') {
      event.respondWith(handleOptions(event.request))
    } else {
      event.respondWith(handleRequest(event.request))
    }
  });
  

0 人点赞