目标
使用 PHP 创建 COS 接口所需要的请求签名
步骤
按照官方示例(也许是我笨,我怎么读都觉得官方文档结构费劲,示例细节互相不挨着,容易引起歧义),请求签名应用在需要身份校验的场景,即非公有读权限时。否则在请求API接口时,就必须携带签名作为请求头的一部分传递。
准备好用户信息
- 将会使用到的用户信息包括:
- SecretId:腾讯云账号内分配
- SecretKey:腾讯云账号内分配
- Bucket:存储桶名称
- Region:区域,即该COS所属区域
- 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 接口,因此为空
- $headerString:这个地方就需要计算了,逻辑是
根据请求头,选择全部或部分请求头,把每项的key都转换为小写,把value都进行URLEncode转换,每项格式都改为key=value,然后按照key进行字典排序,最后把它们用连接符 & 组成字符串
。这是我整理的逻辑,代码如下
```php
/** - 按COS要求从数组中获取 Signature 中 [HttpString] 内容
- 标准格式 key=value&key=value&...
- 数组元素按键字典排序 *
- key转换为小写
- value进行UrlEncode转换
- 转换为key=value格式
- 多对key=value之间用连接符连接
-
*/
try{
function get_http_header_string($headers){
if(!is_array($headers)){
return false;
}
$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个值都有了,有的是来自用户信息,有的需要计算,需要计算的上面也给出了所有的计算方法和为什么如此计算的个人理解。最后只需要按照官方要求进行输出即可。看一下