相关参考Reference

    Sample-Code

    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请求如下:

    PUT /test/myfolder/readme.txt?partNumber=9&uploadId=a44cc9bab11cbd156984767aad637851 HTTP/1.1
    Host: bj.bcebos.com
    Date: Mon, 27 Apr 2015 16:23:49 +0800
    Content-Type: text/plain
    Content-Length: 8
    Content-Md5: NFzcPqhviddjRNnSOGo4rw==
    x-bce-date: 2015-04-27T08:23:49Z
    
    Example

    用户可根据上述HTTP请求填写以下函数中的各个字段。

     if __name__ == "__main__":
         credentials = BceCredentials("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")
         http_method = "PUT"
         path = "/test/myfolder/readme.txt"
         headers = {"host": "bj.bcebos.com",
                    "content-length": 8,
                    "content-md5": "NFzcPqhviddjRNnSOGo4rw==",
                    "content-type":"text/plain",
                    "x-bce-date": "2015-04-27T08:23:49Z"}
         params = {"partNumber": 9,
                   "uploadId": "a44cc9bab11cbd156984767aad637851"}
         timestamp = 1430123029
         result = sign(credentials, http_method, path, headers, params, timestamp)
         print result

    请点击此处获取完整代码

    完整代码内容如下:

     # -*- coding: UTF-8 -*-
     import hashlib
     import hmac
     import string
     import datetime
     
     
     AUTHORIZATION = "authorization"
     BCE_PREFIX = "x-bce-"
     DEFAULT_ENCODING = 'UTF-8'
     
     
     # 保存AK/SK的类
     class BceCredentials(object):
         def __init__(self, access_key_id, secret_access_key):
             self.access_key_id = access_key_id
             self.secret_access_key = secret_access_key
     
     
     # 根据RFC 3986,除了:
     #   1.大小写英文字符
     #   2.阿拉伯数字
     #   3.点'.'、波浪线'~'、减号'-'以及下划线'_'
     # 以外都要编码
     RESERVED_CHAR_SET = set(string.ascii_letters + string.digits + '.~-_')
     def get_normalized_char(i):
         char = chr(i)
         if char in RESERVED_CHAR_SET:
             return char
         else:
             return '%%%02X' % i
     NORMALIZED_CHAR_LIST = [get_normalized_char(i) for i in range(256)]
     
     
     # 正规化字符串
     def normalize_string(in_str, encoding_slash=True):
         if in_str is None:
             return ''
     
         # 如果输入是unicode,则先使用UTF8编码之后再编码
         in_str = in_str.encode(DEFAULT_ENCODING) if isinstance(in_str, unicode) else str(in_str)
     
         # 在生成规范URI时。不需要对斜杠'/'进行编码,其他情况下都需要
         if encoding_slash:
             encode_f = lambda c: NORMALIZED_CHAR_LIST[ord(c)]
         else:
             # 仅仅在生成规范URI时。不需要对斜杠'/'进行编码
             encode_f = lambda c: NORMALIZED_CHAR_LIST[ord(c)] if c != '/' else c
     
         # 按照RFC 3986进行编码
         return ''.join([encode_f(ch) for ch in in_str])
     
     
     # 生成规范时间戳
     def get_canonical_time(timestamp=0):
         # 不使用任何参数调用的时候返回当前时间
         if timestamp == 0:
             utctime = datetime.datetime.utcnow()
         else:
             utctime = datetime.datetime.utcfromtimestamp(timestamp)
     
         # 时间戳格式:[year]-[month]-[day]T[hour]:[minute]:[second]Z
         return "%04d-%02d-%02dT%02d:%02d:%02dZ" % (
             utctime.year, utctime.month, utctime.day,
             utctime.hour, utctime.minute, utctime.second)
     
     
     # 生成规范URI
     def get_canonical_uri(path):
         # 规范化URI的格式为:/{bucket}/{object},并且要对除了斜杠"/"之外的所有字符编码
         return normalize_string(path, False)
     
     
     # 生成规范query string
     def get_canonical_querystring(params):
         if params is None:
             return ''
     
         # 除了authorization之外,所有的query string全部加入编码
         result = ['%s=%s' % (normalize_string(k), normalize_string(v)) for k, v in params.items() if k.lower != AUTHORIZATION]
     
         # 按字典序排序
         result.sort()
     
         # 使用&符号连接所有字符串并返回
         return '&'.join(result)
     
     
     # 生成规范header
     def get_canonical_headers(headers, headers_to_sign=None):
         headers = headers or {}
     
         # 没有指定header_to_sign的情况下,默认使用:
         #   1.host
         #   2.content-md5
         #   3.content-length
         #   4.content-type
         #   5.所有以x-bce-开头的header项
         # 生成规范header
         if headers_to_sign is None or len(headers_to_sign) == 0:
             headers_to_sign = {"host", "content-md5", "content-length", "content-type"}
     
         # 对于header中的key,去掉前后的空白之后需要转化为小写
         # 对于header中的value,转化为str之后去掉前后的空白
         f = lambda (key, value): (key.strip().lower(), str(value).strip())
     
         result = []
         for k, v in map(f, headers.iteritems()):
             # 无论何种情况,以x-bce-开头的header项都需要被添加到规范header中
             if k.startswith(BCE_PREFIX) or k in headers_to_sign:
                 result.append("%s:%s" % (normalize_string(k), normalize_string(v)))
     
         # 按照字典序排序
         result.sort()
     
         # 使用\n符号连接所有字符串并返回
         return '\n'.join(result)
     
     
     # 签名主算法
     def sign(credentials, http_method, path, headers, params,
              timestamp=0, expiration_in_seconds=1800, headers_to_sign=None):
         headers = headers or {}
         params = params or {}
     
         # 1.生成sign key
         # 1.1.生成auth-string,格式为:bce-auth-v1/{accessKeyId}/{timestamp}/{expirationPeriodInSeconds}
         sign_key_info = 'bce-auth-v1/%s/%s/%d' % (
             credentials.access_key_id,
             get_canonical_time(timestamp),
             expiration_in_seconds)
         # 1.2.使用auth-string加上SK,用SHA-256生成sign key
         sign_key = hmac.new(
             credentials.secret_access_key,
             sign_key_info,
             hashlib.sha256).hexdigest()
     
         # 2.生成规范化uri
         canonical_uri = get_canonical_uri(path)
     
         # 3.生成规范化query string
         canonical_querystring = get_canonical_querystring(params)
     
         # 4.生成规范化header
         canonical_headers = get_canonical_headers(headers, headers_to_sign)
     
         # 5.使用'\n'将HTTP METHOD和2、3、4中的结果连接起来,成为一个大字符串
         string_to_sign = '\n'.join(
             [http_method, canonical_uri, canonical_querystring, canonical_headers])
     
         # 6.使用5中生成的签名串和1中生成的sign key,用SHA-256算法生成签名结果
         sign_result = hmac.new(sign_key, string_to_sign, hashlib.sha256).hexdigest()
     
         # 7.拼接最终签名结果串
         if headers_to_sign:
             # 指定header to sign
             result = '%s/%s/%s' % (sign_key_info, ';'.join(headers_to_sign), sign_result)
         else:
             # 不指定header to sign情况下的默认签名结果串
             result = '%s//%s' % (sign_key_info, sign_result)
     
         return result
     
     if __name__ == "__main__":
         credentials = BceCredentials("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")
         http_method = "PUT"
         path = "/test/myfolder/readme.txt"
         headers = {"host": "bj.bcebos.com",
                    "content-length": 8,
                    "content-md5": "NFzcPqhviddjRNnSOGo4rw==",
                    "content-type":"text/plain",
                    "x-bce-date": "2015-04-27T08:23:49Z"}
         params = {"partNumber": 9,
                   "uploadId": "a44cc9bab11cbd156984767aad637851"}
         timestamp = 1430123029
         result = sign(credentials, http_method, path, headers, params, timestamp)
         print result

    Php示例

    假设用户向北京的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请求如下:

    PUT /test/myfolder/readme.txt?partNumber=9&uploadId=a44cc9bab11cbd156984767aad637851 HTTP/1.1
    Host: bj.bcebos.com
    Date: Mon, 27 Apr 2015 16:23:49 +0800
    Content-Type: text/plain
    Content-Length: 8
    Content-Md5: NFzcPqhviddjRNnSOGo4rw==
    x-bce-date: 2015-04-27T08:23:49Z
    
    Example

    签名示范代码

    $signer = new SampleSigner();
    $credentials = array("ak" => "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","sk" => "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
    $httpMethod = "PUT";
    $path = "/v1/test/myfolder/readme.txt";
    $headers = array("Host" => "bj.bcebos.com",
                    "Content-Length" => 8,
                    "Content-MD5" => "NFzcPqhviddjRNnSOGo4rw==",
                    "Content-Type" => "text/plain",
                    "x-bce-date" => "2015-04-27T08:23:49Z");
    $params = array("partNumber" => 9, "uploadId" => "a44cc9bab11cbd156984767aad637851");
    $timestamp = new \DateTime();
    $timestamp->setTimestamp(1430123029);
    $options = array(SignOption::TIMESTAMP => $timestamp);
    $ret = $signer->sign($credentials, $httpMethod, $path, $headers, $params, $options);
    print $ret;

    请点击此处获取完整代码

    完整代码内容如下:

    <?php
    /*
    * Copyright (c) 2014 Baidu.com, Inc. All Rights Reserved
    *
    * Licensed under the Apache License, Version 2.0 (the "License"); you may not
    * use this file except in compliance with the License. You may obtain a copy of
    * the License at
    *
    * Http://www.apache.org/licenses/LICENSE-2.0
    *
    * Unless required by applicable law or agreed to in writing, software
    * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    * License for the specific language governing permissions and limitations under
    * the License.
    */
    
    namespace BaiduBce\Auth;
    
    class SignOption
    {
        const EXPIRATION_IN_SECONDS = 'expirationInSeconds';
    
        const HEADERS_TO_SIGN = 'headersToSign';
    
        const TIMESTAMP = 'timestamp';
    
        const DEFAULT_EXPIRATION_IN_SECONDS = 1800;
    
        const MIN_EXPIRATION_IN_SECONDS = 300;
    
        const MAX_EXPIRATION_IN_SECONDS = 129600;
    }
    
    class HttpUtil
    {
        // 根据RFC 3986,除了:
        //   1.大小写英文字符
        //   2.阿拉伯数字
        //   3.点'.'、波浪线'~'、减号'-'以及下划线'_'
        // 以外都要编码
        public static $PERCENT_ENCODED_STRINGS;
    
        //填充编码数组
        public static function __init()
        {
            HttpUtil::$PERCENT_ENCODED_STRINGS = array();
            for ($i = 0; $i < 256; ++$i) {
                HttpUtil::$PERCENT_ENCODED_STRINGS[$i] = sprintf("%%%02X", $i);
            }
    
            //a-z不编码
            foreach (range('a', 'z') as $ch) {
                HttpUtil::$PERCENT_ENCODED_STRINGS[ord($ch)] = $ch;
            }
    
            //A-Z不编码
            foreach (range('A', 'Z') as $ch) {
                HttpUtil::$PERCENT_ENCODED_STRINGS[ord($ch)] = $ch;
            }
    
            //0-9不编码
            foreach (range('0', '9') as $ch) {
                HttpUtil::$PERCENT_ENCODED_STRINGS[ord($ch)] = $ch;
            }
    
            //以下4个字符不编码
            HttpUtil::$PERCENT_ENCODED_STRINGS[ord('-')] = '-';
            HttpUtil::$PERCENT_ENCODED_STRINGS[ord('.')] = '.';
            HttpUtil::$PERCENT_ENCODED_STRINGS[ord('_')] = '_';
            HttpUtil::$PERCENT_ENCODED_STRINGS[ord('~')] = '~';
        }
    
        //在uri编码中不能对'/'编码
        public static function urlEncodeExceptSlash($path)
        {
            return str_replace("%2F", "/", HttpUtil::urlEncode($path));
        }
    
        //使用编码数组编码
        public static function urlEncode($value)
        {
            $result = '';
            for ($i = 0; $i < strlen($value); ++$i) {
                $result .= HttpUtil::$PERCENT_ENCODED_STRINGS[ord($value[$i])];
            }
            return $result;
        }
    
        //生成标准化QueryString
        public static function getCanonicalQueryString(array $parameters)
        {
            //没有参数,直接返回空串
            if (count($parameters) == 0) {
                return '';
            }
    
            $parameterStrings = array();
            foreach ($parameters as $k => $v) {
                //跳过Authorization字段
                if (strcasecmp('Authorization', $k) == 0) {
                    continue;
                }
                if (!isset($k)) {
                    throw new \InvalidArgumentException(
                        "parameter key should not be null"
                    );
                }
                if (isset($v)) {
                    //对于有值的,编码后放在=号两边
                    $parameterStrings[] = HttpUtil::urlEncode($k)
                        . '=' . HttpUtil::urlEncode((string) $v);
                } else {
                    //对于没有值的,只将key编码后放在=号的左边,右边留空
                    $parameterStrings[] = HttpUtil::urlEncode($k) . '=';
                }
            }
            //按照字典序排序
            sort($parameterStrings);
    
            //使用'&'符号连接它们
            return implode('&', $parameterStrings);
        }
    
        //生成标准化uri
        public static function getCanonicalURIPath($path)
        {
            //空路径设置为'/'
            if (empty($path)) {
                return '/';
            } else {
                //所有的uri必须以'/'开头
                if ($path[0] == '/') {
                    return HttpUtil::urlEncodeExceptSlash($path);
                } else {
                    return '/' . HttpUtil::urlEncodeExceptSlash($path);
                }
            }
        }
    
        //生成标准化http请求头串
        public static function getCanonicalHeaders($headers)
        {
            //如果没有headers,则返回空串
            if (count($headers) == 0) {
                return '';
            }
    
            $headerStrings = array();
            foreach ($headers as $k => $v) {
                //跳过key为null的
                if ($k === null) {
                    continue;
                }
                //如果value为null,则赋值为空串
                if ($v === null) {
                    $v = '';
                }
                //trim后再encode,之后使用':'号连接起来
                $headerStrings[] = HttpUtil::urlEncode(strtolower(trim($k))) . ':' . HttpUtil::urlEncode(trim($v));
            }
            //字典序排序
            sort($headerStrings);
    
            //用'\n'把它们连接起来
            return implode("\n", $headerStrings);
        }
    
    }
    HttpUtil::__init();
    
    
    class SampleSigner
    {
    
        const BCE_AUTH_VERSION = "bce-auth-v1";
        const BCE_PREFIX = 'x-bce-';
    
        //不指定headersToSign情况下,默认签名http头,包括:
        //    1.host
        //    2.content-length
        //    3.content-type
        //    4.content-md5
        public static $defaultHeadersToSign;
    
        public static function  __init()
        {
            SampleSigner::$defaultHeadersToSign = array(
                "host",
                "content-length",
                "content-type",
                "content-md5",
            );
        }
    
        //签名函数
        public function sign(
            array $credentials,
            $httpMethod,
            $path,
            $headers,
            $params,
            $options = array()
        ) {
            //设定签名有效时间
            if (!isset($options[SignOption::EXPIRATION_IN_SECONDS])) {
                //默认值1800秒
                $expirationInSeconds = SignOption::DEFAULT_EXPIRATION_IN_SECONDS;
            } else {
                $expirationInSeconds = $options[SignOption::EXPIRATION_IN_SECONDS];
            }
    
            //解析ak sk
            $accessKeyId = $credentials['ak'];
            $secretAccessKey = $credentials['sk'];
    
            //设定时间戳,注意:如果自行指定时间戳需要为UTC时间
            if (!isset($options[SignOption::TIMESTAMP])) {
                //默认值当前时间
                $timestamp = new \DateTime();
            } else {
                $timestamp = $options[SignOption::TIMESTAMP];
            }
            $timestamp->setTimezone(new \DateTimeZone("UTC"));
    
            //生成authString
            $authString = SampleSigner::BCE_AUTH_VERSION . '/' . $accessKeyId . '/'
                . $timestamp->format("Y-m-d\TH:i:s\Z") . '/' . $expirationInSeconds;
    
            //使用sk和authString生成signKey
            $signingKey = hash_hmac('sha256', $authString, $secretAccessKey);
    
            //生成标准化URI
            $canonicalURI = HttpUtil::getCanonicalURIPath($path);
    
            //生成标准化QueryString
            $canonicalQueryString = HttpUtil::getCanonicalQueryString($params);
    
            //填充headersToSign,也就是指明哪些header参与签名
            $headersToSignOption = null;
            if (isset($options[SignOption::HEADERS_TO_SIGN])) {
                $headersToSignOption = $options[SignOption::HEADERS_TO_SIGN];
            }
            
            $headersToSign = SampleSigner::getHeadersToSign($headers, $headersToSignOption);
            
            //生成标准化header
            $canonicalHeader = HttpUtil::getCanonicalHeaders($headersToSign);
            
            $headersToSign = array_keys($headersToSign);
            sort($headersToSign);
            //整理headersToSign,以';'号连接
            $signedHeaders = '';
            if ($headersToSignOption !== null) {
                $signedHeaders = strtolower(
                    trim(implode(";", $headersToSign))
                );
            }
    
            //组成标准请求串
            $canonicalRequest = "$httpMethod\n$canonicalURI\n"
                . "$canonicalQueryString\n$canonicalHeader";
    
            //使用signKey和标准请求串完成签名
            $signature = hash_hmac('sha256', $canonicalRequest, $signingKey);
    
            //组成最终签名串
            $authorizationHeader = "$authString/$signedHeaders/$signature";
    
            return $authorizationHeader;
        }
    
        /** 根据headsToSign过滤应该参与签名的header
         * 
         * @param $headers array
         * @param $headersToSign array
         * @return array
         */
        public static function getHeadersToSign($headers, $headersToSign)
        {
    
            $ret = array();
            if ($headersToSign !== null) {
                $tmp = array();
    
                //处理headers的key:去掉前后的空白并转化成小写
                foreach ($headersToSign as $header) {
                    $tmp[] = strtolower(trim($header));
                }
                $headersToSign = $tmp;
            }
            foreach ($headers as $k => $v) {
                if (trim((string) $v) !== '') {
                    if ($headersToSign !== null) {
                        //预处理headersToSign:去掉前后的空白并转化成小写
                        if (in_array(strtolower(trim($k)), $headersToSign)) {
                            $ret[$k] = $v;
                        }
                    } else {
                        //如果没有headersToSign,则根据默认规则来选取headers
                        if (SampleSigner::isDefaultHeaderToSign($k, $headersToSign)) {
                            $ret[$k] = $v;
                        }
                    }
                }
            }
            return $ret;
        }
    
        /**
         * 检查header是不是默认参加签名的:
         * 1.是host、content-type、content-md5、content-length之一
         * 2.以x-bce开头
         *
         * @param $header string
         * @return bool
         */
        public static function isDefaultHeaderToSign($header)
        {
            $header = strtolower(trim($header));
            if (in_array($header, SampleSigner::$defaultHeadersToSign)) {
                return true;
            }
            $prefix = substr($header, 0, strlen(SampleSigner::BCE_PREFIX));
            if ($prefix === SampleSigner::BCE_PREFIX) {
                return true;
            } else {
                return false;
            }
        }
    }
    
    SampleSigner::__init();
    
    
    
    //签名示范代码
    $signer = new SampleSigner();
    $credentials = array("ak" => "0b0f67dfb88244b289b72b142befad0c","sk" => "bad522c2126a4618a8125f4b6cf6356f");
    $httpMethod = "PUT";
    $path = "/v1/test/myfolder/readme.txt";
    $headers = array("Host" => "bj.bcebos.com",
                    "Content-Length" => 8,
                    "Content-MD5" => "0a52730597fb4ffa01fc117d9e71e3a9",
                    "Content-Type" => "text/plain",
                    "x-bce-date" => "2015-04-27T08:23:49Z");
    $params = array("partNumber" => 9, "uploadId" => "VXBsb2FkIElpZS5tMnRzIHVwbG9hZA");
    date_default_timezone_set("PRC");
    $timestamp = new \DateTime();
    $timestamp->setTimestamp(1430123029);
    $options = array(SignOption::TIMESTAMP => $timestamp);
    // $options = array(SignOption::TIMESTAMP => $timestamp, SignOption::HEADERS_TO_SIGN => array("Content-Type", "Host", "x-bce-date"));
    $ret = $signer->sign($credentials, $httpMethod, $path, $headers, $params, $options);
    print $ret;

    Java示例

    用户可参考以下代码,进一步了解百度智能云API认证机制。

    代码下载路径:https://github.com/baidubce/bce-sdk-java/blob/master/src/main/java/com/baidubce/auth/BceV1Signer.java

    说明:Android语言的API认证示例代码和Java示例代码一致。

    /*
     * Copyright (c) 2014 Baidu.com, Inc. All Rights Reserved
     *
     * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
     * the License. You may obtain a copy of the License at
     *
     * http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
     * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
     * specific language governing permissions and limitations under the License.
     */
    
    package com.baidubce.auth;
    
    import com.baidubce.BceClientException;
    import com.baidubce.http.Headers;
    import com.baidubce.internal.InternalRequest;
    import com.baidubce.util.DateUtils;
    import com.baidubce.util.HttpUtils;
    import com.google.common.base.Joiner;
    import com.google.common.collect.Lists;
    import com.google.common.collect.Maps;
    import com.google.common.collect.Sets;
    
    import org.apache.commons.codec.binary.Hex;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import javax.crypto.Mac;
    import javax.crypto.spec.SecretKeySpec;
    
    import java.nio.charset.Charset;
    import java.util.Collections;
    import java.util.Date;
    import java.util.List;
    import java.util.Map;
    import java.util.Set;
    import java.util.SortedMap;
    
    import static com.google.common.base.Preconditions.checkNotNull;
    
    /**
     * The V1 implementation of Signer with the BCE signing protocol.
     */
    public class BceV1Signer implements Signer {
    
        private static final Logger logger = LoggerFactory.getLogger(BceV1Signer.class);
    
        private static final String BCE_AUTH_VERSION = "bce-auth-v1";
        private static final String DEFAULT_ENCODING = "UTF-8";
        private static final Charset UTF8 = Charset.forName(DEFAULT_ENCODING);
    
        // Default headers to sign with the BCE signing protocol.
        private static final Set<String> defaultHeadersToSign = Sets.newHashSet();
        private static final Joiner headerJoiner = Joiner.on('\n');
        private static final Joiner signedHeaderStringJoiner = Joiner.on(';');
    
        static {
            BceV1Signer.defaultHeadersToSign.add(Headers.HOST.toLowerCase());
            BceV1Signer.defaultHeadersToSign.add(Headers.CONTENT_LENGTH.toLowerCase());
            BceV1Signer.defaultHeadersToSign.add(Headers.CONTENT_TYPE.toLowerCase());
            BceV1Signer.defaultHeadersToSign.add(Headers.CONTENT_MD5.toLowerCase());
        }
    
        /**
         * @see com.baidubce.auth.Signer#sign(InternalRequest, BceCredentials)
         */
        @Override
        public void sign(InternalRequest request, BceCredentials credentials) {
            this.sign(request, credentials, null);
        }
    
        /**
         * Sign the given request with the given set of credentials. Modifies the passed-in request to apply the signature.
         *
         * @param request     the request to sign.
         * @param credentials the credentials to sign the request with.
         * @param options     the options for signing.
         */
        @Override
        public void sign(InternalRequest request, BceCredentials credentials, SignOptions options) {
            checkNotNull(request, "request should not be null.");
    
            if (credentials == null) {
                return;
            }
    
            if (options == null) {
                if (request.getSignOptions() != null) {
                    options = request.getSignOptions();
                } else {
                    options = SignOptions.DEFAULT;
                }
            }
    
            String accessKeyId = credentials.getAccessKeyId();
            String secretAccessKey = credentials.getSecretKey();
    
            request.addHeader(Headers.HOST, HttpUtils.generateHostHeader(request.getUri()));
    
            Date timestamp = options.getTimestamp();
            if (timestamp == null) {
                timestamp = new Date();
            }
    
            String authString =
                    BceV1Signer.BCE_AUTH_VERSION + "/" + accessKeyId + "/"
                            + DateUtils.formatAlternateIso8601Date(timestamp) + "/" + options.getExpirationInSeconds();
    
            String signingKey = this.sha256Hex(secretAccessKey, authString);
            // Formatting the URL with signing protocol.
            String canonicalURI = this.getCanonicalURIPath(request.getUri().getPath());
            // Formatting the query string with signing protocol.
            String canonicalQueryString = HttpUtils.getCanonicalQueryString(request.getParameters(), true);
            // Sorted the headers should be signed from the request.
            SortedMap<String, String> headersToSign =
                    this.getHeadersToSign(request.getHeaders(), options.getHeadersToSign());
            // Formatting the headers from the request based on signing protocol.
            String canonicalHeader = this.getCanonicalHeaders(headersToSign);
            String signedHeaders = "";
            if (options.getHeadersToSign() != null) {
                signedHeaders = BceV1Signer.signedHeaderStringJoiner.join(headersToSign.keySet());
                signedHeaders = signedHeaders.trim().toLowerCase();
            }
    
            String canonicalRequest =
                    request.getHttpMethod() + "\n" + canonicalURI + "\n" + canonicalQueryString + "\n" + canonicalHeader;
    
            // Signing the canonical request using key with sha-256 algorithm.
            String signature = this.sha256Hex(signingKey, canonicalRequest);
    
            String authorizationHeader = authString + "/" + signedHeaders + "/" + signature;
    
            logger.debug("CanonicalRequest:{}\tAuthorization:{}", canonicalRequest.replace("\n", "[\\n]"),
                    authorizationHeader);
    
            request.addHeader(Headers.AUTHORIZATION, authorizationHeader);
        }
    
        private String getCanonicalURIPath(String path) {
            if (path == null) {
                return "/";
            } else if (path.startsWith("/")) {
                return HttpUtils.normalizePath(path);
            } else {
                return "/" + HttpUtils.normalizePath(path);
            }
        }
    
        private String getCanonicalHeaders(SortedMap<String, String> headers) {
            if (headers.isEmpty()) {
                return "";
            }
    
            List<String> headerStrings = Lists.newArrayList();
            for (Map.Entry<String, String> entry : headers.entrySet()) {
                String key = entry.getKey();
                if (key == null) {
                    continue;
                }
                String value = entry.getValue();
                if (value == null) {
                    value = "";
                }
                headerStrings.add(HttpUtils.normalize(key.trim().toLowerCase()) + ':' + HttpUtils.normalize(value.trim()));
            }
            Collections.sort(headerStrings);
    
            return headerJoiner.join(headerStrings);
        }
    
        private SortedMap<String, String> getHeadersToSign(Map<String, String> headers, Set<String> headersToSign) {
            SortedMap<String, String> ret = Maps.newTreeMap();
            if (headersToSign != null) {
                Set<String> tempSet = Sets.newHashSet();
                for (String header : headersToSign) {
                    tempSet.add(header.trim().toLowerCase());
                }
                headersToSign = tempSet;
            }
            for (Map.Entry<String, String> entry : headers.entrySet()) {
                String key = entry.getKey();
                if (entry.getValue() != null && !entry.getValue().isEmpty()) {
                    if ((headersToSign == null && this.isDefaultHeaderToSign(key))
                            || (headersToSign != null && headersToSign.contains(key.toLowerCase())
                                    && !Headers.AUTHORIZATION.equalsIgnoreCase(key))) {
                        ret.put(key, entry.getValue());
                    }
                }
            }
            return ret;
        }
    
        private boolean isDefaultHeaderToSign(String header) {
            header = header.trim().toLowerCase();
            return header.startsWith(Headers.BCE_PREFIX) || defaultHeadersToSign.contains(header);
        }
    
        private String sha256Hex(String signingKey, String stringToSign) {
            try {
                Mac mac = Mac.getInstance("HmacSHA256");
                mac.init(new SecretKeySpec(signingKey.getBytes(UTF8), "HmacSHA256"));
                return new String(Hex.encodeHex(mac.doFinal(stringToSign.getBytes(UTF8))));
            } catch (Exception e) {
                throw new BceClientException("Fail to generate the signature", e);
            }
        }
    
    }

    Javascript示例

    用户可参考以下代码,进一步了解Javascript语言的百度智能云API认证机制。

    代码下载路径:https://github.com/baidubce/bce-sdk-js/blob/master/test/sdk/auth.spec.js

    var Auth = require('@baiducloud/sdk').Auth;
    
    
        var auth = new Auth('my_ak', 'my_sk');
    
        var method = 'PUT';
        var uri = '/v1/bucket/object1';
        var params = {
            A: null,
            b: '',
            C: 'd'
        };
        var headers = {
            'Host': 'bce.baidu.com',
            'abc': '123',
            'x-bce-meta-key1': 'ABC'
        };
    
        var signature = auth.generateAuthorization(method, uri, params, headers, 1402639056);
        expect(signature).to.eql('bce-auth-v1/my_ak/2014-06-13T05:57:36Z/1800/host;x-bce-meta-key1/'
                                 + '80c9672aca2ea9af4bb40b9a8ff458d72df94e97d550840727f3a929af271d25');
    
        signature = auth.generateAuthorization(method, uri, params, headers, 1402639056, 1800);
        expect(signature).to.eql('bce-auth-v1/my_ak/2014-06-13T05:57:36Z/1800/host;'
                                  + 'x-bce-meta-key1/80c9672aca2ea9af4bb40b9a8ff458d72'
                                  + 'df94e97d550840727f3a929af271d25');
    
        method = 'DELETE';
        uri = '/v1/test-bucket1361199862';
        params = {};
        headers = {
            'Content-Type': 'application/json; charset=utf-8',
            'Content-Length': 0,
            'User-Agent': 'This is the user-agent'
        };
        signature = auth.generateAuthorization(method, uri, params, headers, 1402639056, 1800);
        expect(signature).to.eql('bce-auth-v1/my_ak/2014-06-13T05:57:36Z/1800/'
                                  + 'content-length;content-type/'
                                  + 'c9386b15d585960ae5e6972f73ed92a9a682dc81025480ba5b41206d3e489822');

    C#示例

    用户可参考以下代码,进一步了解C#语言的百度智能云API认证机制。完整示例代码:

    using BaiduBce;
    using BaiduBce.Auth;
    using BaiduBce.Services.Bos;
    using BaiduBce.Services.Bos.Model;
    using BaiduBce.Services.Sts;
    using System;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.Diagnostics;
    using System.Globalization;
    using System.IO;
    using System.Net;
    using System.Security.Cryptography;
    using System.Text;
    using System.Web;
    
    namespace BOSTest
    {
        class Program
        {
            static string UriEncode(string input, bool encodeSlash = false)
            {
                StringBuilder builder = new StringBuilder();
                foreach (byte b in Encoding.UTF8.GetBytes(input))
                {
                    if ((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || (b >= '0' && b <= '9') || b == '_' || b == '-' || b == '~' || b == '.')
                    {
                        builder.Append((char)b);
                    }
                    else if (b == '/')
                    {
                        if (encodeSlash)
                        {
                            builder.Append("%2F");
                        }
                        else
                        {
                            builder.Append((char)b);
                        }
                    }
                    else
                    {
                        builder.Append('%').Append(b.ToString("X2"));
                    }
                }
                return builder.ToString();
            }
    
            static string Hex(byte[] data)
            {
                var sb = new StringBuilder();
                foreach (var b in data)
                {
                    sb.Append(b.ToString("x2"));
                }
                return sb.ToString();
            }
    
            static string CanonicalRequest(HttpWebRequest req)
            {
                Uri uri = req.RequestUri;
                StringBuilder canonicalReq = new StringBuilder();
                canonicalReq.Append(req.Method).Append("\n").Append(UriEncode(Uri.UnescapeDataString(uri.AbsolutePath))).Append("\n");
    
                var parameters = HttpUtility.ParseQueryString(uri.Query);
                List<string> parameterStrings = new List<string>();
                foreach (KeyValuePair<string, string> entry in parameters)
                {
                    parameterStrings.Add(UriEncode(entry.Key) + '=' + UriEncode(entry.Value));
                }
                parameterStrings.Sort();
                canonicalReq.Append(string.Join("&", parameterStrings.ToArray())).Append("\n");
    
                string host = uri.Host;
                if (!(uri.Scheme == "https" && uri.Port == 443) && !(uri.Scheme == "http" && uri.Port == 80))
                {
                    host += ":" + uri.Port;
                }
                canonicalReq.Append("host:" + UriEncode(host));
                return canonicalReq.ToString();
            }
    
            static void Main(string[] args)
            {
                string bucket = "mybucket";
                string key = "我的文件";
                string ak = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
                string sk = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
                DateTime now = DateTime.Now;
                int expirationInSeconds = 1200;
    
                HttpWebRequest req = WebRequest.Create("http://bj.bcebos.com/" + bucket + "/" + key) as HttpWebRequest;
                Uri uri = req.RequestUri;
                req.Method = "GET";
    
                string signDate = now.ToUniversalTime().ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ssK");
                Console.WriteLine(signDate);
                string authString = "bce-auth-v1/" + ak + "/" + signDate + "/" + expirationInSeconds;
                string signingKey = Hex(new HMACSHA256(Encoding.UTF8.GetBytes(sk)).ComputeHash(Encoding.UTF8.GetBytes(authString)));
                Console.WriteLine(signingKey);
    
                string canonicalRequestString = CanonicalRequest(req);
                Console.WriteLine(canonicalRequestString);
    
                string signature = Hex(new HMACSHA256(Encoding.UTF8.GetBytes(signingKey)).ComputeHash(Encoding.UTF8.GetBytes(canonicalRequestString)));
                string authorization = authString + "/host/" + signature;
                Console.WriteLine(authorization);
    
                req.Headers.Add("x-bce-date", signDate);
                req.Headers.Add(HttpRequestHeader.Authorization, authorization);
    
                HttpWebResponse res;
                string message = "";
                try
                {
                    res = req.GetResponse() as HttpWebResponse;
                }
                catch (WebException e)
                {
                    res = e.Response as HttpWebResponse;
                    message = new StreamReader(res.GetResponseStream()).ReadToEnd();
                }
                Console.WriteLine((int)res.StatusCode);
                Console.WriteLine(res.Headers);
                Console.WriteLine(message);
                Console.ReadLine();
            }
        }
    }

    iOS示例

    用户可参考以下代码,进一步了解iOS的百度智能云API认证机制。代码包含类代码和调用代码两部分,完整代码地址:代码。调用代码请参考:

    #import <XCTest/XCTest.h>
    #import "BDCloudSigner.h"
    
    @interface UT : XCTestCase
    @end
    
    @implementation UT
    
    id<BDCloudSigner> createSigner() {
        BDCloudCredentials* credentials = [BDCloudCredentials new];
        credentials.accessKey = @"<access key>";
        credentials.secretKey = @"<secret key>";
    
        id<BDCloudSigner> signer = [[BDCloudAKSKSigner alloc] initWithCredentials:credentials];
        signer.expiredTimeInSeconds = 3600;
    
        return signer;
    }
    
    NSMutableURLRequest* createRequest() {
        // create url directly, or use NSURLComponents.
        NSURL* url = [NSURL URLWithString:@"http://bj.bcebos.com/v1/bucket/object?append"];
    
        // create request.
        NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:url];
        request.HTTPMethod = @"POST";
        [request setValue:@"<length>" forHTTPHeaderField:@"Content-Length"];
        [request setValue:@"<md5>" forHTTPHeaderField:@"Content-MD5"];
        [request setValue:@"text/plain" forHTTPHeaderField:@"Content-Type"];
    
        // custom metadata key should begin with lower case prefix 'x-bce-'.
        [request setValue:@"2017-01-08T21:42:30Z" forHTTPHeaderField:@"x-bce-user-metadata-createtime"];
    
        // Host will be set when call sign.
        //[request setValue:@"bj.bcebos.com" forHTTPHeaderField:@"Host"];
    
        return request;
    }
    
    void sign() {
        id<BDCloudSigner> signer = createSigner();
        NSMutableURLRequest* request = createRequest();
        if (![signer sign:request]) {
            return;
        }
    
        // url
        NSURL* fileURL = [NSURL fileURLWithPath:@"<file path>"];
    
        // send request
        // sample purpose, don't care task will running correctly.
        [[NSURLSession sharedSession] uploadTaskWithRequest:request
                                                   fromFile:fileURL];
    }
    
    - (void)testAKSKSigner {
        sign();
    }
    
    @end

    C++示例

    用户可参考以下代码,进一步了解C++的百度智能云API认证机制。完整代码可下载百度智能云C++ sdk参考,下载地址:C++ SDK

    std::string DefaultSigner::generate_auth(HttpRequest *request, int expire_seconds) {
        std::stringstream auth_str;
        std::string sign_time = DateUtil::format_iso8601_date(time(NULL));
        auth_str << "bce-auth-v1/" << _credential.ak() << "/" << sign_time << "/" << expire_seconds;
        std::string sign_key = StringUtil::hmac_sha256_hex(_credential.sk(), auth_str.str());
    
        std::ostringstream canonical_req;
        // method
        canonical_req << method_str(request->method()) << '\n';
        // uri
        canonical_req << StringUtil::url_encode(request->uri(), false) << '\n';
        // query string
        const StringMap &params = request->parameters();
        if (params.size() > 0) {
            StringMap tmp;
            for (StringMap::const_iterator it = params.begin(); it != params.end(); ++it) {
                std::ostringstream p;
                p << StringUtil::url_encode(StringUtil::trim(it->first)) << '='
                    << StringUtil::url_encode(StringUtil::trim(it->second));
                tmp[p.str()] = "";
            }
            StringMap::iterator it = tmp.begin();
            canonical_req << it->first;
            for (++it; it != tmp.end(); ++it) {
                canonical_req << '&' << it->first;
            }
        }
        canonical_req << '\n';
    
        bool use_sts = !_credential.sts_token().empty();
        // header
        canonical_req << "host:" << StringUtil::url_encode(request->host());
        if (use_sts) {
            canonical_req << "\nx-bce-security-token:" << StringUtil::url_encode(_credential.sts_token());
        }
        LOG(DEBUG) << "canonical request: [" << canonical_req.str() << ']';
    
        std::string signature = StringUtil::hmac_sha256_hex(sign_key, canonical_req.str());
        auth_str << "/host";
        if (use_sts) {
            auth_str << ";x-bce-security-token";
            request->append_header("x-bce-security-token", _credential.sts_token());
        }
        auth_str << "/" << signature;
        return auth_str.str();
    }
    
    void DefaultSigner::sign(HttpRequest *request) {
        request->append_header("Authorization", generate_auth(request, _sign_expire_seconds));
    }
    上一篇
    在URL中包含认证字符串
    下一篇
    常见签名认证错误排查