Sample-Code
Python Example
Assume that the user uploads the last part of a file to the BOS cluster in Beijing with the UploadPart interface, whose content is Example
.
- Bucket name: test
- Object key: myfolder/readme.txt
- uploadId: a44cc9bab11cbd156984767aad637851
- partNumber: 9
- Access Key ID: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
- Secret Access Key: bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
- Time: Beijing time at 16:23:49 on April 27, 2015 (translated to UTC time: 8:23:49 on April 27, 2015)
The HTTP request is as follows:
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
Users can fill in the fields in the following functions according to the above HTTP request.
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
Please click here for the full code
The complete code is as follows:
# -*- coding: UTF-8 -*-
import hashlib
import hmac
import string
import datetime
AUTHORIZATION = "authorization"
BCE_PREFIX = "x-bce-"
DEFAULT_ENCODING = 'UTF-8'
# Save AK/SK classes
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
# According to RFC 3986, except:
# 1. Uppercase and lowercase English characters
# 2. Arabic numerals
# 3. Dot '.', tilde '~', minus sign '-', and underscore '_'
# The others need to be encoded
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)]
# Normalize String
def normalize_string(in_str, encoding_slash=True):
if in_str is None:
return ''
# If the input is unicode, it shall be encoded by UTF8 first and then encoded
in_str = in_str.encode(DEFAULT_ENCODING) if isinstance(in_str, unicode) else str(in_str)
# When generating a canonical URI. No need to encode the slash '/', otherwise
if encoding_slash:
encode_f = lambda c: NORMALIZED_CHAR_LIST[ord(c)]
else:
# Only when generating canonical URIs. No need to encode the slash '/'
encode_f = lambda c: NORMALIZED_CHAR_LIST[ord(c)] if c != '/' else c
# Encoding according to RFC 3986
return ''.join([encode_f(ch) for ch in in_str])
# Generate canonical timestamp
def get_canonical_time(timestamp=0):
# Return the current time when called without any parameters
if timestamp == 0:
utctime = datetime.datetime.utcnow()
else:
utctime = datetime.datetime.utcfromtimestamp(timestamp)
# Timestamp format: [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)
# Generate normalized URI
def get_canonical_uri(path):
# The format of a normalized URI is: / {bucket}/{object}, and encode all characters except the slash "/"
return normalize_string(path, False)
# Generate normalized query string
def get_canonical_querystring(params):
if params is None:
return ''
# In addition to authorization, all query strings are added to the encoding
result = ['%s=%s' % (normalize_string(k), normalize_string(v)) for k, v in params.items() if k.lower != AUTHORIZATION]
# Sort in lexicographic order
result.sort ()
# Concatenate all strings with&symbol and return
return '&'.join(result)
# Generate normalized header
def get_canonical_headers(headers, headers_to_sign=None):
headers = headers or {}
# If header_to_sign is not specified, use by default:
# 1.host
# 2.content-md5
# 3.content-length
# 4.content-type
# 5. All header items that start with x-bce-
# Generate normalized header
if headers_to_sign is None or len(headers_to_sign) == 0:
headers_to_sign = {"host", "content-md5", "content-length", "content-type"}
# For the key in the header, it needs to be converted to lowercase after removing the white space before and after
# For the value in the header, remove the white space before and after the conversion to str
f = lambda (key, value): (key.strip().lower(), str(value).strip())
result = []
for k, v in map(f, headers.iteritems()):
# In any case, header items starting with x-bce- need to be added to the canonical header
if k.startswith(BCE_PREFIX) or k in headers_to_sign:
result.append("%s:%s" % (normalize_string(k), normalize_string(v)))
# Sort in lexicographic order
result.sort ()
# Concatenate all strings with\n symbol and return
return '\n'.join(result)
# Signature Master Algorithm
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. Generate sign key
# 1.1. Generate auth-string in the format: 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. Use auth-string plus SK and SHA-256 to generate sign key
sign_key = hmac.new(
credentials.secret_access_key,
sign_key_info,
hashlib.sha256).hexdigest()
# 2. Generate normalized uri
canonical_uri = get_canonical_uri(path)
# 3. Generate a normalized query string
canonical_querystring = get_canonical_querystring(params)
# 4. Generate a normalized header
canonical_headers = get_canonical_headers(headers, headers_to_sign)
# 5. Use '\ n' to connect the HTTP METHOD with the results in 2, 3, 4 into a large string
string_to_sign = '\n'.join(
[http_method, canonical_uri, canonical_querystring, canonical_headers])
# 6. Use the signature string generated in 5 and the sign key generated in 1 to generate a signature result using the SHA-256 algorithm
sign_result = hmac.new(sign_key, string_to_sign, hashlib.sha256).hexdigest()
# 7. Stitch the final signature result string
if headers_to_sign:
# Specify header to sign
result = '%s/%s/%s' % (sign_key_info, ';'.join(headers_to_sign), sign_result)
else:
# Default signature result string without specifying 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 Example
Assume that the user uploads the last part of a file to the BOS cluster in Beijing with the UploadPart interface, whose content is Example
.
- Bucket name: test
- Object key: myfolder/readme.txt
- uploadId: a44cc9bab11cbd156984767aad637851
- partNumber: 9
- Access Key ID: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
- Secret Access Key: bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
- Time: Beijing time at 16:23:49 on April 27, 2015 (translated to UTC time: 8:23:49 on April 27, 2015)
The HTTP request is as follows:
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
Signing sample code
$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;
Please click here for the full code
The complete code is as follows:
<?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 privileges 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
{
// According to RFC 3986, except:
// 1. Uppercase and lowercase English characters
// 2. Arabic numerals
// 3. Dot '.', tilde '~', minus sign '-', and underscore '_'
// The others need to be encoded
public static $PERCENT_ENCODED_STRINGS;
// Fill the encoded array
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 does not encode
foreach (range('a', 'z') as $ch) {
HttpUtil::$PERCENT_ENCODED_STRINGS[ord($ch)] = $ch;
}
// A-Z does not encode
foreach (range('A', 'Z') as $ch) {
HttpUtil::$PERCENT_ENCODED_STRINGS[ord($ch)] = $ch;
}
// 0-9 does not encode
foreach (range('0', '9') as $ch) {
HttpUtil::$PERCENT_ENCODED_STRINGS[ord($ch)] = $ch;
}
// The following 4 characters are not encoded
HttpUtil::$PERCENT_ENCODED_STRINGS[ord('-')] = '-';
HttpUtil::$PERCENT_ENCODED_STRINGS[ord('.')] = '.';
HttpUtil::$PERCENT_ENCODED_STRINGS[ord('_')] = '_';
HttpUtil::$PERCENT_ENCODED_STRINGS[ord('~')] = '~';
}
// Can't encode '/' in uri encoding
public static function urlEncodeExceptSlash($path)
{
return str_replace("%2F", "/", HttpUtil::urlEncode($path));
}
// use encoding array encoding
public static function urlEncode($value)
{
$result = '';
for ($i = 0; $i < strlen($value); ++$i) {
$result .= HttpUtil::$PERCENT_ENCODED_STRINGS[ord($value[$i])];
}
return $result;
}
// Generate standardized QueryString
public static function getCanonicalQueryString(array $parameters)
{
// No parameters, return empty string directly
if (count($parameters) == 0) {
return '';
}
$parameterStrings = array();
foreach ($parameters as $k => $v) {
// Skip the Authorization field
if (strcasecmp('Authorization', $k) == 0) {
continue;
}
if (!isset($k)) {
throw new \InvalidArgumentException(
"parameter key should not be null"
);
}
if (isset($v)) {
// For the value, put it on both sides of = after encoding
$parameterStrings[] = HttpUtil::urlEncode($k)
. '=' . HttpUtil::urlEncode((string) $v);
} else {
// For those without a value, just encode the key and place it to the left of =, leaving the right blank
$parameterStrings[] = HttpUtil::urlEncode($k) . '=';
}
}
// Sort in lexicographic order
sort($parameterStrings);
// connect them using the '&' symbol
return implode('&', $parameterStrings);
}
// Generate standardized uri
public static function getCanonicalURIPath($path)
{
// The empty path is set to '/'
if (empty($path)) {
return '/';
} else {
// All uri must start with '/'
if ($path[0] == '/') {
return HttpUtil::urlEncodeExceptSlash($path);
} else {
return '/' . HttpUtil::urlEncodeExceptSlash($path);
}
}
}
// Generate a standardized http request header string
public static function getCanonicalHeaders($headers)
{
// If no headers, return empty string
if (count($headers) == 0) {
return '';
}
$headerStrings = array();
foreach ($headers as $k => $v) {
// Skip the null key
if ($k === null) {
continue;
}
// If value is null, the assignment is an empty string
if ($v === null) {
$v = '';
}
// encode after trim, then connect with ':'
$headerStrings[] = HttpUtil::urlEncode(strtolower(trim($k))) . ':' . HttpUtil::urlEncode(trim($v));
}
// Sort in lexicographic order
sort($headerStrings);
// connect them with '\ n'
return implode("\n", $headerStrings);
}
}
HttpUtil::__init();
class SampleSigner
{
const BCE_AUTH_VERSION = "bce-auth-v1";
const BCE_PREFIX = 'x-bce-';
// If headersToSign is not specified, the default http header is signed, including:
// 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",
);
}
// Signature function
public function sign(
array $credentials,
$httpMethod,
$path,
$headers,
$params,
$options = array()
) {
// Set signature validity time
if (!isset($options[SignOption::EXPIRATION_IN_SECONDS])) {
// The default value is 1800 seconds
$expirationInSeconds = SignOption::DEFAULT_EXPIRATION_IN_SECONDS;
} else {
$expirationInSeconds = $options[SignOption::EXPIRATION_IN_SECONDS];
}
// Parse ak sk
$accessKeyId = $credentials['ak'];
$secretAccessKey = $credentials['sk'];
// Set the timestamp, Note: If you specify the timestamp yourself, it needs to be UTC time
if (!isset($options[SignOption::TIMESTAMP])) {
// default value current time
$timestamp = new \DateTime();
} else {
$timestamp = $options[SignOption::TIMESTAMP];
}
$timestamp->setTimezone(new \DateTimeZone("UTC"));
// Generate authString
$authString = SampleSigner::BCE_AUTH_VERSION . '/' . $accessKeyId . '/'
. $timestamp->format("Y-m-d\TH:i:s\Z") . '/' . $expirationInSeconds;
// Generate signKey using sk and authString
$signingKey = hash_hmac('sha256', $authString, $secretAccessKey);
// Generate a standardized URI
$canonicalURI = HttpUtil::getCanonicalURIPath($path);
// Generate standardized QueryString
$canonicalQueryString = HttpUtil::getCanonicalQueryString($params);
// fill headersToSign, which indicates which headers participate in the signature
$headersToSignOption = null;
if (isset($options[SignOption::HEADERS_TO_SIGN])) {
$headersToSignOption = $options[SignOption::HEADERS_TO_SIGN];
}
$headersToSign = SampleSigner::getHeadersToSign($headers, $headersToSignOption);
// Generate standardized header
$canonicalHeader = HttpUtil::getCanonicalHeaders($headersToSign);
$headersToSign = array_keys($headersToSign);
sort($headersToSign);
// Fix headersToSign and connect with ';'
$signedHeaders = '';
if ($headersToSignOption !== null) {
$signedHeaders = strtolower(
trim(implode(";", $headersToSign))
);
}
// make up a standard request string
$canonicalRequest = "$httpMethod\n$canonicalURI\n"
. "$canonicalQueryString\n$canonicalHeader";
// Use signKey and standard request string to complete the signature
$signature = hash_hmac('sha256', $canonicalRequest, $signingKey);
// make up the final signature string
$authorizationHeader = "$authString/$signedHeaders/$signature";
return $authorizationHeader;
}
/** Filter headers that should participate in signature based on headsToSign
*
* @param $headers array
* @param $headersToSign array
* @return array
*/
public static function getHeadersToSign($headers, $headersToSign)
{
$ret = array();
if ($headersToSign !== null) {
$tmp = array();
// Handle the keys of the headers: Remove whitespace before and after and convert to lowercase
foreach ($headersToSign as $header) {
$tmp[] = strtolower(trim($header));
}
$headersToSign = $tmp;
}
foreach ($headers as $k => $v) {
if (trim((string) $v) !== '') {
if ($headersToSign !== null) {
// Preprocessing headersToSign: Remove whitespace before and after and convert to lowercase
if (in_array(strtolower(trim($k)), $headersToSign)) {
$ret[$k] = $v;
}
} else {
// If there is no headersToSign, the headers are selected according to the default rules
if (SampleSigner::isDefaultHeaderToSign($k, $headersToSign)) {
$ret[$k] = $v;
}
}
}
}
return $ret;
}
/**
* Check if the header is signed by default:
* 1. it is one of host, content-type, content-md5, content-length
* 2. Start with 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();
// Sign the sample code
$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 Example
Users can refer to the following code to learn more about Baidu AI Cloud API authentication mechanism.
Code download path: https://github.com/baidubce/bce-sdk-java/blob/master/src/main/java/com/baidubce/auth/BceV1Signer.java
Note: The API authentication sample code of the Android language is consistent with the Java sample code.
/*
* 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 privileges 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 Example
Users can refer to the following code to learn more about Baidu AI Cloud API authentication mechanism in Javascript language.
Code download path: 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 # Example
Users can refer to the following code to learn more about Baidu AI Cloud API authentication mechanism in C # language. Complete example code:
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 = "myfile";
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 Example
Users can refer to the following code to learn more about Baidu AI Cloud API authentication mechanism for iOS. The code contains two types of class code and calling code, the complete code address: Code. For the calling code, please see:
#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