【AI接入迷你赛】腾讯云产品鉴权签名 v3

腾讯云 API 会对每个请求进行身份验证,用户需要使用安全凭证,经过特定的步骤对请求进行签名 Signature,每个请求都需要在公共请求参数中指定该签名结果并以指定的方式和格式发送请求 。

前言

最近开始接触一些腾讯云 OCR 文字识别产品的工作,但总会遇到遇到各种鉴权签名的问题,而且完整的鉴权签名代码,官网上只有 JavaPython 版本的,于是我打算撸一份 Nodejs 版本的鉴权签名代码 。

本文适用于腾讯云 API 3.0 下的所有产品的鉴权签名 , 并将使用鉴权签名 v3 方法通过对腾讯云 OCR 文字识别服务的通用印刷体识别接口的完整调用分享一些鉴权签名的准备工作、开发思路及调用流程、调用结果及踩坑指南等 。

当然,您也可以直接使用我写好的代码,已经分享到了 Github,欢迎大家参阅及提出意见 。

准备

在写鉴权签名之前,需要准备一些开发所需要的东西:腾讯云账号开发环境、开发工具 VSCode、腾讯云账号注册、腾讯云账号实名认证、获取 API 密钥、 参考文档等 。

开发环境

Nodejs 和 npm 安装

一: 下载安装

  • 这里仅介绍 windows 环境下的 Nodejs 安装
  • 下载地址
  • 图一: 图片中左边稳定版,右边最新版
node安装
  • 图二: 点击,下载,是一个msi文件,直接安装就可以,记住你的安装路径,后面做环境配置
node下载
  • 安装之后就可以做配置了

二: 配置环境变量

一般选择 msi 安装方式之后会自动帮配置好环境变量,也可以按如下步骤先查看下是否已配置好路径,若 Path 中有对应的路径,则无需再配置,若没有需要自行配置 ,可见下面四张图

  • win + r 打开 windows 运行面板
  • 输入 sysdm.cpl 打开系统属性面板,见图三
  • 选择系统属性面板上的高级,见图四
  • 选择环境变量,见图五
  • 双击系统变量中的 path,添加一条你安装 Nodejs 的路径即可,我这里是 C:\Program Files\nodejs\;,见图五、图六
  • 双击用户变量中的 path,添加一条你安装 npm 的路径即可,我这里是 C:\Users\User\AppData\Roaming\npm;,见图七、图八
  • 图三:
运行面板
  • 图四:
高级
  • 图五:
系统变量
  • 图六:
变量值
  • 图七:
用户变量
  • 图八:
用户变量值

三: 安装完成并配置好环境变量之后测试**

  • win + r => cmd 打开命令行面板,输入以下指令
代码语言:txt
复制
node -v

四: npm 安装

npm 即包管理工具,一般安装完 Nodejs 之后,npm 也会同时被安装 , 同样的 win + r => cmd 打开命令行面板,输入以下指令

代码语言:txt
复制
npm --version

五: 正确安装

我们看下正确安装并测试安装之后,是什么样的,见下图,可以看到一般正确安装后,输入指令后会有版本号

cmd
VSCode 安装及环境配置

一: 下载安装

  • 下载地址
  • 根据您的电脑系统和位数下载安装
  • 安装: 安装没什么需要注意的,一直下一步就行,但依然要记住您的安装路径,方便后面配置环境变量,见图九、图十
  • 图九:
vscode官网
  • 图十:
vscode下载

二: 配置环境变量

  • 同以上 Nodejsnpm 配置环境变量操作步骤一样,一般安装好 VSCode , 也会同时被配置好环境的,但依然需要检查一下,没配置的则自行配置,已配置的则不用配置 。我这里是 E:\dep\Microsoft VS Code\bin , 每条路径之后用 ; 隔开 。配置流程见以上图一 ~ 图四、图七、图八六张图

腾讯云账号

腾讯云账号注册
  • 进入 腾讯云官网 => 右上角免费注册
腾讯云账号注册
腾讯云账号实名认证
  • 点击访问账户信息 => 认证状态 => 实名认证
腾讯云账号实名认证
获取 API 密钥
  • 点击查看密钥 => 查看或者新建密钥 SecretIdSecretKey

参考文档

  • 请求结构
    • 帮助我们了解做腾讯云产品请求的服务地址、通信协议、请求方法、请求类型、字符编码等 。
  • 公共参数
    • 公共参数是用于标识用户和接口鉴权目的的参数,每次请求均需要携带这些参数,才能正常发起请求,可以帮助我们了解签名方法 v3 、签名方法 v1 及 地域列表(腾讯云产品接口下的 Region 字段 ),这里强烈建议使用签名方法 v3
  • 通用印刷体识别
    • 腾讯云 OCR 文字识别产品之一,通用印刷体识别,支持多场景、任意版面下整图文字的识别。支持自动识别语言类型,同时支持自选语言种类(推荐),除中英文外,支持日语、韩语、西班牙语、法语、德语、葡萄牙语、越南语、马来语、俄语、意大利语、荷兰语、瑞典语、芬兰语、丹麦语、挪威语、匈牙利语、泰语等多种语言。应用场景包括:印刷文档识别、网络图片识别、广告图文字识别、街景店招识别、菜单识别、视频标题识别、头像文字识别等 。
  • 接口鉴权 v3
    • 腾讯云 API 会对每个请求进行身份验证,用户需要使用安全凭证,经过特定的步骤对请求进行签名 Signature,每个请求都需要在公共请求参数中指定该签名结果并以指定的方式和格式发送请求 。本节课的主要内容就是结合 通用印刷体识别 , 说明该如何开发接口鉴权 v3 签名代码及如何实现腾讯云产品调用 。
  • 错误码
    • 腾讯云 OCR 文字识别业务错误码 及 公共错误码 , 如果开发过程中遇到问题,到这里找一下,方便快速定位问题 。
  • 公共错误码
    • 接口鉴权 v3 公共错误码,如果开发过程中遇到问题,到这里找一下,方便快速定位问题 。

TC3-HMAC-SHA256 签名方法

TC3-HMAC-SHA256 签名方法相比以前的 HmacSHA1HmacSHA256 签名方法,功能上覆盖了以前的签名方法,而且更安全,支持更大的请求,支持 json 格式,性能有一定提升,建议使用该签名方法计算签名 。 云 API支持 GETPOST 请求。对于 GET 方法,支持 Content-Type: application/x-www-form-urlencoded 协议格式。对于 POST 方法,目前支持 Content-Type: application/json 以及 Content-Type: multipart/form-data 两种协议格式,json 格式绝大多数接口均支持,multipart 格式只有特定接口支持,此时该接口不能使用 json 格式调用,参考具体业务接口文档说明 。推荐使用 POST 请求,因为两者的结果并无差异,但 GET 请求只支持 32 KB 以内的请求包。 下面以云服务器查询广州区实例列表作为例子,分步骤介绍签名的计算过程。我们选择该接口是因为: 1. 云服务器默认已开通,该接口很常用; 2. 该接口是只读的,不会改变现有资源的状态 ; 3. 接口覆盖的参数种类较全,可以演示包含数据结构的数组如何使用 。

注意: 在示例中,不论公共参数或者接口的参数,我们尽量选择容易犯错的情况 。在实际调用接口时,请根据实际情况来,每个接口的参数并不相同,不要照抄这个例子的参数和值 。

写一个自己的签名

这里以我的 SecretIdSecretKey 为例写一个自己的签名,并会在写签名的过程中提到一些注意事项 。

载入模块

代码语言:txt
复制
  const crypto = require('crypto')
  const request = require('request')

第一步 拼接规范请求串 CanonicalRequest

代码语言:txt
复制
  /**
  * 第一步: 拼接规范请求串 CanonicalRequest
  * 注意: 可对照代码看参数含义及注意事项 。
  */
  // 说明: HTTP 请求方法(GET、POST )。此示例取值为 POST
  var HTTPRequestMethod = 'POST'; 
  // 说明: URI 参数,API 3.0 固定为正斜杠(/)
  var CanonicalURI = '/'; 
  // 说明: POST请求时为空
  var CanonicalQueryString = ""; 
  /**  说明:
   * 参与签名的头部信息,content-type 和 host 为必选头部 ,
   * content-type 必须为小写 , 推荐 content-type 值 application/json , 对应方法为 TC3-HMAC-SHA256 签名方法 。
   * 其中 host 指接口请求域名 POST 请求支持的 Content-Type 类型有:
   * 1. application/json(推荐), 必须使用 TC3-HMAC-SHA256 签名方法 ;
   * 2. application/x-www-form-urlencoded , 必须使用 HmacSHA1 或 HmacSHA256 签名方法 ;
   * 3. multipart/form-data(仅部分接口支持), 必须使用 TC3-HMAC-SHA256 签名方法 。
   * 
   * 注意:
   * content-type 必须和实际发送的相符合 , 有些编程语言网络库即使未指定也会自动添加 charset 值 , 
   * 如果签名时和发送时不  一致,服务器会返回签名校验失败。
  */
  var CanonicalHeaders = "content-type:application/json\nhost:ocr.tencentcloudapi.com\n";
  /**  说明:
   * 参与签名的头部信息的 key,可以说明此次请求都有哪些头部参与了签名,和 CanonicalHeaders 包含的头部内容是一一对应的。
   * content-type 和 host 为必选头部 。 
   * 注意: 
   * 1. 头部 key 统一转成小写; 
   * 2. 多个头部 key(小写)按照 ASCII 升序进行拼接,并且以分号(;)分隔 。 
  */  
  var SignedHeaders = "content-type;host";
  /**
   * 参与签名的头部信息的 key,可以说明此次请求都有哪些头部参与了签名,和 CanonicalHeaders 包含的头部内容是一一对应的。
   * content-type 和 host 为必选头部 。 
   * 注意: 
   * 1. 头部 key 统一转成小写; 
   * 2. 多个头部 key(小写)按照 ASCII 升序进行拼接,并且以分号(;)分隔 。 
   */
  // 传入需要做 HTTP 请求的正文 body
  var payload = {
      "ImageUrl":"https://imgcache.qq.com/open_proj/proj_qcloud_v2/gateway/product/ocr-demo/css/img/GeneralBasicOCR1.jpg", 
      "LanguageType":"auto" // 语言类型,可选,此处我用的是 auto 即自动
  } 
  /**  说明:
   * 对请求体加密后的字符串 , 每个语言加密加密最终结果一致 , 但加密方法不同 , 
   * 这里 nodejs 的加密方法为 crypto.createHash('sha256').update(JSON.stringify(payload)).digest('hex'); 
   * 选择加密函数需要能够满足对 HTTP 请求正文做 SHA256 哈希 , 然后十六进制编码 , 最后编码串转换成小写字母的功能即可 。
  */
  var HashedRequestPayload = crypto.createHash('sha256').update(JSON.stringify(payload)).digest('hex'); 
  // 最后拼接以上六个字段 , 注意中间用 '/n' 拼接 , 拼接格式一定要如下格式 , 否则会报错
  var CanonicalRequest =  HTTPRequestMethod + '\n' +
    CanonicalURI + '\n' +
    CanonicalQueryString + '\n' +
    CanonicalHeaders + '\n' +
    SignedHeaders + '\n' +
    HashedRequestPayload;
  console.log('1. 拼接规范请求串' + '\n' + CanonicalRequest);

第二步: 拼接待签名字符串 StringToSign

代码语言:txt
复制
  // 2. 拼接待签名字符串
  // 签名算法,接口鉴权v3为固定值 TC3-HMAC-SHA256
  var Algorithm = "TC3-HMAC-SHA256"; 
  // 请求时间戳,即请求头部的公共参数 X-TC-Timestamp 取值,取当前时间 UNIX 时间戳,精确到秒
  var RequestTimestamp = Math.round(new Date().getTime()/1000) + "";
  /**
   * Date 必须从时间戳 X-TC-Timestamp 计算得到,且时区为 UTC+0。
   * 如果加入系统本地时区信息,例如东八区,将导致白天和晚上调用成功,但是凌晨时调用必定失败。
   * 假设时间戳为 1551113065,在东八区的时间是 2019-02-26 00:44:25,但是计算得到的 Date 取 UTC+0 的日期应为 2019-02-25,而不是 2019-02-26。
   * Timestamp 必须是当前系统时间,且需确保系统时间和标准时间是同步的,如果相差超过五分钟则必定失败。
   * 如果长时间不和标准时间同步,可能导致运行一段时间后,请求必定失败,返回签名过期错误。
  */ 
  var t = new Date();
  var date = t.toISOString().substr(0, 10); // 计算 Date 日期   date = "2019-08-26"
  /**
   *  拼接 CredentialScope 凭证范围,格式为 Date/service/tc3_request , 
   * service 为服务名,慧眼用 faceid , OCR 文字识别用 ocr
  */
  var CredentialScope = date + "/ocr/tc3_request"; 
  // 将第一步拼接得到的 CanonicalRequest 再次进行哈希加密
  var HashedCanonicalRequest = crypto.createHash('sha256').update(CanonicalRequest).digest('hex'); 
  // 拼接 StringToSign
  var StringToSign = Algorithm + '\n' +
    RequestTimestamp + '\n' +
    CredentialScope + '\n' +
    HashedCanonicalRequest;
  console.log('2. 拼接待签名字符串' + '\n' + StringToSign);

第三步: 计算签名 Signature

代码语言:txt
复制
// 3. 计算签名 Signature
  var SecretKey = "请替换为自己的 SecretKey";
  var SecretDate = crypto.createHmac('sha256', "TC3"+SecretKey).update(date).digest();
  var SecretService = crypto.createHmac('sha256', SecretDate).update("ocr").digest();
  var SecretSigning = crypto.createHmac('sha256', SecretService).update("tc3_request").digest();
  var Signature = crypto.createHmac('sha256', SecretSigning).update(StringToSign).digest('hex');
  console.log('3. 计算签名' + Signature); 

第四步: 拼接签名 Authorization

代码语言:txt
复制
// 4. 拼接签名 Authorization
  var SecretId = "请替换为自己的 SecretId"; // SecretId, 需要替换为自己的
  var Algorithm = "TC3-HMAC-SHA256";
  var Authorization =
    Algorithm + ' ' +
    'Credential=' + SecretId + '/' + CredentialScope + ', ' +
    'SignedHeaders=' + SignedHeaders + ', ' +
    'Signature=' + Signature
  console.log('4. 拼接Authorization' + '\n' + Authorization)

第五步: 发送 POST 请求

代码语言:txt
复制
  // 5. 发送POST请求 options 配置
  var options = {
    url: 'https://ocr.tencentcloudapi.com/',
    method:'POST',
    json: true,
    headers: {
      "Content-Type": "application/json",
      "Authorization": Authorization,
      "Host": "ocr.tencentcloudapi.com",
      "X-TC-Action": "GeneralBasicOCR",
      "X-TC-Version": "2018-11-19",
      "X-TC-Timestamp": RequestTimestamp,
      "X-TC-Region": "ap-guangzhou"
    },
    body: payload,
  };
  // 发起请求
  request(options, function (error, response, body) {
    if (error) throw new Error(error); 
    console.log(JSON.stringify(body))
  });

获取结果

result

避坑指南

帮助我们更少的犯错

  • UnsupportedProtocol
    • HTTP(S) 协议错误,常发生于请求方法用错或者 content-type 值写错 。
  • AuthFailure.SignatureExpire
    • 签名过期,注意 X-TC-TimeStamp 参数计算得到的时间戳必须和腾讯云服务器时间是 5 分钟以内 , 且是 +0 时区,若和电脑系统时间比对时,需要注意电脑时间是 +8 区,需转化为 +0 区进行比较 。
  • AuthFailure.SignatureFailure
    • 签名错误,经常有人直接拿官网鉴权文档上计算好的签名去用,那是不可行的,每个腾讯云账户签名都不一样的,每隔五分钟签名也是不一样的,所以需要计算 。
  • Content-type 规范
    • 注意下请求方法 和 content-type 的对应和写法规范,例如我们在计算签名的头部信息的时候,需要注意 content-type 必须为小写(而在发送请求的headers里,必须首字母为大写),且推荐 content-type 值为 application/json ,且content-type 必须和实际发送的相符合,有些编程语言网络库即使未指定也会自动添加 charset 值,如果签名时和发送时不一致,服务器会返回签名校验失败 。 对应方法为 TC3-HMAC-SHA256 签名方法 。
  • 尽量使用 POST 请求并在后端做请求
    • GET 请求只支持 32KB 以内的请求包,而 POST 请求可支持更大的请求包 。
    • 不在前端做请求是因为防止被抓包 。
  • v3 和 v1
    • v3 鉴权和 v1 鉴权传入的接口参数不同,v3 鉴权需要加 X-TC-,例如 v1 鉴权参数 Action,在 v3 鉴权中要传 X-TC-Action … 具体参数怎么传,可参照文档
  • 不同语言用到的加密方法不同
    • 加密的时候注意不同语言加密用到的函数是不同的(所以加密函数不能完全参照文档,需要能够完成对 HTTP 请求正文做 SHA256 哈希,然后十六进制编码,最后编码串转换成小写字母的功能),例如Nodejs 做加密的时候,用这个方法 crypto.createHmac('sha256',SecretSigning).update(StringToSign).digest(‘hex’);
  • 拼接字符串格式规范注意
    • 严格参照文档格式规范,特别拼接字符串的时候,文档中用 ‘\n’和 ‘/’拼接的格式一定要一致 。
  • 其它问题
    • 计费相关
    • 性能相关
    • 功能相关
    • 账号相关

完整代码

Nodejs鉴权签名完整代码