目标
使用 PHP 创建 COS 接口所需要的请求签名, 按照官方示例, 请求签名应用在需要身份校验的场景, 即非公有读权限时. 否则在请求 API 接口时, 就必须携带签名作为请求头的一部分传递.
步骤
准备好用户信息
将会使用到的用户信息包括:
SecretId: 腾讯云账号内分配
SecretKey: 腾讯云账号内分配
Bucket: 存储桶名称
Region: 区域, 即该 COS 所属区域 https://cloud.tencent.com/document/product/436/6224
FileUri: 请求路径, 如
PUT /textfile HTTP1.1
, 意思是将新上传的文件放在目标存储桶根目录下并命名为 textfile
Host: 主机, 存储桶具体访问地址, 腾讯云存储桶详情可以找到
Content-Length: 上传文件时必须的请求头
创建参数
先看一个官方文档给出的栗子
通过 RESTful API 对 COS 发起的 HTTP 签名请求, 使用标准的 HTTP Authorization 头部来传递, 如下例所示:
- PUT /testfile2 HTTP/1.1
- Host: bucket1-1254000000.cos.ap-beijing.myqcloud.com
x-cos-content-sha1: 7b502c3a1f48c8609ae212cdfb639dee39673f5e
- x-cos-storage-class: standard
- Hello world
解读一下, 这是一个很简单的 http 请求, 第一行是请求行, 第二, 三, 四行都是请求头, 先放在这儿, 后面会用到. 按照官方文档, 先准备好必需的参数, 参数均已键值对方式存在, 首先看看官方给出的完整签名结构
- q-sign-algorithm=sha1&q-ak=[SecretID]&q-sign-time=[SignTime]&
- q-key-time=[KeyTime]&q-header-list=[SignedHeaderList]&
- q-url-param-list=[SignedParameterList]&q-signature=[Signature]
文本中每一个方括号中的内容就是用户信息, 其实 sha1 也是一个参数截止到发文官方文档表示只支持 sha1 因此直接填写即可, 其它的以下逐一解读
q-xxx: 参数中的键, 固定值, 接口设定
[SecretID]: 对应客户的 SecretId, 如
AKIDQjz3ltompVjBni5LitkWHFlFpwkn9U5q
[SignTime]: 一个由本签名起始时间和结束时间组成的字符串, 官方文档解释的很清楚, 不再赘述
[KeyTime]: 与 [SignTime] 相同
[SignedHeaderList]:HTTP 请求头所组成, 官方文档说明需从 key:value 中提取部分或全部 key..., 第一次没有理解怎么还允许部分, 到底是全部还是部分. 再次研究接口时, 明白了, 数据来自对接口的原始请求, 即还没计算签名前的
HTTP Request Headers
. 往回倒两步来看 2. 创建参数开始时给出的请求示例. Host,x-cos-content-sha1,
x-cos-storage-class
说明该请求全部请求头有三个, 因此 [SignedHeaderList] 可以有这三个请求头组成, 也可以挑两个甚至一个(我按照理论猜测未测试). 该值是由请求头的 Key 部分组成, Key 需转化为小写并且以字典排序再用连接符; 连接, 正确处理后的结果应该是
host;x-cos-content-sha1;x-cos-storage-class
. 当然如果有 Content-Type 这样的请求头, 结果应为
- content-type;host;x-cos-content-sha1;x-cos-storage-class
- .
[SignedParameterList]: 官方文档说明该值来自接口中的 HTTP 请求部分, 即第一行请求行. 官方给出的例子是 HTTP 请求
GET /?prefix=abc&max-keys=20
时,
[SignedParameterList]
为 max-keys;prefix 或 prefix. 按字面意思如果是非 GET 请求应该就没有查询部分也就没有参数, 该值应该是空.
[Signature]: 使用特定的算法计算出的签名字符串, 后面细说.
编写
根据上面的结构分解, 可以了解到以下结构
请求签名是由 7 个键值对组成的字符串
请求签名最后一个值 [Signature] 需要进一步计算
在创建请求签名之前, HTTP 原始请求头最好就设计好包括哪些字段, 最好只包括最必要的如 Host, 因为原始请求头将作为一种数据源影响请求签名的计算结果
了解了结构之后, 开始创建一个签名, 为了和官方文档比对结果, 用户信息使用官方文档给出的内容
用户信息
APPID:1254000000(在计算请求签名的过程中我没发现该参数有什么用)
SecretId:AKIDQjz3ltompVjBni5LitkWHFlFpwkn9U5q
SecretKey:BQYIM75p8x0iWVFSIgqEKwFprpRSVHlz
- Bucket:bucket1-1254000000
- Region:ap-beijing
- FileUri:/testfile2
- Host:bucket1-1254000000.cos.ap-beijing.myqcloud.com(Host 的构造可以从官方给出的示例自行组成或拆分)
Content-Length: 示例中未有此项
x-cos-content-sha1: 7b502c3a1f48c8609ae212cdfb639dee39673f5e
x-cos-storage-class: standard
请求签名结果比照
根据用户信息, 带入到请求签名结构中, 对应关系如下
键(key) | 值 (value) | 备注 |
---|---|---|
q-sign-algorithm | sha1 | 目前仅支持 sha1 签名算法 |
q-ak | AKIDQjz3ltompVjBni5LitkWHFlFpwkn9U5q | SecretId 字段 |
q-sign-time | 1417773892;1417853898 | 2014/12/5 18:04:52 到 2014/12/6 16:18:18 |
q-key-time | 1417773892;1417853898 | 2014/12/5 18:04:52 到 2014/12/6 16:18:18 |
q-header-list | host;x-cos-content-sha1;x-cos-storage-class | HTTP 头部 key 的字典顺序排序列表 |
q-url-param-list | HTTP 参数列表为空 | |
q-signature | 14e6ebd7955b0c6da532151bf97045e2c5a64e10 | 通过代码计算所得 |
以上是官方文档给出的 "结果", 也就是说如果自己计算出来的 7 个键值对跟表格中结果一致, 即说明算法正确.
先计算已有的值
- /**
- * 计算签名
- * secretId,secretKey 为必需参数, qSignStart,qSignEnd 为调试需要, 测试通过后应取消, 改为方法内自动创建
- */
- function get_authorization( $secretId, $secretKey, $qSignStart, $qSignEnd, $fileUri, $headers ){
- /*
- * 计算 COS 签名
- * 2018-05-17
- * author:cinlap <cash216@163>
- * ref:https://cloud.tencent.com/document/product/436/7778
- */
- $qSignTime = "$qSignStart;$qSignEnd"; //unix_timestamp;unix_timestamp
- $qKeyTime = $qSignTime;
- $header_list = get_q_header_list($headers);
- // 如果 Uri 中带有 ? 的请求参数, 该处应为数组排序后的字符串组合
- $url_param_list = '';
- //compute signature
- $httpMethod = 'put';
- $httpUri = $fileUri;
- // 与 q-url-param-list 相同
- $httpParameters = $url_param_list;
- // 将自定义请求头分解为 & 连接的字符串
- $headerString = get_http_header_string( $headers );
- // 计算签名中的 signature 部分
- $signTime = $qSignTime;
- $signKey = hash_hmac('sha1', $signTime, $secretKey);
- $httpString = "$httpMethod\n$httpUri\n$httpParameters\n$headerString\n";
- $sha1edHttpString = sha1($httpString);
- $stringToSign = "sha1\n$signTime\n$sha1edHttpString\n";
- $signature = hash_hmac('sha1', $stringToSign, $signKey);
- // 组合结果
- $authorization = "q-sign-algorithm=sha1&q-ak=$secretId&q-sign-time=$qSignTime&q-key-time=$qKeyTime&q-header-list=$header_list&q-url-param-list=$url_param_list&q-signature=$signature";
- return $authorization;
- }
为了测试, 该方法参数应该是多过需要了, 前六个参数是已经给出的, 是来自用户的, 因此直接赋值即可
$authorization = "q-sign-algorithm=sha1&q-ak=$secretId&q-sign-time=$qSignTime&q-key-time=$qKeyTime...
q-header-list
这个值需要计算, 逻辑是
从 HTTP 请求头中, 选择全部或部分, 将每项的 key 转化为小写并按字典排序, 最终输出成字符串, 多个 key 用字符 ; 连接
. 代码如下
- ```php
- /**
- 按 COS 要求对 header_list 内容进行转换
- 提取所有 key
- 字典排序
- key 转换为小写
- 多对 key=value 之间用连接符连接
- */
- function get_q_header_list($headers){
- if(!is_array($headers)){
- return false;
- }
- try{
- $tmpArray = array();
- foreach( $headers as $key=>$value){
- array_push($tmpArray, strtolower($key));
- }
- sort($tmpArray);
- return implode(';', $tmpArray);
- }
- catch(Exception $error){
- return false;
- }
- }
本例中, HTTP 请求头是三个, 因此输出结果应该是
host;x-cos-content-sha1;x-cos-storage-class```, 和官方给出结果一致.
q-url-param-list
上面讲过, 这个值是 HTTP 请求参数, 对于 PUT 方法没有? 后参数, 自然值为 0, 所以代码中 "偷懒" 直接给了空字符串, 实际应该根据用户给的 URI 做个智能判断之类的, 但是想想从逻辑上不做也行.
最后重点是计算 Signature
算法官方已经给出了, PHP 还是很幸福的直接拿来用, 先看一下 $httpString 的组成. 由四个部分组成 $httpMethod, $httpUri, $httpParameters, $headerString.
$httpMethod:HTTP 请求方法, 小写, 比如 put,get
$httpUri:HTTP 请求的 URI 部分, 从 "/" 虚拟根开始, 如 /testfile 说明在存储桶根目录下创建一个叫 testfile 的文件,/image/face1.jpg 说明在根目录 / image 目录下建立一个叫 face1.jpg 的文件, 至于是不是图片文件, 不管
$httpParameters:HTTP 请求参数, 即请求 URI 中? 后面的部分, 本例调用的是 PUT Object https://cloud.tencent.com/document/product/436/7749 接口, 因此为空
$headerString: 这个地方就需要计算了, 逻辑是
根据请求头, 选择全部或部分请求头, 把每项的 key 都转换为小写, 把 value 都进行 URLEncode 转换, 每项格式都改为 key=value, 然后按照 key 进行字典排序, 最后把它们用连接符 & 组成字符串
. 这是我整理的逻辑, 代码如下
- ```php
- /**
- 按 COS 要求从数组中获取 Signature 中 [HttpString] 内容
- 标准格式 key=value&key=value&...
- 数组元素按键字典排序 *
- key 转换为小写
- value 进行 UrlEncode 转换
- 转换为 key=value 格式
- 多对 key=value 之间用连接符连接
- */
- function get_http_header_string($headers){
- if(!is_array($headers)){
- return false;
- }
- try{
- $tmpArray = array();
- foreach($headers as $key => $value){
- $tmpKey = strtolower($key);
- $tmpArray[$tmpKey] = urlencode($value);
- }
- ksort($tmpArray);
- $headerArray = array();
- foreach( $tmpArray as $key => $value){
- array_push($headerArray, "$key=$value");
- }
- return implode('&', $headerArray);
- }
- catch(Exception $error){
- return false;
- }
- }
- ```
组合输出
至此, 请求签名中 7 个值都有了, 有的是来自用户信息, 有的需要计算, 需要计算的上面也给出了所有的计算方法和为什么如此计算的个人理解. 最后只需要按照官方要求进行输出即可. 看一下, 在 PostMan 中选择 Post 方法, 选择 form-data 方式提交数据, 在 Body 中给出所有用户参数(这个地方为了测试算法是否与官方一直, 所以几乎所有的值都是 Post 提交上去的, 实际时间, Host 都可以在算法中创建)
提交后, 返回结果
字很小, 单独把结果提取出来
- {
- "Authorization": "q-sign-algorithm=sha1&q-ak=AKIDQjz3ltompVjBni5LitkWHFlFpwkn9U5q&q-sign-time=1417773892;1417853898&q-key-time=1417773892;1417853898&q-header-list=host;x-cos-content-sha1;x-cos-storage-class&q-url-param-list=&q-signature=14e6ebd7955b0c6da532151bf97045e2c5a64e10",
- "Host": "bucket1-1254000000.cos.ap-beijing.myqcloud.com",
- "Content-Length": "12000"
- }
Host 和 Content-Length 是我自定义输出, 主要是看 Authorization 部分, 和官方文档给出的结果值完全一致, 说明算法逻辑正确.
吐槽
之前 C# 做过一次对接口的研究, 死活不行, 最后通过腾讯技术支持提供的 AWS 的 SDK 调用成功, 真是心累. 本次需要用 PHP 做项目, 必须要攻克, 本来不应该多难, 必须要为自己的智力和年龄讨个说法. 不过还是想再次吐槽官方文档, 看似详尽, 顺序前后不够一致, 示例代码细节比如参数不够统一, 造成新手容易误解怎么前后对不上, 对一些细节和前后逻辑不能第一时间融汇贯通. 比如我自己, 就是再次研究接口时, 才理解里边关于 [SignHeaderList] 等和计算 [Signature] 有什么关联.
来源: https://www.cnblogs.com/cinlap/p/9052705.html