使用V2签名时的HttpURLConnection开发 本节主要介绍使用V2签名时的HttpURLConnection开发。 应用场景 V2签名下,使用HttpURLConnection开发。 前提条件 已开通对象存储(经典版)Ⅰ型服务。 具体操作 可以参考下列示例进行HttpURLConnection开发。 import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Base64; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.SortedMap; import java.util.TimeZone; import java.util.TreeMap; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; public class OOSDemoForV2Signer { private static final String DATESTR "EEE, d MMM yyyy HH:mm:ss 'GMT'"; private static final SimpleDateFormat DATEFMT new SimpleDateFormat(DATESTR, Locale.ENGLISH); static { TimeZone gmt TimeZone.getTimeZone("GMT"); DATEFMT.setTimeZone(gmt); } private static final String OOSACCESSKEY "your ak"; private static final String OOSSECRETKEY "your sk"; private static final String OOSENDPOINT "ooscn.ctyunapi.cn"; private static final String OOSOBJECTCONTENT "yourobjectcontent"; private static final int CONNTIMEOUT 30000; private static final int READTIMEOUT 30000; public void putObject(String bucket, String objectKey) { try { Map headers new HashMap<>(); headers.put("ContentType", "text/plain"); headers.put("xamzdate", "xxxx"); HttpURLConnection connection generateConnection("PUT", bucket, objectKey, headers); connection.setFixedLengthStreamingMode(OOSOBJECTCONTENT.length()); connection.setDoOutput(true); connection.connect(); byte[] requestBody OOSOBJECTCONTENT.getBytes(); try (OutputStream outputStream connection.getOutputStream()) { outputStream.write(requestBody); } int responseCode connection.getResponseCode(); if (responseCode 200) { System.out.println("put object success"); } else { try (InputStream inputStream connection.getErrorStream()) { BufferedReader reader new BufferedReader(new InputStreamReader(inputStream)); String line; while ((line reader.readLine()) ! null) { System.out.println(line); } } } } catch (Exception e) { e.printStackTrace(); } } public void getObject(String bucket, String objectKey) { try { HttpURLConnection connection generateConnection("GET", bucket, objectKey, null); connection.connect(); int responseCode connection.getResponseCode(); // 在responseCode为200 的情况下, 可将connection.getInputStream()的对象数据读出。 if (responseCode 200) { System.out.println("get object success"); } try (InputStream inputStream responseCode 200 ? connection.getInputStream() : connection.getErrorStream()) { BufferedReader reader new BufferedReader(new InputStreamReader(inputStream)); String line; while ((line reader.readLine()) ! null) { System.out.println(line); } } } catch (Exception e) { // 异常可选择抛出或者处理掉。 e.printStackTrace(); } } public void deleteObject(String bucket, String objectKey) { try { HttpURLConnection connection generateConnection("DELETE", bucket, objectKey, null); connection.connect(); int responseCode connection.getResponseCode(); if (responseCode 204) { System.out.println("delete object success"); } else { try (InputStream inputStream connection.getErrorStream()) { BufferedReader reader new BufferedReader(new InputStreamReader(inputStream)); String line; while ((line reader.readLine()) ! null) { System.out.println(line); } } } } catch (Exception e) { // 异常可选择抛出或者处理掉。 e.printStackTrace(); } } / 1 并不是headers里的所有头域,都参与计算签名。详情请参照 3.1.1章节StringToSign的构成说明 2 任何头以xamzmeta这个前缀开始都会被认为是用户的元数据,当用户检索时,它将会和对象一起被存储并返回。 PUT请求头大小限制为8KiB。在PUT请求头中,用户定义的元数据大小限制为2KiB。 例:headers.put("xamzmetatest", "oos"); / private HttpURLConnection generateConnection(String method, String bucket, String objectKey, Map headers) throws Exception { if (headers null) { headers new TreeMap<>(); } if (!headers.containsKey("Date")) { String date DATEFMT.format(new Date()); headers.put("Date", date); } Map querys new HashMap<>(32); // 设置查询参数示例,可按需选择是否在请求url上设置查询参数。更多接口参数请参考《OOS开发者文档v6》 querys.put("responsecontenttype", "application/octetstream"); String authorization v2Sign(method, bucket, objectKey, headers, querys); String requestUrl " + bucket + "." + OOSENDPOINT + "/" + urlEncode(objectKey, false); if (querys.size() ! 0) { requestUrl + "?" + encodeParameters(querys); } URL url new URL(requestUrl); HttpURLConnection connection (HttpURLConnection)url.openConnection(); connection.setRequestProperty("Authorization", authorization); connection.setConnectTimeout(CONNTIMEOUT); connection.setReadTimeout(READTIMEOUT); connection.setRequestMethod(method); if (null ! headers) { headers.forEach(connection::setRequestProperty); } return connection; } private String encodeParameters(Map querys) { StringBuilder builder new StringBuilder(); Iterator > pairs querys.entrySet().iterator(); while (pairs.hasNext()) { Map.Entry pair pairs.next(); builder.append(urlEncode(pair.getKey(), false)); builder.append(""); builder.append(urlEncode(pair.getValue(), false)); if (pairs.hasNext()) { builder.append("&"); } } return builder.toString(); } // 以下是签名计算相关方法 / The set of request parameters which must be included in the canonical string to sign. / private static final List SIGNEDPARAMETERS Arrays.asList("acl", "torrent", "logging", "location", "policy", "requestPayment", "versioning", "versions", "versionId", "notification", "uploadId", "uploads", "partNumber", "website", "delete", "lifecycle", "tagging", "cors", "restore", "responsecachecontrol", "responsecontentdisposition", "responsecontentencoding", "responsecontentlanguage", "responsecontenttype", "responseexpires"); private String v2Sign(String method, String bucket, String objectKey, Map headers, Map querys) throws Exception { String canonicalString getCanonicalString(method, toResourcePath(bucket, objectKey), headers, querys); String signature sign(canonicalString); return "AWS " + OOSACCESSKEY + ":" + signature; } private String sign(String data) throws Exception { try { Mac mac Mac.getInstance("HmacSHA1"); mac.init(new SecretKeySpec(OOSSECRETKEY.getBytes(StandardCharsets.UTF8), "HmacSHA1")); byte[] bs mac.doFinal(data.getBytes(StandardCharsets.UTF8)); return Base64.getEncoder().encodeToString(bs); } catch (Exception e) { throw new Exception("Unable to calculate a request signature: " + e.getMessage(), e); } } / Calculate the canonical string for a REST/HTTP request to OOS. When expires is nonnull, it will be used instead of the Date header. / private String getCanonicalString(String method, String resource, Map headers, Map querys) { StringBuilder buf new StringBuilder(); buf.append(method).append("n"); SortedMap interestingHeaders new TreeMap<>(); if (headers ! null && headers.size() > 0) { for (Map.Entry entry : headers.entrySet()) { String key entry.getKey(); String value entry.getValue(); if (key null) { continue; } String lk key.toLowerCase(Locale.getDefault()); if ("contenttype".equals(lk) "contentmd5".equals(lk) "date".equals(lk) lk.startsWith("xamz")) { interestingHeaders.put(lk, value); } } } // Remove default date timestamp if "xamzdate" is set. if (interestingHeaders.containsKey("xamzdate")) { interestingHeaders.put("date", ""); } // These headers require that we still put a new line in after them, // even if they don't exist. if (!interestingHeaders.containsKey("contenttype")) { interestingHeaders.put("contenttype", ""); } if (!interestingHeaders.containsKey("contentmd5")) { interestingHeaders.put("contentmd5", ""); } // Any parameters that are prefixed with "xamz" need to be included // in the headers section of the canonical string to sign if (querys ! null) { for (Map.Entry parameter : querys.entrySet()) { if (parameter.getKey().toLowerCase().startsWith("xamz")) { interestingHeaders.put(parameter.getKey().toLowerCase(), parameter.getValue()); } } } for (Map.Entry entry : interestingHeaders.entrySet()) { String key entry.getKey(); Object value entry.getValue(); if (key.toLowerCase().startsWith("xamz")) { buf.append(key).append(':').append(value); } else { buf.append(value); } buf.append("n"); } buf.append(resource); if (querys ! null) { String[] parameterNames querys.keySet().toArray(new String[0]); Arrays.sort(parameterNames); char separator '?'; for (String parameterName : parameterNames) { // Skip any parameters that aren't part of the canonical signed string if (!SIGNEDPARAMETERS.contains(parameterName)) { continue; } buf.append(separator); buf.append(parameterName); String parameterValue querys.get(parameterName); if (parameterValue ! null && !"".equals(parameterValue)) { buf.append("").append(parameterValue); } separator '&'; } } return buf.toString(); } private String toResourcePath(String bucket, String objectKey) { String resourcePath "/" + ((bucket ! null && !"".equals(bucket)) ? bucket : "") + ((objectKey ! null) ? "/" + objectKey : ""); return urlEncode(resourcePath, true); } / @param keepPathSlash 实际上,根据RFC 3986规范,url中的 '/'和'~' 可以不用转译,URLEncoder做了转译,但是为了兼容浏览器解析文件名,要求 '/'和'~'不能做转译。 @param url 客户请求的url,也就是object key @return 转译后的url / private String urlEncode(String url, boolean keepPathSlash) { String encoded; try { encoded URLEncoder.encode(url, StandardCharsets.UTF8.toString()); } catch (UnsupportedEncodingException e) { throw new RuntimeException("UTF8 encoding is not supported.", e); } if (keepPathSlash) { // Web browsers do not always handle '+' characters well, use the wellsupported '%20' instead. encoded encoded.replaceAll("+", "%20"); // Change all "%2F" back to "/", so that when users download a file in a virtual folder by the presigned // URL, // the web browsers won't mess up the filename. (e.g. 'folder1folder2filename' instead of 'filename') encoded encoded.replace("%2F", "/"); encoded encoded.replace("%7E", "~"); } return encoded; } public static void main(String[] args) { String bucket "your bucket"; String objectKey "your object key"; OOSDemoForV2Signer v2 new OOSDemoForV2Signer(); v2.putObject(bucket, objectKey); v2.getObject(bucket, objectKey); v2.deleteObject(bucket, objectKey); } }