请求示例
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)));}// 构造StringToSignprivate 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、DateStringBuilder 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,拼接CanonicalizedHeadersfor(Map.Entry<String, String> entry : canonicalizedHeaders.entrySet()) {stringToSign.append(entry.getKey()).append(":").append(entry.getValue()).append(SIGN_SEP);}// 构造StringToSign,拼接CanonicalizedResourcestringToSign.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 {// 构造stringToSignString 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分钟。
与请求头中的时间也要保持一致,分秒不差!!!