Sample-Code
更新时间:2023-02-06
Python示例
假设用户向北京的BOS集群使用UploadPart接口上传一个文件的最后一个Part,内容为Example。
- Bucket name:test
- Object key:myfolder/readme.txt
- uploadId:a44cc9bab11cbd156984767aad637851
- partNumber:9
- Access Key ID:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
- Secret Access Key:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
- 时间:北京时间2015年4月27日16点23分49秒(转换为UTC时间是2015年4月27日8点23分49秒)
则其HTTP请求如下:
                XML
                
            
            1PUT /test/myfolder/readme.txt?partNumber=9&uploadId=a44cc9bab11cbd156984767aad637851 HTTP/1.1
2Host: bj.bcebos.com
3Date: Mon, 27 Apr 2015 16:23:49 +0800
4Content-Type: text/plain
5Content-Length: 8
6Content-Md5: NFzcPqhviddjRNnSOGo4rw==
7x-bce-date: 2015-04-27T08:23:49Z
8
9Example用户可根据上述HTTP请求填写以下函数中的各个字段。
                Plain Text
                
            
            1 if __name__ == "__main__":
2     credentials = BceCredentials("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")
3     http_method = "PUT"
4     path = "/test/myfolder/readme.txt"
5     headers = {"host": "bj.bcebos.com",
6                "content-length": 8,
7                "content-md5": "NFzcPqhviddjRNnSOGo4rw==",
8                "content-type":"text/plain",
9                "x-bce-date": "2015-04-27T08:23:49Z"}
10     params = {"partNumber": 9,
11               "uploadId": "a44cc9bab11cbd156984767aad637851"}
12     timestamp = 1430123029
13     result = sign(credentials, http_method, path, headers, params, timestamp)
14     print result完整代码内容如下:
                Plain Text
                
            
            1 # -*- coding: UTF-8 -*-
2 import hashlib
3 import hmac
4 import string
5 import datetime
6 
7 
8 AUTHORIZATION = "authorization"
9 BCE_PREFIX = "x-bce-"
10 DEFAULT_ENCODING = 'UTF-8'
11 
12 
13 # 保存AK/SK的类
14 class BceCredentials(object):
15     def __init__(self, access_key_id, secret_access_key):
16         self.access_key_id = access_key_id
17         self.secret_access_key = secret_access_key
18 
19 
20 # 根据RFC 3986,除了:
21 #   1.大小写英文字符
22 #   2.阿拉伯数字
23 #   3.点'.'、波浪线'~'、减号'-'以及下划线'_'
24 # 以外都要编码
25 RESERVED_CHAR_SET = set(string.ascii_letters + string.digits + '.~-_')
26 def get_normalized_char(i):
27     char = chr(i)
28     if char in RESERVED_CHAR_SET:
29         return char
30     else:
31         return '%%%02X' % i
32 NORMALIZED_CHAR_LIST = [get_normalized_char(i) for i in range(256)]
33 
34 
35 # 正规化字符串
36 def normalize_string(in_str, encoding_slash=True):
37     if in_str is None:
38         return ''
39 
40     # 如果输入是unicode,则先使用UTF8编码之后再编码
41     in_str = in_str.encode(DEFAULT_ENCODING) if isinstance(in_str, unicode) else str(in_str)
42 
43     # 在生成规范URI时。不需要对斜杠'/'进行编码,其他情况下都需要
44     if encoding_slash:
45         encode_f = lambda c: NORMALIZED_CHAR_LIST[ord(c)]
46     else:
47         # 仅仅在生成规范URI时。不需要对斜杠'/'进行编码
48         encode_f = lambda c: NORMALIZED_CHAR_LIST[ord(c)] if c != '/' else c
49 
50     # 按照RFC 3986进行编码
51     return ''.join([encode_f(ch) for ch in in_str])
52 
53 
54 # 生成规范时间戳
55 def get_canonical_time(timestamp=0):
56     # 不使用任何参数调用的时候返回当前时间
57     if timestamp == 0:
58         utctime = datetime.datetime.utcnow()
59     else:
60         utctime = datetime.datetime.utcfromtimestamp(timestamp)
61 
62     # 时间戳格式:[year]-[month]-[day]T[hour]:[minute]:[second]Z
63     return "%04d-%02d-%02dT%02d:%02d:%02dZ" % (
64         utctime.year, utctime.month, utctime.day,
65         utctime.hour, utctime.minute, utctime.second)
66 
67 
68 # 生成规范URI
69 def get_canonical_uri(path):
70     # 规范化URI的格式为:/{bucket}/{object},并且要对除了斜杠"/"之外的所有字符编码
71     return normalize_string(path, False)
72 
73 
74 # 生成规范query string
75 def get_canonical_querystring(params):
76     if params is None:
77         return ''
78 
79     # 除了authorization之外,所有的query string全部加入编码
80     result = ['%s=%s' % (normalize_string(k), normalize_string(v)) for k, v in params.items() if k.lower != AUTHORIZATION]
81 
82     # 按字典序排序
83     result.sort()
84 
85     # 使用&符号连接所有字符串并返回
86     return '&'.join(result)
87 
88 
89 # 生成规范header
90 def get_canonical_headers(headers, headers_to_sign=None):
91     headers = headers or {}
92 
93     # 没有指定header_to_sign的情况下,默认使用:
94     #   1.host
95     #   2.content-md5
96     #   3.content-length
97     #   4.content-type
98     #   5.所有以x-bce-开头的header项
99     # 生成规范header
100     if headers_to_sign is None or len(headers_to_sign) == 0:
101         headers_to_sign = {"host", "content-md5", "content-length", "content-type"}
102 
103     # 对于header中的key,去掉前后的空白之后需要转化为小写
104     # 对于header中的value,转化为str之后去掉前后的空白
105     f = lambda (key, value): (key.strip().lower(), str(value).strip())
106 
107     result = []
108     for k, v in map(f, headers.iteritems()):
109         # 无论何种情况,以x-bce-开头的header项都需要被添加到规范header中
110         if k.startswith(BCE_PREFIX) or k in headers_to_sign:
111             result.append("%s:%s" % (normalize_string(k), normalize_string(v)))
112 
113     # 按照字典序排序
114     result.sort()
115 
116     # 使用\n符号连接所有字符串并返回
117     return '\n'.join(result)
118 
119 
120 # 签名主算法
121 def sign(credentials, http_method, path, headers, params,
122          timestamp=0, expiration_in_seconds=1800, headers_to_sign=None):
123     headers = headers or {}
124     params = params or {}
125 
126     # 1.生成sign key
127     # 1.1.生成auth-string,格式为:bce-auth-v1/{accessKeyId}/{timestamp}/{expirationPeriodInSeconds}
128     sign_key_info = 'bce-auth-v1/%s/%s/%d' % (
129         credentials.access_key_id,
130         get_canonical_time(timestamp),
131         expiration_in_seconds)
132     # 1.2.使用auth-string加上SK,用SHA-256生成sign key
133     sign_key = hmac.new(
134         credentials.secret_access_key,
135         sign_key_info,
136         hashlib.sha256).hexdigest()
137 
138     # 2.生成规范化uri
139     canonical_uri = get_canonical_uri(path)
140 
141     # 3.生成规范化query string
142     canonical_querystring = get_canonical_querystring(params)
143 
144     # 4.生成规范化header
145     canonical_headers = get_canonical_headers(headers, headers_to_sign)
146 
147     # 5.使用'\n'将HTTP METHOD和2、3、4中的结果连接起来,成为一个大字符串
148     string_to_sign = '\n'.join(
149         [http_method, canonical_uri, canonical_querystring, canonical_headers])
150 
151     # 6.使用5中生成的签名串和1中生成的sign key,用SHA-256算法生成签名结果
152     sign_result = hmac.new(sign_key, string_to_sign, hashlib.sha256).hexdigest()
153 
154     # 7.拼接最终签名结果串
155     if headers_to_sign:
156         # 指定header to sign
157         result = '%s/%s/%s' % (sign_key_info, ';'.join(headers_to_sign), sign_result)
158     else:
159         # 不指定header to sign情况下的默认签名结果串
160         result = '%s//%s' % (sign_key_info, sign_result)
161 
162     return result
163 
164 if __name__ == "__main__":
165     credentials = BceCredentials("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")
166     http_method = "PUT"
167     path = "/test/myfolder/readme.txt"
168     headers = {"host": "bj.bcebos.com",
169                "content-length": 8,
170                "content-md5": "NFzcPqhviddjRNnSOGo4rw==",
171                "content-type":"text/plain",
172                "x-bce-date": "2015-04-27T08:23:49Z"}
173     params = {"partNumber": 9,
174               "uploadId": "a44cc9bab11cbd156984767aad637851"}
175     timestamp = 1430123029
176     result = sign(credentials, http_method, path, headers, params, timestamp)
177     print resultPhp示例
假设用户向北京的BOS集群使用UploadPart接口上传一个文件的最后一个Part,内容为Example。
- Bucket name:test
- Object key:myfolder/readme.txt
- uploadId:a44cc9bab11cbd156984767aad637851
- partNumber:9
- Access Key ID:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
- Secret Access Key:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
- 时间:北京时间2015年4月27日16点23分49秒(转换为UTC时间是2015年4月27日8点23分49秒)
则其HTTP请求如下:
                XML
                
            
            1PUT /test/myfolder/readme.txt?partNumber=9&uploadId=a44cc9bab11cbd156984767aad637851 HTTP/1.1
2Host: bj.bcebos.com
3Date: Mon, 27 Apr 2015 16:23:49 +0800
4Content-Type: text/plain
5Content-Length: 8
6Content-Md5: NFzcPqhviddjRNnSOGo4rw==
7x-bce-date: 2015-04-27T08:23:49Z
8
9Example签名示范代码
                Plain Text
                
            
            1$signer = new SampleSigner();
2$credentials = array("ak" => "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","sk" => "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
3$httpMethod = "PUT";
4$path = "/v1/test/myfolder/readme.txt";
5$headers = array("Host" => "bj.bcebos.com",
6                "Content-Length" => 8,
7                "Content-MD5" => "NFzcPqhviddjRNnSOGo4rw==",
8                "Content-Type" => "text/plain",
9                "x-bce-date" => "2015-04-27T08:23:49Z");
10$params = array("partNumber" => 9, "uploadId" => "a44cc9bab11cbd156984767aad637851");
11$timestamp = new \DateTime();
12$timestamp->setTimestamp(1430123029);
13$options = array(SignOption::TIMESTAMP => $timestamp);
14$ret = $signer->sign($credentials, $httpMethod, $path, $headers, $params, $options);
15print $ret;完整代码内容如下:
                Plain Text
                
            
            1<?php
2/*
3* Copyright (c) 2014 Baidu.com, Inc. All Rights Reserved
4*
5* Licensed under the Apache License, Version 2.0 (the "License"); you may not
6* use this file except in compliance with the License. You may obtain a copy of
7* the License at
8*
9* Http://www.apache.org/licenses/LICENSE-2.0
10*
11* Unless required by applicable law or agreed to in writing, software
12* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14* License for the specific language governing permissions and limitations under
15* the License.
16*/
17
18namespace BaiduBce\Auth;
19
20class SignOption
21{
22    const EXPIRATION_IN_SECONDS = 'expirationInSeconds';
23
24    const HEADERS_TO_SIGN = 'headersToSign';
25
26    const TIMESTAMP = 'timestamp';
27
28    const DEFAULT_EXPIRATION_IN_SECONDS = 1800;
29
30    const MIN_EXPIRATION_IN_SECONDS = 300;
31
32    const MAX_EXPIRATION_IN_SECONDS = 129600;
33}
34
35class HttpUtil
36{
37    // 根据RFC 3986,除了:
38    //   1.大小写英文字符
39    //   2.阿拉伯数字
40    //   3.点'.'、波浪线'~'、减号'-'以及下划线'_'
41    // 以外都要编码
42    public static $PERCENT_ENCODED_STRINGS;
43
44    //填充编码数组
45    public static function __init()
46    {
47        HttpUtil::$PERCENT_ENCODED_STRINGS = array();
48        for ($i = 0; $i < 256; ++$i) {
49            HttpUtil::$PERCENT_ENCODED_STRINGS[$i] = sprintf("%%%02X", $i);
50        }
51
52        //a-z不编码
53        foreach (range('a', 'z') as $ch) {
54            HttpUtil::$PERCENT_ENCODED_STRINGS[ord($ch)] = $ch;
55        }
56
57        //A-Z不编码
58        foreach (range('A', 'Z') as $ch) {
59            HttpUtil::$PERCENT_ENCODED_STRINGS[ord($ch)] = $ch;
60        }
61
62        //0-9不编码
63        foreach (range('0', '9') as $ch) {
64            HttpUtil::$PERCENT_ENCODED_STRINGS[ord($ch)] = $ch;
65        }
66
67        //以下4个字符不编码
68        HttpUtil::$PERCENT_ENCODED_STRINGS[ord('-')] = '-';
69        HttpUtil::$PERCENT_ENCODED_STRINGS[ord('.')] = '.';
70        HttpUtil::$PERCENT_ENCODED_STRINGS[ord('_')] = '_';
71        HttpUtil::$PERCENT_ENCODED_STRINGS[ord('~')] = '~';
72    }
73
74    //在uri编码中不能对'/'编码
75    public static function urlEncodeExceptSlash($path)
76    {
77        return str_replace("%2F", "/", HttpUtil::urlEncode($path));
78    }
79
80    //使用编码数组编码
81    public static function urlEncode($value)
82    {
83        $result = '';
84        for ($i = 0; $i < strlen($value); ++$i) {
85            $result .= HttpUtil::$PERCENT_ENCODED_STRINGS[ord($value[$i])];
86        }
87        return $result;
88    }
89
90    //生成标准化QueryString
91    public static function getCanonicalQueryString(array $parameters)
92    {
93        //没有参数,直接返回空串
94        if (count($parameters) == 0) {
95            return '';
96        }
97
98        $parameterStrings = array();
99        foreach ($parameters as $k => $v) {
100            //跳过Authorization字段
101            if (strcasecmp('Authorization', $k) == 0) {
102                continue;
103            }
104            if (!isset($k)) {
105                throw new \InvalidArgumentException(
106                    "parameter key should not be null"
107                );
108            }
109            if (isset($v)) {
110                //对于有值的,编码后放在=号两边
111                $parameterStrings[] = HttpUtil::urlEncode($k)
112                    . '=' . HttpUtil::urlEncode((string) $v);
113            } else {
114                //对于没有值的,只将key编码后放在=号的左边,右边留空
115                $parameterStrings[] = HttpUtil::urlEncode($k) . '=';
116            }
117        }
118        //按照字典序排序
119        sort($parameterStrings);
120
121        //使用'&'符号连接它们
122        return implode('&', $parameterStrings);
123    }
124
125    //生成标准化uri
126    public static function getCanonicalURIPath($path)
127    {
128        //空路径设置为'/'
129        if (empty($path)) {
130            return '/';
131        } else {
132            //所有的uri必须以'/'开头
133            if ($path[0] == '/') {
134                return HttpUtil::urlEncodeExceptSlash($path);
135            } else {
136                return '/' . HttpUtil::urlEncodeExceptSlash($path);
137            }
138        }
139    }
140
141    //生成标准化http请求头串
142    public static function getCanonicalHeaders($headers)
143    {
144        //如果没有headers,则返回空串
145        if (count($headers) == 0) {
146            return '';
147        }
148
149        $headerStrings = array();
150        foreach ($headers as $k => $v) {
151            //跳过key为null的
152            if ($k === null) {
153                continue;
154            }
155            //如果value为null,则赋值为空串
156            if ($v === null) {
157                $v = '';
158            }
159            //trim后再encode,之后使用':'号连接起来
160            $headerStrings[] = HttpUtil::urlEncode(strtolower(trim($k))) . ':' . HttpUtil::urlEncode(trim($v));
161        }
162        //字典序排序
163        sort($headerStrings);
164
165        //用'\n'把它们连接起来
166        return implode("\n", $headerStrings);
167    }
168
169}
170HttpUtil::__init();
171
172
173class SampleSigner
174{
175
176    const BCE_AUTH_VERSION = "bce-auth-v1";
177    const BCE_PREFIX = 'x-bce-';
178
179    //不指定headersToSign情况下,默认签名http头,包括:
180    //    1.host
181    //    2.content-length
182    //    3.content-type
183    //    4.content-md5
184    public static $defaultHeadersToSign;
185
186    public static function  __init()
187    {
188        SampleSigner::$defaultHeadersToSign = array(
189            "host",
190            "content-length",
191            "content-type",
192            "content-md5",
193        );
194    }
195
196    //签名函数
197    public function sign(
198        array $credentials,
199        $httpMethod,
200        $path,
201        $headers,
202        $params,
203        $options = array()
204    ) {
205        //设定签名有效时间
206        if (!isset($options[SignOption::EXPIRATION_IN_SECONDS])) {
207            //默认值1800秒
208            $expirationInSeconds = SignOption::DEFAULT_EXPIRATION_IN_SECONDS;
209        } else {
210            $expirationInSeconds = $options[SignOption::EXPIRATION_IN_SECONDS];
211        }
212
213        //解析ak sk
214        $accessKeyId = $credentials['ak'];
215        $secretAccessKey = $credentials['sk'];
216
217        //设定时间戳,注意:如果自行指定时间戳需要为UTC时间
218        if (!isset($options[SignOption::TIMESTAMP])) {
219            //默认值当前时间
220            $timestamp = new \DateTime();
221        } else {
222            $timestamp = $options[SignOption::TIMESTAMP];
223        }
224        $timestamp->setTimezone(new \DateTimeZone("UTC"));
225
226        //生成authString
227        $authString = SampleSigner::BCE_AUTH_VERSION . '/' . $accessKeyId . '/'
228            . $timestamp->format("Y-m-d\TH:i:s\Z") . '/' . $expirationInSeconds;
229
230        //使用sk和authString生成signKey
231        $signingKey = hash_hmac('sha256', $authString, $secretAccessKey);
232
233        //生成标准化URI
234        $canonicalURI = HttpUtil::getCanonicalURIPath($path);
235
236        //生成标准化QueryString
237        $canonicalQueryString = HttpUtil::getCanonicalQueryString($params);
238
239        //填充headersToSign,也就是指明哪些header参与签名
240        $headersToSignOption = null;
241        if (isset($options[SignOption::HEADERS_TO_SIGN])) {
242            $headersToSignOption = $options[SignOption::HEADERS_TO_SIGN];
243        }
244        
245        $headersToSign = SampleSigner::getHeadersToSign($headers, $headersToSignOption);
246        
247        //生成标准化header
248        $canonicalHeader = HttpUtil::getCanonicalHeaders($headersToSign);
249        
250        $headersToSign = array_keys($headersToSign);
251        sort($headersToSign);
252        //整理headersToSign,以';'号连接
253        $signedHeaders = '';
254        if ($headersToSignOption !== null) {
255            $signedHeaders = strtolower(
256                trim(implode(";", $headersToSign))
257            );
258        }
259
260        //组成标准请求串
261        $canonicalRequest = "$httpMethod\n$canonicalURI\n"
262            . "$canonicalQueryString\n$canonicalHeader";
263
264        //使用signKey和标准请求串完成签名
265        $signature = hash_hmac('sha256', $canonicalRequest, $signingKey);
266
267        //组成最终签名串
268        $authorizationHeader = "$authString/$signedHeaders/$signature";
269
270        return $authorizationHeader;
271    }
272
273    /** 根据headsToSign过滤应该参与签名的header
274     * 
275     * @param $headers array
276     * @param $headersToSign array
277     * @return array
278     */
279    public static function getHeadersToSign($headers, $headersToSign)
280    {
281
282        $ret = array();
283        if ($headersToSign !== null) {
284            $tmp = array();
285
286            //处理headers的key:去掉前后的空白并转化成小写
287            foreach ($headersToSign as $header) {
288                $tmp[] = strtolower(trim($header));
289            }
290            $headersToSign = $tmp;
291        }
292        foreach ($headers as $k => $v) {
293            if (trim((string) $v) !== '') {
294                if ($headersToSign !== null) {
295                    //预处理headersToSign:去掉前后的空白并转化成小写
296                    if (in_array(strtolower(trim($k)), $headersToSign)) {
297                        $ret[$k] = $v;
298                    }
299                } else {
300                    //如果没有headersToSign,则根据默认规则来选取headers
301                    if (SampleSigner::isDefaultHeaderToSign($k, $headersToSign)) {
302                        $ret[$k] = $v;
303                    }
304                }
305            }
306        }
307        return $ret;
308    }
309
310    /**
311     * 检查header是不是默认参加签名的:
312     * 1.是host、content-type、content-md5、content-length之一
313     * 2.以x-bce开头
314     *
315     * @param $header string
316     * @return bool
317     */
318    public static function isDefaultHeaderToSign($header)
319    {
320        $header = strtolower(trim($header));
321        if (in_array($header, SampleSigner::$defaultHeadersToSign)) {
322            return true;
323        }
324        $prefix = substr($header, 0, strlen(SampleSigner::BCE_PREFIX));
325        if ($prefix === SampleSigner::BCE_PREFIX) {
326            return true;
327        } else {
328            return false;
329        }
330    }
331}
332
333SampleSigner::__init();
334
335
336
337//签名示范代码
338$signer = new SampleSigner();
339$credentials = array("ak" => "0b0f67dfb88244b289b72b142befad0c","sk" => "bad522c2126a4618a8125f4b6cf6356f");
340$httpMethod = "PUT";
341$path = "/v1/test/myfolder/readme.txt";
342$headers = array("Host" => "bj.bcebos.com",
343                "Content-Length" => 8,
344                "Content-MD5" => "0a52730597fb4ffa01fc117d9e71e3a9",
345                "Content-Type" => "text/plain",
346                "x-bce-date" => "2015-04-27T08:23:49Z");
347$params = array("partNumber" => 9, "uploadId" => "VXBsb2FkIElpZS5tMnRzIHVwbG9hZA");
348date_default_timezone_set("PRC");
349$timestamp = new \DateTime();
350$timestamp->setTimestamp(1430123029);
351$options = array(SignOption::TIMESTAMP => $timestamp);
352// $options = array(SignOption::TIMESTAMP => $timestamp, SignOption::HEADERS_TO_SIGN => array("Content-Type", "Host", "x-bce-date"));
353$ret = $signer->sign($credentials, $httpMethod, $path, $headers, $params, $options);
354print $ret;Java示例
用户可参考以下代码,进一步了解百度智能云API认证机制。
说明:Android语言的API认证示例代码和Java示例代码一致。
                Plain Text
                
            
            1/*
2 * Copyright (c) 2014 Baidu.com, Inc. All Rights Reserved
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5 * the License. You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11 * specific language governing permissions and limitations under the License.
12 */
13
14package com.baidubce.auth;
15
16import com.baidubce.BceClientException;
17import com.baidubce.http.Headers;
18import com.baidubce.internal.InternalRequest;
19import com.baidubce.util.DateUtils;
20import com.baidubce.util.HttpUtils;
21import com.google.common.base.Joiner;
22import com.google.common.collect.Lists;
23import com.google.common.collect.Maps;
24import com.google.common.collect.Sets;
25
26import org.apache.commons.codec.binary.Hex;
27import org.slf4j.Logger;
28import org.slf4j.LoggerFactory;
29
30import javax.crypto.Mac;
31import javax.crypto.spec.SecretKeySpec;
32
33import java.nio.charset.Charset;
34import java.util.Collections;
35import java.util.Date;
36import java.util.List;
37import java.util.Map;
38import java.util.Set;
39import java.util.SortedMap;
40
41import static com.google.common.base.Preconditions.checkNotNull;
42
43/**
44 * The V1 implementation of Signer with the BCE signing protocol.
45 */
46public class BceV1Signer implements Signer {
47
48    private static final Logger logger = LoggerFactory.getLogger(BceV1Signer.class);
49
50    private static final String BCE_AUTH_VERSION = "bce-auth-v1";
51    private static final String DEFAULT_ENCODING = "UTF-8";
52    private static final Charset UTF8 = Charset.forName(DEFAULT_ENCODING);
53
54    // Default headers to sign with the BCE signing protocol.
55    private static final Set<String> defaultHeadersToSign = Sets.newHashSet();
56    private static final Joiner headerJoiner = Joiner.on('\n');
57    private static final Joiner signedHeaderStringJoiner = Joiner.on(';');
58
59    static {
60        BceV1Signer.defaultHeadersToSign.add(Headers.HOST.toLowerCase());
61        BceV1Signer.defaultHeadersToSign.add(Headers.CONTENT_LENGTH.toLowerCase());
62        BceV1Signer.defaultHeadersToSign.add(Headers.CONTENT_TYPE.toLowerCase());
63        BceV1Signer.defaultHeadersToSign.add(Headers.CONTENT_MD5.toLowerCase());
64    }
65
66    /**
67     * @see com.baidubce.auth.Signer#sign(InternalRequest, BceCredentials)
68     */
69    @Override
70    public void sign(InternalRequest request, BceCredentials credentials) {
71        this.sign(request, credentials, null);
72    }
73
74    /**
75     * Sign the given request with the given set of credentials. Modifies the passed-in request to apply the signature.
76     *
77     * @param request     the request to sign.
78     * @param credentials the credentials to sign the request with.
79     * @param options     the options for signing.
80     */
81    @Override
82    public void sign(InternalRequest request, BceCredentials credentials, SignOptions options) {
83        checkNotNull(request, "request should not be null.");
84
85        if (credentials == null) {
86            return;
87        }
88
89        if (options == null) {
90            if (request.getSignOptions() != null) {
91                options = request.getSignOptions();
92            } else {
93                options = SignOptions.DEFAULT;
94            }
95        }
96
97        String accessKeyId = credentials.getAccessKeyId();
98        String secretAccessKey = credentials.getSecretKey();
99
100        request.addHeader(Headers.HOST, HttpUtils.generateHostHeader(request.getUri()));
101
102        Date timestamp = options.getTimestamp();
103        if (timestamp == null) {
104            timestamp = new Date();
105        }
106
107        String authString =
108                BceV1Signer.BCE_AUTH_VERSION + "/" + accessKeyId + "/"
109                        + DateUtils.formatAlternateIso8601Date(timestamp) + "/" + options.getExpirationInSeconds();
110
111        String signingKey = this.sha256Hex(secretAccessKey, authString);
112        // Formatting the URL with signing protocol.
113        String canonicalURI = this.getCanonicalURIPath(request.getUri().getPath());
114        // Formatting the query string with signing protocol.
115        String canonicalQueryString = HttpUtils.getCanonicalQueryString(request.getParameters(), true);
116        // Sorted the headers should be signed from the request.
117        SortedMap<String, String> headersToSign =
118                this.getHeadersToSign(request.getHeaders(), options.getHeadersToSign());
119        // Formatting the headers from the request based on signing protocol.
120        String canonicalHeader = this.getCanonicalHeaders(headersToSign);
121        String signedHeaders = "";
122        if (options.getHeadersToSign() != null) {
123            signedHeaders = BceV1Signer.signedHeaderStringJoiner.join(headersToSign.keySet());
124            signedHeaders = signedHeaders.trim().toLowerCase();
125        }
126
127        String canonicalRequest =
128                request.getHttpMethod() + "\n" + canonicalURI + "\n" + canonicalQueryString + "\n" + canonicalHeader;
129
130        // Signing the canonical request using key with sha-256 algorithm.
131        String signature = this.sha256Hex(signingKey, canonicalRequest);
132
133        String authorizationHeader = authString + "/" + signedHeaders + "/" + signature;
134
135        logger.debug("CanonicalRequest:{}\tAuthorization:{}", canonicalRequest.replace("\n", "[\\n]"),
136                authorizationHeader);
137
138        request.addHeader(Headers.AUTHORIZATION, authorizationHeader);
139    }
140
141    private String getCanonicalURIPath(String path) {
142        if (path == null) {
143            return "/";
144        } else if (path.startsWith("/")) {
145            return HttpUtils.normalizePath(path);
146        } else {
147            return "/" + HttpUtils.normalizePath(path);
148        }
149    }
150
151    private String getCanonicalHeaders(SortedMap<String, String> headers) {
152        if (headers.isEmpty()) {
153            return "";
154        }
155
156        List<String> headerStrings = Lists.newArrayList();
157        for (Map.Entry<String, String> entry : headers.entrySet()) {
158            String key = entry.getKey();
159            if (key == null) {
160                continue;
161            }
162            String value = entry.getValue();
163            if (value == null) {
164                value = "";
165            }
166            headerStrings.add(HttpUtils.normalize(key.trim().toLowerCase()) + ':' + HttpUtils.normalize(value.trim()));
167        }
168        Collections.sort(headerStrings);
169
170        return headerJoiner.join(headerStrings);
171    }
172
173    private SortedMap<String, String> getHeadersToSign(Map<String, String> headers, Set<String> headersToSign) {
174        SortedMap<String, String> ret = Maps.newTreeMap();
175        if (headersToSign != null) {
176            Set<String> tempSet = Sets.newHashSet();
177            for (String header : headersToSign) {
178                tempSet.add(header.trim().toLowerCase());
179            }
180            headersToSign = tempSet;
181        }
182        for (Map.Entry<String, String> entry : headers.entrySet()) {
183            String key = entry.getKey();
184            if (entry.getValue() != null && !entry.getValue().isEmpty()) {
185                if ((headersToSign == null && this.isDefaultHeaderToSign(key))
186                        || (headersToSign != null && headersToSign.contains(key.toLowerCase())
187                                && !Headers.AUTHORIZATION.equalsIgnoreCase(key))) {
188                    ret.put(key, entry.getValue());
189                }
190            }
191        }
192        return ret;
193    }
194
195    private boolean isDefaultHeaderToSign(String header) {
196        header = header.trim().toLowerCase();
197        return header.startsWith(Headers.BCE_PREFIX) || defaultHeadersToSign.contains(header);
198    }
199
200    private String sha256Hex(String signingKey, String stringToSign) {
201        try {
202            Mac mac = Mac.getInstance("HmacSHA256");
203            mac.init(new SecretKeySpec(signingKey.getBytes(UTF8), "HmacSHA256"));
204            return new String(Hex.encodeHex(mac.doFinal(stringToSign.getBytes(UTF8))));
205        } catch (Exception e) {
206            throw new BceClientException("Fail to generate the signature", e);
207        }
208    }
209
210}Javascript示例
用户可参考以下代码,进一步了解Javascript语言的百度智能云API认证机制。
代码下载路径:https://github.com/baidubce/bce-sdk-js/blob/master/test/sdk/auth.spec.js
                Plain Text
                
            
            1var Auth = require('@baiducloud/sdk').Auth;
2
3
4    var auth = new Auth('my_ak', 'my_sk');
5
6    var method = 'PUT';
7    var uri = '/v1/bucket/object1'; // 请勿传入已encode的字符串,sdk执行的时候会encode一次
8    var params = {
9        A: null,
10        b: '',
11        C: 'd'
12    };
13    var headers = {
14        'Host': 'bce.baidu.com',
15        'abc': '123',
16        'x-bce-meta-key1': 'ABC'
17    };
18    var headersToSign = ['Host', 'abc', 'x-bce-meta-key1'];
19
20    var signature = auth.generateAuthorization(method, uri, params, headers, 1402639056, 1800, headersToSign);
21    expect(signature).to.eql('bce-auth-v1/my_ak/2014-06-13T05:57:36Z/1800/host;x-bce-meta-key1/'
22                             + '80c9672aca2ea9af4bb40b9a8ff458d72df94e97d550840727f3a929af271d25');
23
24    signature = auth.generateAuthorization(method, uri, params, headers, 1402639056, 1800, headersToSign);
25    expect(signature).to.eql('bce-auth-v1/my_ak/2014-06-13T05:57:36Z/1800/host;'
26                              + 'x-bce-meta-key1/80c9672aca2ea9af4bb40b9a8ff458d72'
27                              + 'df94e97d550840727f3a929af271d25');
28
29    method = 'DELETE';
30    uri = '/v1/test-bucket1361199862';
31    params = {};
32    headers = {
33        'Content-Type': 'application/json; charset=utf-8',
34        'Content-Length': 0,
35        'User-Agent': 'This is the user-agent'
36    };
37    signature = auth.generateAuthorization(method, uri, params, headers, 1402639056, 1800);
38    expect(signature).to.eql('bce-auth-v1/my_ak/2014-06-13T05:57:36Z/1800/'
39                              + 'content-length;content-type/'
40                              + 'c9386b15d585960ae5e6972f73ed92a9a682dc81025480ba5b41206d3e489822');C#示例
用户可参考以下代码,进一步了解C#语言的百度智能云API认证机制。完整示例代码:
                Plain Text
                
            
            1using BaiduBce;
2using BaiduBce.Auth;
3using BaiduBce.Services.Bos;
4using BaiduBce.Services.Bos.Model;
5using BaiduBce.Services.Sts;
6using System;
7using System.Collections.Generic;
8using System.Collections.Specialized;
9using System.Diagnostics;
10using System.Globalization;
11using System.IO;
12using System.Net;
13using System.Security.Cryptography;
14using System.Text;
15using System.Web;
16
17namespace BOSTest
18{
19    class Program
20    {
21        static string UriEncode(string input, bool encodeSlash = false)
22        {
23            StringBuilder builder = new StringBuilder();
24            foreach (byte b in Encoding.UTF8.GetBytes(input))
25            {
26                if ((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || (b >= '0' && b <= '9') || b == '_' || b == '-' || b == '~' || b == '.')
27                {
28                    builder.Append((char)b);
29                }
30                else if (b == '/')
31                {
32                    if (encodeSlash)
33                    {
34                        builder.Append("%2F");
35                    }
36                    else
37                    {
38                        builder.Append((char)b);
39                    }
40                }
41                else
42                {
43                    builder.Append('%').Append(b.ToString("X2"));
44                }
45            }
46            return builder.ToString();
47        }
48
49        static string Hex(byte[] data)
50        {
51            var sb = new StringBuilder();
52            foreach (var b in data)
53            {
54                sb.Append(b.ToString("x2"));
55            }
56            return sb.ToString();
57        }
58
59        static string CanonicalRequest(HttpWebRequest req)
60        {
61            Uri uri = req.RequestUri;
62            StringBuilder canonicalReq = new StringBuilder();
63            canonicalReq.Append(req.Method).Append("\n").Append(UriEncode(Uri.UnescapeDataString(uri.AbsolutePath))).Append("\n");
64
65            var parameters = HttpUtility.ParseQueryString(uri.Query);
66            List<string> parameterStrings = new List<string>();
67            foreach (KeyValuePair<string, string> entry in parameters)
68            {
69                parameterStrings.Add(UriEncode(entry.Key) + '=' + UriEncode(entry.Value));
70            }
71            parameterStrings.Sort();
72            canonicalReq.Append(string.Join("&", parameterStrings.ToArray())).Append("\n");
73
74            string host = uri.Host;
75            if (!(uri.Scheme == "https" && uri.Port == 443) && !(uri.Scheme == "http" && uri.Port == 80))
76            {
77                host += ":" + uri.Port;
78            }
79            canonicalReq.Append("host:" + UriEncode(host));
80            return canonicalReq.ToString();
81        }
82
83        static void Main(string[] args)
84        {
85            string bucket = "mybucket";
86            string key = "我的文件";
87            string ak = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
88            string sk = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
89            DateTime now = DateTime.Now;
90            int expirationInSeconds = 1200;
91
92            HttpWebRequest req = WebRequest.Create("http://bj.bcebos.com/" + bucket + "/" + key) as HttpWebRequest;
93            Uri uri = req.RequestUri;
94            req.Method = "GET";
95
96            string signDate = now.ToUniversalTime().ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ssK");
97            Console.WriteLine(signDate);
98            string authString = "bce-auth-v1/" + ak + "/" + signDate + "/" + expirationInSeconds;
99            string signingKey = Hex(new HMACSHA256(Encoding.UTF8.GetBytes(sk)).ComputeHash(Encoding.UTF8.GetBytes(authString)));
100            Console.WriteLine(signingKey);
101
102            string canonicalRequestString = CanonicalRequest(req);
103            Console.WriteLine(canonicalRequestString);
104
105            string signature = Hex(new HMACSHA256(Encoding.UTF8.GetBytes(signingKey)).ComputeHash(Encoding.UTF8.GetBytes(canonicalRequestString)));
106            string authorization = authString + "/host/" + signature;
107            Console.WriteLine(authorization);
108
109            req.Headers.Add("x-bce-date", signDate);
110            req.Headers.Add(HttpRequestHeader.Authorization, authorization);
111
112            HttpWebResponse res;
113            string message = "";
114            try
115            {
116                res = req.GetResponse() as HttpWebResponse;
117            }
118            catch (WebException e)
119            {
120                res = e.Response as HttpWebResponse;
121                message = new StreamReader(res.GetResponseStream()).ReadToEnd();
122            }
123            Console.WriteLine((int)res.StatusCode);
124            Console.WriteLine(res.Headers);
125            Console.WriteLine(message);
126            Console.ReadLine();
127        }
128    }
129}iOS示例
用户可参考以下代码,进一步了解iOS的百度智能云API认证机制。代码包含类代码和调用代码两部分,完整代码地址:代码。调用代码请参考:
                Plain Text
                
            
            1#import <XCTest/XCTest.h>
2#import "BDCloudSigner.h"
3
4@interface UT : XCTestCase
5@end
6
7@implementation UT
8
9id<BDCloudSigner> createSigner() {
10    BDCloudCredentials* credentials = [BDCloudCredentials new];
11    credentials.accessKey = @"<access key>";
12    credentials.secretKey = @"<secret key>";
13
14    id<BDCloudSigner> signer = [[BDCloudAKSKSigner alloc] initWithCredentials:credentials];
15    signer.expiredTimeInSeconds = 3600;
16
17    return signer;
18}
19
20NSMutableURLRequest* createRequest() {
21    // create url directly, or use NSURLComponents.
22    NSURL* url = [NSURL URLWithString:@"http://bj.bcebos.com/v1/bucket/object?append"];
23
24    // create request.
25    NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:url];
26    request.HTTPMethod = @"POST";
27    [request setValue:@"<length>" forHTTPHeaderField:@"Content-Length"];
28    [request setValue:@"<md5>" forHTTPHeaderField:@"Content-MD5"];
29    [request setValue:@"text/plain" forHTTPHeaderField:@"Content-Type"];
30
31    // custom metadata key should begin with lower case prefix 'x-bce-'.
32    [request setValue:@"2017-01-08T21:42:30Z" forHTTPHeaderField:@"x-bce-user-metadata-createtime"];
33
34    // Host will be set when call sign.
35    //[request setValue:@"bj.bcebos.com" forHTTPHeaderField:@"Host"];
36
37    return request;
38}
39
40void sign() {
41    id<BDCloudSigner> signer = createSigner();
42    NSMutableURLRequest* request = createRequest();
43    if (![signer sign:request]) {
44        return;
45    }
46
47    // url
48    NSURL* fileURL = [NSURL fileURLWithPath:@"<file path>"];
49
50    // send request
51    // sample purpose, don't care task will running correctly.
52    [[NSURLSession sharedSession] uploadTaskWithRequest:request
53                                               fromFile:fileURL];
54}
55
56- (void)testAKSKSigner {
57    sign();
58}
59
60@endC++示例
用户可参考以下代码,进一步了解C++的百度智能云API认证机制。完整代码可下载百度智能云C++ sdk参考,下载地址:C++ SDK
                Plain Text
                
            
            1std::string DefaultSigner::generate_auth(HttpRequest *request, int expire_seconds) {
2    std::stringstream auth_str;
3    std::string sign_time = DateUtil::format_iso8601_date(time(NULL));
4    auth_str << "bce-auth-v1/" << _credential.ak() << "/" << sign_time << "/" << expire_seconds;
5    std::string sign_key = StringUtil::hmac_sha256_hex(_credential.sk(), auth_str.str());
6
7    std::ostringstream canonical_req;
8    // method
9    canonical_req << method_str(request->method()) << '\n';
10    // uri
11    canonical_req << StringUtil::url_encode(request->uri(), false) << '\n';
12    // query string
13    const StringMap ¶ms = request->parameters();
14    if (params.size() > 0) {
15        StringMap tmp;
16        for (StringMap::const_iterator it = params.begin(); it != params.end(); ++it) {
17            std::ostringstream p;
18            p << StringUtil::url_encode(StringUtil::trim(it->first)) << '='
19                << StringUtil::url_encode(StringUtil::trim(it->second));
20            tmp[p.str()] = "";
21        }
22        StringMap::iterator it = tmp.begin();
23        canonical_req << it->first;
24        for (++it; it != tmp.end(); ++it) {
25            canonical_req << '&' << it->first;
26        }
27    }
28    canonical_req << '\n';
29
30    bool use_sts = !_credential.sts_token().empty();
31    // header
32    canonical_req << "host:" << StringUtil::url_encode(request->host());
33    if (use_sts) {
34        canonical_req << "\nx-bce-security-token:" << StringUtil::url_encode(_credential.sts_token());
35    }
36    LOG(DEBUG) << "canonical request: [" << canonical_req.str() << ']';
37
38    std::string signature = StringUtil::hmac_sha256_hex(sign_key, canonical_req.str());
39    auth_str << "/host";
40    if (use_sts) {
41        auth_str << ";x-bce-security-token";
42        request->append_header("x-bce-security-token", _credential.sts_token());
43    }
44    auth_str << "/" << signature;
45    return auth_str.str();
46}
47
48void DefaultSigner::sign(HttpRequest *request) {
49    request->append_header("Authorization", generate_auth(request, _sign_expire_seconds));
50}