
# APP认证工作原理
#### APP认证流程
1. **构造规范请求** 。
   将待发送的请求内容按照与APIC后台约定的规则组装，确保客户端签名、APIC后台认证时使用的请求内容一致。
   
2. 使用规范请求和其他信息**创建待签字符串**。
3. 使用AK/SK和待签字符串**计算签名**。
4. 将生成的**签名信息作为请求消息头添加**到HTTP请求中，或者作为查询字符串参数添加到HTTP请求中。

5. APIC收到请求后，执行[1]\~[3]，计算签名。
6. 将[3]中的生成的签名与[5]中生成的签名进行比较，如果签名匹配，则处理请求，否则将拒绝请求。
![](https://support.huaweicloud.com/devg-roma/public_sys-resources/note_3.0-zh-cn.png)
APP签名仅支持Body体12M及以下的请求签名。
#### 步骤1：构造规范请求
使用APP方式进行签名与认证，首先需要规范请求内容，然后再进行签名。客户端与APIC使用相同的请求规范，可以确保同一个HTTP请求的前后端得到相同的签名结果，从而完成身份校验。
HTTP请求规范伪代码如下：
```
CanonicalRequest =
      HTTPRequestMethod + '\n' +
      CanonicalURI + '\n' +
      CanonicalQueryString + '\n' +
      CanonicalHeaders + '\n' +
      SignedHeaders + '\n' +
      HexEncode(Hash(RequestPayload))
```
通过以下示例来说明规范请求的构造步骤。
假设原始请求如下：
```
GET https://30030113-3657-4fb6-a7ef-90764239b038.apigw.exampleRegion.com/app1?b=2&a=1 HTTP/1.1
Host: 30030113-3657-4fb6-a7ef-90764239b038.apigw.exampleRegion.com
X-Sdk-Date: 20180330T123600Z
```
1. 构造**HTTP请求方法** （**HTTPRequestMethod** ），以换行符结束。
   HTTP请求方法，如GET、PUT、POST等。请求方法示例：
   ```
   GET
   ```
   
2. 添加规范**URI参数** （**CanonicalURI** ），以换行符结束。
   **释义**：
   规范URI，即请求资源路径，是URI的绝对路径部分的URI编码。
   **格式**：
   根据RFC 3986标准化URI路径，移除冗余和相对路径部分，路径中每个部分必须为URI编码。如果URI路径不以"/"结尾，则在尾部添加"/"。
   **举例**：
   示例中的URI：/app1，此时规范的URI编码为：
   ```
   GET
   /app1/
   ```
   ![](https://support.huaweicloud.com/devg-roma/public_sys-resources/note_3.0-zh-cn.png)
   计算签名时，URI必须以"/"结尾。发送请求时，可以不以"/"结尾。
   

3. 添加**规范** **查询字符串（** **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
   ```
   

4. 添加规范**消息头** （**CanonicalHeaders** ），以换行符结束。
   **释义**：
   规范消息头，即请求消息头列表。包括签名请求中的所有HTTP消息头列表。消息头必须包含X-Sdk-Date，用于校验签名时间，格式为ISO8601标准的UTC时间格式：YYYYMMDDTHHMMSSZ。如果API发布到非RELEASE环境时，需要增加自定义的环境名称。
   ![](https://support.huaweicloud.com/devg-roma/public_sys-resources/notice_3.0-zh-cn.png)
   客户端须注意本地时间与时钟服务器的同步，避免请求消息头X-Sdk-Date的值出现较大误差。
   ROMA Connect除了校验X-Sdk-Date的时间格式外，还会校验该时间值与收到请求的时间差，如果时间差超过15分钟，ROMA Connect将拒绝请求。
   **格式**：
   CanonicalHeaders由多个请求消息头共同组成，**CanonicalHeadersEntry0 + CanonicalHeadersEntry1 + ...** ，其中每个请求消息头（CanonicalHeadersEntry**）的格式为**Lowercase(HeaderName) + ':' + Trimall(HeaderValue) + '\\n'****
   ![](https://support.huaweicloud.com/devg-roma/public_sys-resources/note_3.0-zh-cn.png)
   - Lowercase表示将所有字符转换为小写字母的函数。
   
   - Trimall表示删除值前后的多余空格的函数。
   
   - 最后一个请求消息头也会携带一个换行符。叠加规范中**CanonicalHeaders**自身携带的换行符，因此会出现一个空行。
    
   **举例**：
   ```
   GET
   /app1/
   a=1&b=2
   host:30030113-3657-4fb6-a7ef-90764239b038.apigw.exampleRegion.com
   x-sdk-date:20180330T123600Z
   ```
   ![](https://support.huaweicloud.com/devg-roma/public_sys-resources/notice_3.0-zh-cn.png)
   规范消息头需要满足以下要求：
   - 将消息头名称转换为小写形式，并删除前导空格和尾随空格。
   
   - 按照字符代码对消息头名称进行升序排序。
    
   例如原始消息头为：
   ```
   Host:30030113-3657-4fb6-a7ef-90764239b038.apigw.exampleRegion.com\n
   Content-Type: application/json;charset=utf8\n
   My-header1:    a   b   c  \n
   X-Sdk-Date:20180330T123600Z\n
   My-Header2:    "a   b   c"  \n
   ```
   规范消息头为：
   ```
   content-type:application/json;charset=utf8\n
   host:30030113-3657-4fb6-a7ef-90764239b038.apigw.exampleRegion.com\n
   my-header1:a   b   c\n
   my-header2:"a   b   c"\n
   x-sdk-date:20180330T123600Z\n
   ```
   
5. 添加用于**签名的消息头声明** （**SignedHeaders** ），以换行符结束。
   **释义**：
   用于签名的请求消息头列表。通过添加此消息头，向APIC告知请求中哪些消息头是签名过程的一部分，以及在验证请求时APIC可以忽略哪些消息头。X-Sdk-date必须作为已签名的消息头。
   **格式**：
   SignedHeaders = Lowercase(HeaderName0) + ';' + Lowercase(HeaderName1) + ";" + ...
   已签名的消息头需要满足以下要求：将已签名的消息头名称转换为小写形式，按照字符代码对消息头进行排序，并使用";"来分隔多个消息头。
   Lowercase表示将所有字符转换为小写字母。
   **举例**：
   以下表示有两个消息头参与签名：host、x-sdk-date
   ```
   GET
   /app1/
   a=1&b=2
   host:30030113-3657-4fb6-a7ef-90764239b038.apigw.exampleRegion.com
   x-sdk-date:20180330T123600Z
   host;x-sdk-date
   ```
   
6. 使用SHA 256哈希函数以基于HTTP或HTTPS请求正文中的body体（**RequestPayload** ），创建哈希值。
   **释义**：
   请求消息体。消息体需要做两层转换：HexEncode(Hash(RequestPayload))，其中Hash表示生成消息摘要的函数，当前支持SHA-256算法。HexEncode表示以小写字母形式返回摘要的Base-16编码的函数。例如，HexEncode("m") 返回值为"6d"而不是"6D"。输入的每一个字节都表示为两个十六进制字符。
   ![](https://support.huaweicloud.com/devg-roma/public_sys-resources/note_3.0-zh-cn.png)
   计算RequestPayload的哈希值时，对于"RequestPayload==null"的场景，直接使用空字符串""来计算。
   **举例**：
   本示例为GET方法，body体为空。经过哈希处理的body（空字符串）如下：
   ```
   GET
   /app1/
   a=1&b=2
   host:30030113-3657-4fb6-a7ef-90764239b038.apigw.exampleRegion.com
   x-sdk-date:20180330T123600Z
   host;x-sdk-date
   e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
   ```
   
7. 对构造好的规范请求进行哈希处理，算法与对RequestPayload哈希处理的算法相同。经过哈希处理的规范请求必须以小写十六进制字符串形式表示。 算法伪代码：**Lowercase(HexEncode(Hash.SHA256(CanonicalRequest)))**
   经过哈希处理的规范请求示例：
   ```
   aa521bbe74d13cd8cf536c1a03a5dd85d1934179d33d47110b528eae8b7251e1
   ```
   
 
#### 步骤2：创建待签字符串
对HTTP请求进行规范并取得请求的哈希值后，将其与签名算法、签名时间一起组成待签名字符串。
```
StringToSign =
    Algorithm + \n +
    RequestDateTime + \n +
    HashedCanonicalRequest
```
伪代码中参数说明如下。
- **Algorithm**
  签名算法。对于SHA 256，算法为SDK-HMAC-SHA256。
  
- **RequestDateTime**
  请求时间戳。与请求消息头X-Sdk-Date的值相同，格式为YYYYMMDDTHHMMSSZ。
  
- **HashedCanonicalRequest**
  经过哈希处理的规范请求。
  
上述例子得到的待签字符串为：
```
SDK-HMAC-SHA256
20180330T123600Z
aa521bbe74d13cd8cf536c1a03a5dd85d1934179d33d47110b528eae8b7251e1
```
#### 步骤3：计算签名
将APP secret和创建的待签字符串作为加密哈希函数的输入，计算签名，将二进制值转换为十六进制表示形式。
伪代码如下：
```
signature = HexEncode(HMAC(APP secret, string to sign))
```
其中HMAC指密钥相关的哈希运算，HexEncode指转十六进制。伪代码中参数说明如[表1]所示。
 表1参数说明 
| 参数名称           | 参数解释     |
|:---|:---|
| APP secret     | 签名密钥     |
| string to sign | 创建的待签字符串 |
   
假设APP secret为12345678-1234-1234-1234-123456781234，则计算得到的signature为：
```
121c2501e8951ff7d5574423939b9acaa283e55a27c0107d767bb0d68b5ffcab
```
#### 步骤4：添加签名信息到请求头
在计算签名后，将它添加到Authorization的HTTP消息头。Authorization消息头未包含在已签名消息头中，主要用于身份验证。
伪代码如下：
```
Authorization header创建伪码：
Authorization: algorithm Access=APP key, SignedHeaders=SignedHeaders, Signature=signature
```
需要注意的是算法与Access之前没有逗号，但是SignedHeaders与Signature之前需要使用逗号隔开。
得到的签名消息头为：
```
Authorization: SDK-HMAC-SHA256 Access=071fe245-9cf6-4d75-822d-c29945a1e06a, SignedHeaders=host;x-sdk-date, Signature=121c2501e8951ff7d5574423939b9acaa283e55a27c0107d767bb0d68b5ffcab
```
得到签名消息头后，将其增加到原始HTTP请求内容中，请求将被发送给APIC，由APIC完成身份认证。身份认证通过后，该请求才会发送给后端服务进行业务处理。
