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
参数具体意义如表1所示。
参数名称 |
描述 |
是否必选 |
---|---|---|
AccessKeyId |
签发者的AK信息。OBS根据AK确定签发者的身份,并认为URL就是签发者在访问。 类型:字符串。 |
是 |
Expires |
临时授权失效的时间;临时授权失效的时间为24小时,但是如果含有了临时的AK,其授权失效时间最大值也只能为24小时;UTC时间,1970年1月1日零时之后的指定的Expires时间内有效(以秒为单位)。 类型:字符串。 |
是 |
Signature |
根据用户SK、Expires等参数计算出的签名信息。 类型:字符串。 |
是 |
x-obs-security-token |
使用临时AK/SK鉴权时,临时AK/SK和securitytoken必须同时使用,请求头中需要添加“x-obs-security-token”字段 |
否 |
签名的计算过程如下:
1、构造请求字符串(StringToSign)。
2、对第一步的结果进行UTF-8编码。
3、使用SK对第二步的结果进行HMAC-SHA1签名计算。
4、对第三步的结果进行Base64编码。
5、对第四步的结果进行URL编码,得到签名。
请求字符串(StringToSign)按照如下规则进行构造,各个参数的含义如表2所示:
StringToSign = HTTP-Verb + "\n" + Content-MD5 + "\n" + Content-Type + "\n" + Expires + "\n" + CanonicalizedHeaders + CanonicalizedResource;
根据请求字符串(StringToSign)和用户SK使用如下算法生成Signature,生成过程使用HMAC算法(hash-based authentication code algorithm)。
Signature = URL-Encode( Base64( HMAC-SHA1( YourSecretAccessKeyID, UTF-8-Encoding-Of( StringToSign ) ) ) )
URL中的Signature计算方法和Header中携带的Authorization签名计算方法有两处不同:
使用URL携带签名方式为浏览器生成预定义的URL实例:
请求消息头 |
StringToSign |
---|---|
GET /objectkey?AccessKeyId=MFyfvK41ba2giqM7Uio6PznpdUKGpownRZlmVmHc&Expires=1532779451&Signature=0Akylf43Bm3mD1bh2rM3dmVp1Bo%3D HTTP/1.1 Host: examplebucket.obs.cn-north-4.myhuaweicloud.com |
GET \n \n \n 1532779451\n /examplebucket/objectkey |
请求消息头 |
StringToSign |
---|---|
GET /objectkey?AccessKeyId=MFyfvK41ba2giqM7Uio6PznpdUKGpownRZlmVmHc&Expires=1532779451&Signature=0Akylf43Bm3mD1bh2rM3dmVp1Bo%3D&x-obs-security-token=YwkaRTbdY8g7q.... HTTP/1.1 Host: examplebucket.obs.cn-north-4.myhuaweicloud.com |
GET \n \n \n 1532779451\n /examplebucket/objectkey?x-obs-security-token:YwkaRTbdY8g7q.... |
根据签名计算规则
Signature = URL-Encode( Base64( HMAC-SHA1( YourSecretAccessKeyID, UTF-8-Encoding-Of( StringToSign ) ) ) )
计算出签名,然后将Host作为URL的前缀,可以生成预定义的URL:
http(s)://examplebucket.obs.cn-north-4.myhuaweicloud.com/objectkey?AccessKeyId=AccessKeyID&Expires=1532779451&Signature=0Akylf43Bm3mD1bh2rM3dmVp1Bo%3D
在浏览器中直接输入该地址则可以下载examplebucket桶中的objectkey对象。这个链接的有效期是1532779451(Sat Jul 28 20:04:11 CST 2018)。
在Linux环境上使用curl命令访问注意&字符需要\转义,如下命令将对象objectkey下载到output文件中:
curl http(s)://examplebucket.obs.cn-north-4.myhuaweicloud.com/objectkey?AccessKeyId=AccessKeyID\&Expires=1532779451\&Signature=0Akylf43Bm3mD1bh2rM3dmVp1Bo%3D -X GET -o output
如果想要在浏览器中使用URL中携带签名生成的预定义URL,则计算签名时不要使用只能携带在头域部分的“Content-MD5”、“Content-Type”、“CanonicalizedHeaders”来计算签名。否则浏览器不能携带这些参数,请求发送到服务端之后,会提示签名错误。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 | 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",
"response-content-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",
"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", "/");
}
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);
if (i < items.size() - 1)
{
sb.append(delimiter);
}
}
return sb.toString();
}
private boolean isValid(String input) {
return input != null && !input.equals("");
}
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,
String bucketName, String objectName) throws Exception{
String contentMd5 = "";
String contentType = "";
String date = "";
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(key == null || entry.getValue() == null || entry.getValue().length == 0) {
continue;
}
key = key.trim().toLowerCase(Locale.ENGLISH);
if(key.equals("content-md5")) {
contentMd5 = entry.getValue()[0];
continue;
}
if(key.equals("content-type")) {
contentType = entry.getValue()[0];
continue;
}
if(key.equals("date")) {
date = entry.getValue()[0];
continue;
}
if(key.startsWith(OBS_PREFIX)) {
for(String value : entry.getValue()) {
if(value != null) {
temp.add(value.trim());
}
}
canonicalizedHeaders.put(key, this.join(temp, ","));
temp.clear();
}
}
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);
}
// handle CanonicalizedResource
stringToSign.append("/");
if(this.isValid(bucketName)) {
stringToSign.append(bucketName).append("/");
if(this.isValid(objectName)) {
stringToSign.append(this.urlEncode(objectName));
}
}
TreeMap<String, String> canonicalizedResource = new TreeMap<String, String>();
for(Map.Entry<String, String> entry : queries.entrySet()) {
key = entry.getKey();
if(key == null) {
continue;
}
if(SUB_RESOURCES.contains(key)) {
canonicalizedResource.put(key, entry.getValue());
}
}
if(canonicalizedResource.size() > 0) {
stringToSign.append("?");
for(Map.Entry<String, String> entry : canonicalizedResource.entrySet()) {
stringToSign.append(entry.getKey());
if(this.isValid(entry.getValue())) {
stringToSign.append("=").append(entry.getValue());
}
stringToSign.append("&");
}
stringToSign.deleteCharAt(stringToSign.length()-1);
}
// 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));
}
public static void main(String[] args) throws Exception {
SignDemo demo = new SignDemo();
demo.ak = "<your-access-key-id>";
demo.sk = "<your-secret-key-id>";
String bucketName = "bucket-test";
String objectName = "hello.jpg";
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);
// 请求消息参数Expires,设置24小时后失效
long expires = System.currentTimeMillis() + 86400000L;
System.out.println(demo.querySignature("PUT", headers, queries, bucketName, objectName,expires));
}
}
|
OBS提供可视化签名工具,帮助您轻松完成签名计算。
案例背景 杭州市一医院是杭州市属最大综合性三级甲等医院,也是浙江省首批通过三...
什么是轻量应用服务器(Lighthouse)? 轻量应用服务器(Lighthouse)是新一代开...
开篇 Python作为一门快速发展的解释性编程语言,数以百万计的开发者已经将Python...
计费项 计费项分为包含服务和关联服务两类。 包含服务: 专属主机服务,用户需要...
近年来,大数据这个术语似乎比其他IT术语都更加流行。这不仅是术语的传播,而且...
许多公司使用多个公共云提供商提供的云计算服务,也就是所谓的多云,而也有一些...
昨天三月十四日就是3.14,3.14就是?π,π?就是Pi,Pi就是派,派就是馅饼。俗语...
Python 的 logging 模块实现了灵活的日志系统。整个模块仅仅 3 个类,不到 5000 ...
作者:张妙成 项目背景 PaaS 下管理了大量集群,监控和告警能快速的让开发维护人...
TOP云 (west.cn)2月27日,据外媒domaininvesting报道,图片分享社交平台Instag...