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