请求示例
Authorization
为了安全,华为云的 Api 调用都是需要在请求的 Header 中携带 Authorization 鉴权的,这个鉴权15分钟内有效,超过15分钟就不能用了,而且是需要调用方自己手动拼接的。
Authorization的格式为
OBS 用户AK:手动生成的Signature
Signature生成
Signature生成流程:
参考文档:Header中携带签名_对象存储服务 OBS
Signature生成代码
package com.fdw.algorithm.HWCloud;
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;
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", "mirrorBackToSource", "modify", "name", "notification", "obscompresspolicy", "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", "object-lock", "retention"));
private String ak;
private String sk;
// 对字符串进行UTF8编码
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);
if (i < items.size() - 1) {
sb.append(delimiter);
}
}
return sb.toString();
}
private boolean isValid(String input) {
return input != null && !input.equals("");
}
// 使用访问密钥SK计算HmacSHA1值
public String hmacSha1(String input) throws NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException {
SecretKeySpec signingKey = new SecretKeySpec(this.sk.getBytes(DEFAULT_ENCODING), "HmacSHA1");
// 获取Mac实例,并通过getInstance方法指定使用HMAC-SHA1算法
Mac mac = Mac.getInstance("HmacSHA1");
// 使用访问密钥SK初始化Mac对象
mac.init(signingKey);
return Base64.getEncoder().encodeToString(mac.doFinal(input.getBytes(DEFAULT_ENCODING)));
}
// 构造StringToSign
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();
}
}
// 如果header头域中包含x-obs-date,Date参数置空
if(canonicalizedHeaders.containsKey("x-obs-date")) {
date = "";
}
// 构造StringToSign,拼接HTTP-Verb、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);
// 构造StringToSign,拼接CanonicalizedHeaders
for(Map.Entry<String, String> entry : canonicalizedHeaders.entrySet()) {
stringToSign.append(entry.getKey()).append(":").append(entry.getValue()).append(SIGN_SEP);
}
// 构造StringToSign,拼接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 {
// 构造stringToSign
String stringToSign = this.stringToSign(httpMethod, headers, queries, bucketName, objectName);
System.out.println("stringToSign: "+stringToSign);
// 计算签名
return String.format("OBS %s:%s", this.ak, this.hmacSha1(stringToSign));
}
public static void main(String[] args) throws Exception {
SignDemo demo = new SignDemo();
/* 认证用的ak和sk硬编码到代码中或者明文存储都有很大的安全风险,建议在配置文件或者环境变量中密文存放,使用时解密,确保安全;
本示例以ak和sk保存在环境变量中为例,运行本示例前请先在本地环境中设置环境变量HUAWEICLOUD_SDK_AK和HUAWEICLOUD_SDK_SK。*/
//demo.ak = System.getenv("HUAWEICLOUD_SDK_AK");
//demo.sk = System.getenv("HUAWEICLOUD_SDK_SK");
//最好用上面的方法赋值,这里只是简化操作
demo.ak = "ZI6KNMNGWZUWMJV5WMKW";
demo.sk = "hIAb8jwgPEHTYknEMKql6DRqGZMLxRY66cHzd8D2";
String bucketName = "bucket-test";
String objectName = "hello.jpg";
Map<String, String[]> headers = new HashMap<String, String[]>();
headers.put("date", new String[] {"Wed, 14 Aug 2024 07:20:28 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);
//计算Header中携带的签名
System.out.println(demo.headerSignature("GET", headers, queries, null, null));
}
}
注意
代码中的 date 是手动填写的时间,必须为RFC 1123格式的GMT时间(和北京时间差了8个小时),与系统当前时间差不能超过15分钟。
与请求头中的时间也要保持一致,分秒不差!!!