首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【玩转 EdgeOne】在边缘函数实现腾讯云API 3.0签名

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

原创
作者头像
kr
修改2023-11-02 08:00:31
3440
修改2023-11-02 08:00:31
举报
文章被收录于专栏:个人教程个人教程

前言

边缘函数是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

完整的签名代码

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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
    • 解决办法
    • 目录
    • 一、签名代码
    • 二、如何使用
      • 签名函数输入参数:
        • 签名函数输出:
          • 示例代码
          • 三、案例演示(通过边缘函数调用API获取轻量应用服务器可用区列表)
            • 获取基本信息
              • 整理信息编写函数
                • 部署运行的结果 (代码在末尾)
                  • 本次演示使用的完整代码
                  相关产品与服务
                  云 API
                  云 API 是腾讯云开放生态的基石。通过云 API,只需少量的代码即可快速操作云产品;在熟练的情况下,使用云 API 完成一些频繁调用的功能可以极大提高效率;除此之外,通过 API 可以组合功能,实现更高级的功能,易于自动化, 易于远程调用, 兼容性强,对系统要求低。
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档