3.2 认证鉴权
3.2.3 URL 中携带签名
OBS提供的SDK已集成了签名计算,建议您使用SDK进行开发。
以Header中携带签名为例,用户签名验证流程如表3-4所示。Header中携带签名方法 的具体参数说明及代码示例,请参见3.2.2 Header中携带签名。
表3-4 OBS 签名计算和验证步骤
步骤 示例
签名
计算 1. 构造HTTP消息 PUT /object HTTP/1.1 Host:
bucket.obs.cn-north-4.myhuaweicloud.com
Date: Tue, 04 Jun 2019 06:54:59 GMT Content-Type: text/plain
Content-Length: 5913 2. 按照签名规则计算
StringToSign StringToSign = HTTP-Verb + "\n" + Content-MD5 + "\n" + Content-Type + "\n" + Date +
"\n" + CanonicalizedHeaders + CanonicalizedResource
3. 准备AK和SK AK: ******
SK: ******
步骤 示例
4. 计算签名Signature Signature = Base64(
HMAC-SHA1( SecretAccessKeyID, UTF-8-Encoding-Of( StringToSign ) ) )
5. 添加签名头域发送到
OBS服务 PUT /object HTTP/1.1 Host:
bucket.obs.cn-north-4.myhuaweicloud.com
Date: Tue, 04 Jun 2019 06:54:59 GMT Content-Type: text/plain
Content-Length: 5913
Authorization: OBS AccessKeyID:Signature 签名
验证
6. 接收HTTP消息 PUT /object HTTP/1.1 Host:
bucket.obs.cn-north-4.myhuaweicloud.com
Date: Tue, 04 Jun 2019 06:54:59 GMT Content-Type: text/plain
Content-Length: 5913
Authorization: OBS AccessKeyID:Signature 7. 根据请求中的AK获取
SK 从头域Authorization中取出AK,去IAM取回用户 的SK
8. 按照签名规则计算
StringToSign StringToSign = HTTP-Verb + "\n" + Content-MD5 + "\n" + Content-Type + "\n" + Date +
"\n" + CanonicalizedHeaders + CanonicalizedResource
9. 计算签名Signature Signature = Base64(
HMAC-SHA1( SecretAccessKeyID, UTF-8-Encoding-Of( StringToSign ) ) )
10. 验证签名 验证头域Authorization中的Signature与服务端计 算的Signature是否相等
相等:签名验证通过 不相等:签名验证失败
3.2.2 Header 中携带签名
OBS的所有API接口都可以通过在header中携带签名方式来进行身份认证,也是最常用 的身份认证方式。
在Header中携带签名是指将通过HTTP消息中Authorization header头域携带签名信 息,消息头域的格式为:
Authorization: OBS AccessKeyID:signature
签名的计算过程如下:
1、构造请求字符串(StringToSign)。
2、对第一步的结果进行UTF-8编码。
3、使用SK对第二步的结果进行HMAC-SHA1签名计算。
4、对第三步的结果进行Base64编码,得到签名。
请求字符串(StringToSign)按照如下规则进行构造,各个参数的含义如表3-5所示。
StringToSign = HTTP-Verb + "\n" + Content-MD5 + "\n" + Content-Type + "\n" + Date + "\n" +
CanonicalizedHeaders + CanonicalizedResource
表3-5 构造 StringToSign 所需参数说明
参数 描述
HTTP-Verb 指接口操作的方法,对REST接口而言,即为http请求操作的VERB,
如:"PUT","GET","DELETE"等字符串。
Content-MD5 按照RFC 1864标准计算出消息体的MD5摘要字符串,即消息体128-bit MD5值经过base64编码后得到的字符串,可以为空。具体请参见 表3-10以及表下方的计算方法示例。
Content-Type 内容类型,用于指定消息类型,例如: text/plain。
当请求中不带该头域时,该参数按照空字符串处理,见表3-6。
Date 生成请求的时间,该时间格式遵循RFC 1123;该时间与当前服务器 的时间超过15分钟时服务端返回403。
当有自定义字段x-obs-date时,该参数按照空字符串处理;见表 3-10。
如果进行临时授权方式操作(如临时授权方式获取对象内容等操 作)时,该参数不需要。
参数 描述 Canonicalize
dHeaders HTTP请求头域中的OBS请求头字段,即以“x-obs-”作为前辍的头 域,如“x-obs-date,x-obs-acl,x-obs-meta-*”。
1. 请求头字段中关键字的所有字符要转为小写(但内容值需要区分 大小写,如“x-obs-storage-class:STANDARD”),需要添加多 个字段时,要将所有字段按照关键字的字典序从小到大进行排 序。
2. 在添加请求头字段时,如果有重名的字段,则需要进行合并。
如:x-obs-meta-name:name1和x-obs-meta-name:name2,则 需要先将重名字段的值(这里是name1和name2)以逗号分隔,
合并成x-obs-meta-name:name1,name2。
3. 头域中的请求头字段中的关键字不允许含有非ASCII码或不可识别 字符;请求头字段中的值也不建议使用非ASCII码或不可识别字 符,如果一定要使用非ASCII码或不可识别字符,需要客户端自行 做编解码处理,可以采用URL编码或者Base64编码,服务端不会 做解码处理。
4. 当请求头字段中含有无意义空格或table键时,需要摒弃。例如:
x-obs-meta-name: name(name前带有一个无意义空格),需 要转换为:x-obs-meta-name:name
5. 每一个请求头字段最后都需要另起新行,见表3-8
参数 描述 Canonicalize
dResource 表示HTTP请求所指定的OBS资源,构造方式如下:
<桶名+对象名>+[子资源1] + [子资源2] + ...
1. 桶名和对象名。
● 通过桶绑定的自定义域名访问OBS,桶名由自定义域名表示,
则为"/obs.ccc.com/object",其中“obs.ccc.com”为桶绑定的 自定义域名。如果没有对象名,如列举桶,则为"/
obs.ccc.com/";
● 不是通过桶绑定的自定义域名访问OBS的场景,则为"/bucket/
object",如果没有对象名,如列举桶,则为"/bucket/"。如果 桶名也没有,则为“/”。
2. 如果有子资源,则将子资源添加进来,例如?acl,?logging。
OBS支持各种子资源,包括:CDNNotifyConfiguration, acl, append, attname, backtosource, cors, customdomain, delete, deletebucket, directcoldaccess, encryption, inventory, length, lifecycle, location, logging, metadata, modify, name,
notification, partNumber, policy, position, quota, rename, replication, response-cache-control, disposition, encoding, response-content-language, response-content-type, response-expires, restore, storageClass, storagePolicy, storageinfo, tagging, torrent, truncate, uploadId, uploads, versionId, versioning, versions, website, x-image-process, bucket, x-image-save-object, x-obs-security-token。
3. 如果有多个子资源,在包含这些子资源时,需要首先将这些子资 源按照其关键字的字典序从小到大排列,并使用“&”拼接。
说明
● 子资源通常是唯一的,不建议请求的URL包含多个相同关键字的子资源
(例如,key=value1&key=value2),如果存在这种情况,OBS服务端签 名时只会计算第一个子资源且也只有第一个子资源的值会对实际业务产生 作用;
● 以获取对象(GetObject)接口为例,假设桶名为bucket-test,对象名为 object-test,对象的版本号为xxx,获取时需要重写Content-Type为text/
plain,那么签名计算出的CanonicalizedResource为:/bucket-test/
object-test?response-content-type=text/plain&versionId=xxx。
下面的几张表提供了一些生成StringToSign的例子。
表3-6 获取对象
请求消息头 StringToSign
GET /object.txt HTTP/1.1 Host:
bucket.obs.cn-north-4.myhuaweicloud.com
Date: Sat, 12 Oct 2015 08:12:38 GMT
GET \n
\n
\n
Sat, 12 Oct 2015 08:12:38 GMT\n /bucket/object.txt
表3-7 使用临时 AK/SK 和 securitytoken 上传对象
请求消息头 StringToSign
PUT /object.txt HTTP/1.1 User-Agent: curl/7.15.5 Host:
bucket.obs.cn-north-4.myhuaweicloud.com
x-obs-date:Tue, 15 Oct 2015 07:20:09 GMT
x-obs-security-token:
YwkaRTbdY8g7q....
content-type: text/plain Content-Length: 5913339
PUT\n
\n
text/plain\n
\n
x-obs-date:Tue, 15 Oct 2015 07:20:09 GMT\n
x-obs-security-token:YwkaRTbdY8g7q....\n /bucket/object.txt
表3-8 带请求头字段上传对象
请求消息头 StringToSign
PUT /object.txt HTTP/1.1 User-Agent: curl/7.15.5 Host:
bucket.obs.cn-north-4.myhuaweicloud.com
Date: Mon, 14 Oct 2015 12:08:34 GMT x-obs-acl: public-read
content-type: text/plain Content-Length: 5913339
PUT\n
\n
text/plain\n
Mon, 14 Oct 2015 12:08:34 GMT\n x-obs-acl:public-read\n
/bucket/object.txt
表3-9 获取对象 ACL
请求消息头 StringToSign
GET /object.txt?acl HTTP/1.1 Host:
bucket.obs.cn-north-4.myhuaweicloud.com
Date: Sat, 12 Oct 2015 08:12:38 GMT
GET \n
\n
\n
Sat, 12 Oct 2015 08:12:38 GMT\n /bucket/object.txt?acl
表3-10 上传对象且携带 Content-MD5 头域
请求消息头 StringToSign
PUT /object.txt HTTP/1.1 Host:
bucket.obs.cn-north-4.myhuaweicloud.com
x-obs-date:Tue, 15 Oct 2015 07:20:09 GMT
Content-MD5:
I5pU0r4+sgO9Emgl1KMQUg==
Content-Length: 5913339
PUT\n
I5pU0r4+sgO9Emgl1KMQUg==\n
\n
\n
x-obs-date:Tue, 15 Oct 2015 07:20:09 GMT\n
/bucket/object.txt
表3-11 使用自定义域名方式上传对象
请求消息头 StringToSign
PUT /object.txt HTTP/1.1 Host: obs.ccc.com
x-obs-date:Tue, 15 Oct 2015 07:20:09 GMT
Content-MD5:
I5pU0r4+sgO9Emgl1KMQUg==
Content-Length: 5913339
PUT\n
I5pU0r4+sgO9Emgl1KMQUg==\n
\n
\n
x-obs-date:Tue, 15 Oct 2015 07:20:09 GMT\n
/obs.ccc.com/object.txt
Java 中 Content-MD5 的计算方法示例
import java.security.MessageDigest;
import sun.misc.BASE64Encoder;
import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
public class Md5{
public static void main(String[] args) { try {
String exampleString = "blog";
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
BASE64Encoder encoder = new BASE64Encoder();
String contentMd5 = encoder.encode(messageDigest.digest(exampleString.getBytes("utf-8")));
System.out.println("Content-MD5:" + contentMd5);
} catch (NoSuchAlgorithmException | UnsupportedEncodingException e) { HMAC算法(hash-based authentication code algorithm)。
Signature = Base64( HMAC-SHA1( YourSecretAccessKeyID, UTF-8-Encoding-Of( StringToSign ) ) )
例如在华北-北京四区域创建桶名为newbucketname2的私有桶,客户端请求格式为:
PUT / HTTP/1.1
Host: newbucketname2.obs.cn-north-4.myhuaweicloud.com Content-Length: length
Date: Fri, 06 Jul 2018 03:45:51 GMT x-obs-acl:private
x-obs-storage-class:STANDARD
Authorization: OBS UDSIAMSTUBTEST000254:ydH8ffpcbS6YpeOMcEZfn0wE90c=
<CreateBucketConfiguration xmlns="http://obs.cn-north-4.myhuaweicloud.com/doc/2015-06-30/">
<Location>cn-north-4</Location>
</CreateBucketConfiguration>
Java 中签名的计算方法
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.omg.CosNaming.IstringHelper;
public class SignDemo {
private static final String SIGN_SEP = "\n";
private static final String OBS_PREFIX = "x-obs-";
private static final String DEFAULT_ENCODING = "UTF-8";
private static final List<String> SUB_RESOURCES = Collections.unmodifiableList(Arrays.asList(
"CDNNotifyConfiguration", "acl", "append", "attname", "backtosource", "cors", "customdomain",
"delete",
"deletebucket", "directcoldaccess", "encryption", "inventory", "length", "lifecycle", "location",
"logging",
"metadata", "modify", "name", "notification", "orchestration", "partNumber", "policy", "position",
"quota",
"rename", "replication", "response-cache-control", "response-content-disposition",
content-encoding", content-language", content-type", "response-expires",
"restore", " storageClass", "storagePolicy", "storageinfo", "tagging", "torrent", "truncate", "uploadId", "uploads", "versionId", "versioning", "versions", "website", "x-image-process", "x-image-save-bucket", "x-image-save-object", "x-obs-security-token"));
private String ak;
private String sk;
public String urlEncode(String input) throws UnsupportedEncodingException {
return URLEncoder.encode(input, DEFAULT_ENCODING) .replaceAll("%7E", "~") //for browser
.replaceAll("%2F", "/") .replaceAll("%20", "+");
}
private String join(List<?> items, String delimiter) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < items.size(); i++) {
String item = items.get(i).toString();
sb.append(item);
public String hamcSha1(String input) throws NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException {
SecretKeySpec signingKey = new SecretKeySpec(this.sk.getBytes(DEFAULT_ENCODING), "HmacSHA1");
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(signingKey);
return Base64.getEncoder().encodeToString(mac.doFinal(input.getBytes(DEFAULT_ENCODING)));
}
private String stringToSign(String httpMethod, Map<String, String[]> headers, Map<String, String>
queries,
TreeMap<String, String> canonicalizedHeaders = new TreeMap<String, String>();
String key;
List<String> temp = new ArrayList<String>();
for(Map.Entry<String, String[]> entry : headers.entrySet()) { key = entry.getKey();
}
if(canonicalizedHeaders.containsKey("x-obs-date")) { date = "";
}
// handle method/content-md5/content-type/date StringBuilder stringToSign = new StringBuilder();
stringToSign.append(httpMethod).append(SIGN_SEP) .append(contentMd5).append(SIGN_SEP)
.append(contentType).append(SIGN_SEP) .append(date).append(SIGN_SEP);
// handle canonicalizedHeaders
for(Map.Entry<String, String> entry : canonicalizedHeaders.entrySet()) {
stringToSign.append(entry.getKey()).append(":").append(entry.getValue()).append(SIGN_SEP);
}
TreeMap<String, String> canonicalizedResource = new TreeMap<String, String>();
for(Map.Entry<String, String> entry : queries.entrySet()) { key = entry.getKey();
for(Map.Entry<String, String> entry : canonicalizedResource.entrySet()) { stringToSign.append(entry.getKey());
// System.out.println(String.format("StringToSign:%s%s", SIGN_SEP, stringToSign.toString()));
return stringToSign.toString();
}
public String headerSignature(String httpMethod, Map<String, String[]> headers, Map<String, String>
queries,
String bucketName, String objectName) throws Exception { //1. stringToSign
String stringToSign = this.stringToSign(httpMethod, headers, queries, bucketName, objectName);
//2. signature
return String.format("OBS %s:%s", this.ak, this.hamcSha1(stringToSign));
}
public String querySignature(String httpMethod, Map<String, String[]> headers, Map<String, String>
queries,
String bucketName, String objectName, long expires) throws Exception { if(headers.containsKey("x-obs-date")) {
headers.put("x-obs-date", new String[] {String.valueOf(expires)});
}else {
headers.put("date", new String[] {String.valueOf(expires)});
}
//1. stringToSign
String stringToSign = this.stringToSign(httpMethod, headers, queries, bucketName, objectName);
//2. signature
return this.urlEncode(this.hamcSha1(stringToSign));
}
Map<String, String[]> headers = new HashMap<String, String[]>();
headers.put("date", new String[] {"Sat, 12 Oct 2015 08:12:38 GMT"});
headers.put("x-obs-acl", new String[] {"public-read"});
headers.put("x-obs-meta-key1", new String[] {"value1"});
headers.put("x-obs-meta-key2", new String[] {"value2", "value3"});
Map<String, String> queries = new HashMap<String, String>();
queries.put("acl", null);
System.out.println(demo.headerSignature("PUT", headers, queries, bucketName, objectName));
} }
签名计算的样例结果为(按照执行时间的不同变化):
ydH8ffpcbS6YpeOMcEZfn0wE90c=
Python 中签名的计算方法
import sys import hashlib import hmac import binascii
from datetime import datetime
IS_PYTHON2 = sys.version_info.major == 2 or sys.version < '3'
yourSecretAccessKeyID = '275hSvB6EEOorBNsMDEfOaICQnilYaPZhXUaSK64' httpMethod = "PUT"
contentType = "application/xml"
# "date" is the time when the request was actually generated date = datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT') canonicalizedHeaders = "x-obs-acl:private"
CanonicalizedResource = "/newbucketname2"
canonical_string = httpMethod + "\n" + "\n" + contentType + "\n" + date + "\n" + canonicalizedHeaders + CanonicalizedResource
if IS_PYTHON2:
hashed = hmac.new(yourSecretAccessKeyID, canonical_string, hashlib.sha1) encode_canonical = binascii.b2a_base64(hashed.digest())[:-1]
else:
hashed = hmac.new(yourSecretAccessKeyID.encode('UTF-8'), canonical_string.encode('UTF-8'),hashlib.sha1)
encode_canonical = binascii.b2a_base64(hashed.digest())[:-1].decode('UTF-8') print encode_canonical
签名计算的样例结果为(按照执行时间的不同变化):
ydH8ffpcbS6YpeOMcEZfn0wE90c=
C 语言中签名的计算方法
请单击此处,下载C语言签名计算代码样例,其中:
1. 计算签名的接口包含在sign.h头文件中。
2. 计算签名的示例代码在main.c文件中。
签名不匹配报错处理
若调用OBS API报如下错误:
状态码:403 Forbidden
错误码:SignatureDoesNotMatch
错误信息:The request signature we calculated does not match the signature you provided. Check your key and signing method.
请参考以下案例进行排查处理:签名不匹配(SignatureDoesNotMatch)如何处理
3.2.3 URL 中携带签名
URL中携带签名:OBS服务支持用户构造一个特定操作的URL,这个URL中会包含用户 AK、签名、有效期、资源等信息,任何拿到这个URL的人均可执行这个操作,OBS服 务收到这个请求后认为该请求就是签发URL用户自己在执行操作。例如构造一个携带 签名信息的下载对象的URL,拿到相应URL的人能下载这个对象,但该URL只在Expires 指定的失效时间内有效。URL中携带签名主要用于在不提供给其他人Secret Access Key的情况下,让其他人能用预签发的URL来进行身份认证,并执行预定义的操作。
URL中携带签名请求的消息格式如下:
GET /ObjectKey?AccessKeyId=AccessKeyID&Expires=ExpiresValue&Signature=signature HTTP/1.1 Host: bucketname.obs.cn-north-4.myhuaweicloud.com
URL中使用临时AK,SK和securitytoken下载对象消息格式如下:
GET /ObjectKey?AccessKeyId=AccessKeyID&Expires=ExpiresValue&Signature=signature&x-obs-security-token=securitytoken HTTP/1.1
Host: bucketname.obs.cn-north-4.myhuaweicloud.com
参数具体意义如表3-12所示。
表3-12 请求消息参数
参数名称 描述 是否必选
AccessKeyId 签发者的AK信息。OBS根据AK确定签发者的身 份,并认为URL就是签发者在访问。
类型:字符串。
是
Expires 临时授权失效的时间;临时授权失效的时间为 24小时,但是如果含有了临时的AK,其授权失 效时间最大值也只能为24小时;UTC时间,
1970年1月1日零时之后的指定的Expires时间内
1970年1月1日零时之后的指定的Expires时间内