前言

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

但它没法安装依赖,也没法导入腾讯云官方的SDK,在使用调用腾讯云API时签名是个大问题。

解决办法

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

目录

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

一、签名代码

// 将字符串编码为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即可

示例代码

// 填写账户的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文档

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

公共参数文档:链接

地域列表

整理信息编写函数

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

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

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

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"

    }

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

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

浏览器访问结果

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

轻量服务器购买页

本次演示使用的完整代码

// 将字符串编码为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))

    }

  });

  
最后修改:2023 年 10 月 24 日
如果觉得我的文章对你有用,请随意赞赏