前言
边缘函数是EdgeOne的一个特色功能,可以通过它在EdgeOne的边缘节点运行JavaScript函数。
但它没法安装依赖,也没法导入腾讯云官方的SDK,在使用调用腾讯云API时签名是个大问题。
解决办法
用JS纯手写一个API 3.0的签名函数使用
目录
- 给出写好的签名代码
- 讲解使用方法
- 拿
获取轻量应用服务器可用区列表
举例,演示如何使用(需要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));
}
二、如何使用
签名函数输入参数:
- SecretId:API密钥,进控制台访问密钥获取
- SecretKey:API密钥,进控制台访问密钥获取
- Service:API的服务名,参考对应的API文档
- bodyString:API请求参数,参考对应的API文档,注意不包含公共参数
- 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个:
- 接口域名:lighthouse.tencentcloudapi.com
- 接口名称:DescribeZones
- 版本号:2020-03-24
- 请求参数:OrderField和Order
- 地域列表:Region
- Service:就是接口域名的主机名,这里为
lighthouse
其中,地域列表具体内容可以在公共参数页面找到
公共参数文档:链接
整理信息编写函数
根据上面的文档,我们可以得出如下内容
假设我们需要获取广州
的轻量应用服务器可用区列表
注: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))
}
});