APP认证工作原理
- 构造规范请求。
将待发送的请求内容按照与API网关(即API管理)后台约定的规则组装,确保客户端签名、API网关后台认证时使用的请求内容一致。
- 使用规范请求和其他信息创建待签字符串。
- 使用AK/SK和待签字符串计算签名。
- 将生成的签名信息作为请求消息头添加到HTTP请求中,或者作为查询字符串参数添加到HTTP请求中。
APP签名仅支持Body体12M及以下的请求签名。
步骤1:构造规范请求
使用APP方式进行签名与认证,首先需要规范请求内容,然后再进行签名。客户端与API网关使用相同的请求规范,可以确保同一个HTTP请求的前后端得到相同的签名结果,从而完成身份校验。
HTTP请求规范伪代码如下:
CanonicalRequest = HTTPRequestMethod + '\n' + CanonicalURI + '\n' + CanonicalQueryString + '\n' + CanonicalHeaders + '\n' + SignedHeaders + '\n' + HexEncode(Hash(RequestPayload))
通过以下示例来说明规范请求的构造步骤。
假设原始请求如下:
GET https://c967a237-cd6c-470e-906f-a8655461897e.apigw.exampleRegion.com/app1?b=2&a=1 HTTP/1.1 Host: c967a237-cd6c-470e-906f-a8655461897e.apigw.exampleRegion.com X-Sdk-Date: 20191111T093443Z
- 构造HTTP请求方法(HTTPRequestMethod),以换行符结束。
HTTP请求方法,如GET、PUT、POST等。请求方法示例:
GET
- 添加规范URI参数(CanonicalURI),以换行符结束。
释义:
规范URI,即请求资源路径,是URI的绝对路径部分的URI编码。
格式:
根据RFC 3986标准化URI路径,移除冗余和相对路径部分,路径中每个部分必须为URI编码。如果URI路径不以“/”结尾,则在尾部添加“/”。
举例:
示例中的URI:/app1,此时规范的URI编码为:
GET /app1/
计算签名时,URI必须以“/”结尾。发送请求时,可以不以“/”结尾。
- 添加规范查询字符串(CanonicalQueryString),以换行符结束。
释义:
查询字符串,即查询参数。如果没有查询参数,则为空字符串,即规范后的请求为空行。
格式:
规范查询字符串需要满足以下要求:
- 根据以下规则对每个参数名和值进行URI编码:
- 请勿对RFC 3986定义的任何非预留字符进行URI编码,这些字符包括:A-Z、a-z、0-9、-、_、.和~。
- 使用%XY对所有非预留字符进行百分比编码,其中X和Y为十六进制字符(0-9和A-F)。例如,空格字符必须编码为%20,扩展UTF-8字符必须采用“%XY%ZA%BC”格式。
- 对于每个参数,追加“URI编码的参数名称=URI编码的参数值”。如果没有参数值,则以空字符串代替,但不能省略“=”。
例如以下含有两个参数,其中第二个参数parm2的值为空。
parm1=value1&parm2=
- 按照字符代码以升序顺序对参数名进行排序。例如,以大写字母F开头的参数名排在以小写字母b开头的参数名之前。
- 以排序后的第一个参数名开始,构造规范查询字符串。
举例:
示例中包含两个可选参数:a、b
GET /app1/ a=1&b=2
- 根据以下规则对每个参数名和值进行URI编码:
- 添加规范消息头(CanonicalHeaders),以换行符结束。
释义:
规范消息头,即请求消息头列表。包括签名请求中的所有HTTP消息头列表。消息头必须包含X-Sdk-Date,用于校验签名时间,格式为ISO8601标准的UTC时间格式:YYYYMMDDTHHMMSSZ。如果API发布到非RELEASE环境时,需要增加自定义的环境名称。
客户端须注意本地时间与时钟服务器的同步,避免请求消息头X-Sdk-Date的值出现较大误差。
API网关除了校验时间格式外,还会校验该时间值与网关收到请求的时间差,如果时间差大于15分钟,API网关将拒绝请求。
格式:
CanonicalHeaders由多个请求消息头共同组成,CanonicalHeadersEntry0 + CanonicalHeadersEntry1 + ...,其中每个请求消息头(CanonicalHeadersEntry)的格式为Lowercase(HeaderName) + ':' + Trimall(HeaderValue) + '\n'
- Lowercase表示将所有字符转换为小写字母的函数。
- Trimall表示删除值前后的多余空格的函数。
- 最后一个请求消息头也会携带一个换行符。叠加规范中CanonicalHeaders自身携带的换行符,因此会出现一个空行。
- 消息头名称要保持唯一性,出现多个相同消息头名称时,无法完成认证。
举例:
GET /app1/ a=1&b=2 host:c967a237-cd6c-470e-906f-a8655461897e.apigw.exampleRegion.com x-sdk-date:20191111T093443Z
规范消息头需要满足以下要求:- 将消息头名称转换为小写形式,并删除前导空格和尾随空格。
- 按照字符代码对消息头名称进行升序排序。
例如原始消息头为:
Host: c967a237-cd6c-470e-906f-a8655461897e.apigw.exampleRegion.com\n Content-Type: application/json;charset=utf8\n My-header1: a b c \n X-Sdk-Date:20191111T093443Z\n My-Header2: "a b c" \n
规范消息头为:
content-type:application/json;charset=utf8\n host:c967a237-cd6c-470e-906f-a8655461897e.apigw.exampleRegion.com\n my-header1:a b c\n my-header2:"a b c"\n x-sdk-date:20191111T093443Z\n
- 添加用于签名的消息头声明(SignedHeaders),以换行符结束。
释义:
用于签名的请求消息头列表。通过添加此消息头,向API网关告知请求中哪些消息头是签名过程的一部分,以及在验证请求时API网关可以忽略哪些消息头。X-Sdk-date必须作为已签名的消息头。
格式:
SignedHeaders = Lowercase(HeaderName0) + ';' + Lowercase(HeaderName1) + ";" + ...
已签名的消息头需要满足以下要求:将已签名的消息头名称转换为小写形式,按照字符代码对消息头进行排序,并使用“;”来分隔多个消息头。
Lowercase表示将所有字符转换为小写字母。
举例:
以下表示有两个消息头参与签名:host、x-sdk-date
GET /app1/ a=1&b=2 host:c967a237-cd6c-470e-906f-a8655461897e.apigw.exampleRegion.com x-sdk-date:20191111T093443Z host;x-sdk-date
- 基于HTTP或HTTPS请求正文中的body体(RequestPayload),使用SHA-256哈希函数创建哈希值。
释义:
请求消息体。消息体需要做两层转换:HexEncode(Hash(RequestPayload)),其中Hash表示生成消息摘要的函数,当前支持SHA-256算法。HexEncode表示以小写字母形式返回摘要的Base-16编码的函数。例如,HexEncode("m") 返回值为“6d”而不是“6D”。输入的每一个字节都表示为两个十六进制字符。
计算RequestPayload的哈希值时,对于“RequestPayload==null”的场景,直接使用空字符串""来计算。
举例:
本示例为GET方法,body体为空。经过哈希处理的body(空字符串)如下:
GET /app1/ a=1&b=2 host:c967a237-cd6c-470e-906f-a8655461897e.apigw.exampleRegion.com x-sdk-date:20191111T093443Z host;x-sdk-date e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
- 对构造好的规范请求进行哈希处理,算法与对RequestPayload哈希处理的算法相同。经过哈希处理的规范请求必须以小写十六进制字符串形式表示。
算法伪代码:Lowercase(HexEncode(Hash.SHA256(CanonicalRequest)))
经过哈希处理的规范请求示例:
af71c5a7ef45310b8dc05ab15f7da50189ffa81a95cc284379ebaa5eb61155c0
步骤2:创建待签字符串
对HTTP请求进行规范并取得请求的哈希值后,将其与签名算法、签名时间一起组成待签名字符串。
StringToSign = Algorithm + \n + RequestDateTime + \n + HashedCanonicalRequest
伪代码中参数说明如下。
- Algorithm
签名算法。对于SHA 256,算法为SDK-HMAC-SHA256。
- RequestDateTime
请求时间戳。与请求消息头X-Sdk-Date的值相同,格式为YYYYMMDDTHHMMSSZ。
- HashedCanonicalRequest
经过哈希处理的规范请求。
上述例子得到的待签字符串为:
SDK-HMAC-SHA256 20191111T093443Z af71c5a7ef45310b8dc05ab15f7da50189ffa81a95cc284379ebaa5eb61155c0
步骤3:计算签名
将APP secret和创建的待签字符串作为加密哈希函数的输入,计算签名,将二进制值转换为十六进制表示形式。
伪代码如下:
signature = HexEncode(HMAC(APP secret, string to sign))
其中HMAC指密钥相关的哈希运算,HexEncode指转十六进制。伪代码中参数说明如表1所示。
假设APP secret为FWTh5tqu2Pb9ZGt8NI09XYZti2V1LTa8useKXMD8,则计算得到的signature为:
01cc37e53d821da93bb7239c5b6e1640b184a748f8c20e61987b491e00b15822
步骤4:添加签名信息到请求头
在计算签名后,将它添加到Authorization的HTTP消息头。Authorization消息头未包含在已签名消息头中,主要用于身份验证。
伪代码如下:
Authorization header创建伪码: Authorization: algorithm Access=APP key, SignedHeaders=SignedHeaders, Signature=signature
需要注意的是算法与Access之前没有逗号,但是SignedHeaders与Signature之前需要使用逗号隔开。
得到的签名消息头为:
Authorization: SDK-HMAC-SHA256 Access=FM9RLCN************NAXISK, SignedHeaders=host;x-sdk-date, Signature=01cc37e53d821da93bb7239c5b6e1640b184a748f8c20e61987b491e00b15822
得到签名消息头后,将其增加到原始HTTP请求内容中,请求将被发送给API网关,由API网关完成身份认证。身份认证通过后,该请求才会发送给后端服务进行业务处理。