这是我参与8月更文挑战的第19天,活动详情查看:8月更文挑战
腾讯云的 API 网关支持认证后请求,目的是为了保护 API 不被恶意攻击,目前,API 网关支持以下几种认证方式,免鉴权、EIAM 认证、OAuth2.0、应用认证和密钥对认证,其中密钥对认证已不推荐使用,成为了历史功能。 这里选用的是应用认证方式,实现语言为 JavaScript,虽然官方文档有详细的认证过程算法介绍,以及 JavaScript 的实现方式,但对于不熟悉认证过程的新手来说,会有一些踩坑的阶段,这篇文章介绍其一。
应用认证算法流程
使用应用认证的前置条件是在调用 API 之前就已经申请了 API 的签名密钥对(ApiAppKey 和 ApiAppSecret)并授权了对应的 API。 算法的第一步是提取签名串,即需要参与加密计算的请求信息,包含以下内容
Headers
HTTPMethod
Accept
Content-Type
Content-MD5
PathAndParameters
以上 6 个字段构成整个签名串,之间采用\n间隔连接,大小写敏感,且 Headers 必须包含x-date
客户端从 HTTP 请求中提取出关键数据组装成签名串后,需要对签名串进行加密及编码处理,形成最终的签名。步骤如下:
- 将签名串(signing_str 签名内容)使用 UTF-8 解码后得到 Byte 数组。
- 使用加密算法对 Byte 数组进行加密。
- 使用 Base64 算法进行编码,形成最终的签名。
JavaScript 实现
以下是官方文档中 JavaScript 实现 JSON 请求示例代码
const https = require('https')
const crypto = require('crypto')
// 应用 ApiAppKey
const apiAppKey = 'APIDLIA6tMfqsinsadaaaaaaaapHLkQ1z0kO5n5P'
// 应用 ApiAppSecret
const apiAppSecret = 'Dc44ACV2Da3Gm9JVaaaaaaaaumYRI4CZfVG8Qiuv'
const dateTime = new Date().toUTCString()
const body = {
arg1: 'a',
arg2: 'b',
}
const md5 = crypto.createHash('md5').update(JSON.stringify(body), 'utf8').digest('hex')
const contentMD5 = Buffer.from(md5).toString('base64')
const options = {
hostname: 'service-xxxxxxxx-1234567890.gz.apigw.tencentcs.com',
port: 443,
path: '/data',
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'Content-MD5': contentMD5,
'Content-Length': JSON.stringify(body).length,
'x-date': dateTime,
},
}
const signingStr = [
`x-date: ${dateTime}`,
options.method,
options.headers.Accept,
options.headers['Content-Type'],
contentMD5,
options.path,
].join('\n')
const signing = crypto.createHmac('sha1', apiAppSecret).update(signingStr, 'utf8').digest('base64')
const sign = `hmac id="${apiAppKey}", algorithm="hmac-sha1", headers="x-date", signature="${signing}"`
options.headers.Authorization = sign
const req = https.request(options, (res) => {
console.log(`STATUS: ${res.statusCode}`)
res.on('data', (chunk) => {
console.log('BODY: ' + chunk)
})
})
req.on('error', (error) => {
console.error(error)
})
req.write(JSON.stringify(body))
req.end()
401 签名校验失败问题
如果严格按照上面示例请求 post 接口一切正常,但如果是 get 请求,则会返回以下提示:
"message":
"HMAC signature does not match, Server StringToSign:........."
签名校验失败,错误代码为 401,具体原因为文档里不太起眼的一条:
只有在请求存在 Body 且 Body 为非 Form 形式时才计算Content-MD5 头 也就是说,如果请求中不包含 body 信息,那么对应的签名串中的
Content-MD5和Content-Length则需要置空,不能不传。即对应的 headers 为:
headers: {
Accept: 'application/json',
// 'Content-Type': 'application/json',
// 'Content-MD5': contentMD5,
// 'Content-Length': JSON.stringify(body).length,
'x-date': dateTime,
},
提取的签名串内容为:
const signingStr = [
`x-date: ${dateTime}`,
options.method,
options.headers.Accept,
"",
"",
options.path,
].join('\n')