使用V4签名时的HttpURLConnection开发 本节主要介绍使用V4签名时的HttpURLConnection开发。 应用场景 V4签名下,使用HttpURLConnection开发。 前提条件 已开通对象存储(经典版)Ⅰ型服务。 具体操作 可以参考下列示例进行HttpURLConnection开发。 package cn.ctyun.oos.sample; 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.security.MessageDigest; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.SortedMap; import java.util.TimeZone; import java.util.TreeMap; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; public class OOSDemoForV4Signer { private static final SimpleDateFormat ISO8601DATEFORMAT new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'"); private static final SimpleDateFormat ISO8601DAYFORMAT new SimpleDateFormat("yyyyMMdd"); private static final char[] HEXCODE "0123456789abcdef".toCharArray(); private static final String UNSIGNEDPAYLOAD "UNSIGNEDPAYLOAD"; private static final String SCHEME "AWS4"; private static final String ALGORITHM "HMACSHA256"; private static final String TERMINATOR "aws4request"; private static final String HMACSHA256 "HmacSHA256"; private static final String OOSACCESSKEY "youraccesskey"; private static final String OOSSECRETKEY "yoursecretkey"; private static final String OOSENDPOINT "ooscn.ctyunapi.cn"; private static final String OOSBUCKET "your bucket name"; private static final String OOSOBJECTNAME "yourobjectname"; private static final String OOSOBJECTCONTENT "yourobjectcontent"; private static final int DEFAULTTIMEOUT 30000; static { TimeZone utc TimeZone.getTimeZone("UTC"); ISO8601DATEFORMAT.setTimeZone(utc); ISO8601DAYFORMAT.setTimeZone(utc); } public static void main(String[] args) throws Exception { OOSDemoForV4Signer demo new OOSDemoForV4Signer(); demo.putObject(); demo.getObject(); demo.deleteObject(); } public void getObject() { try { HttpURLConnection connection generateConnection("GET", OOSBUCKET, OOSOBJECTNAME); 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 putObject() { try { HttpURLConnection connection generateConnection("PUT", OOSBUCKET, OOSOBJECTNAME); connection.setDoOutput(true); connection.connect(); // Create the object byte[] requestBody OOSOBJECTCONTENT.getBytes(); try (OutputStream outputStream connection.getOutputStream()) { outputStream.write(requestBody); } // Execute the request and print the response 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 deleteObject() { try { HttpURLConnection connection generateConnection("DELETE", OOSBUCKET, OOSOBJECTNAME); 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(); } } private HttpURLConnection generateConnection(String method, String bucket, String objectKey) throws Exception { String requestUrl " + bucket + "." + OOSENDPOINT + "/" + urlEncode(objectKey, true); Map headers new HashMap<>(); // 1 加在headers里的所有头域,都参与计算签名。 // 2 任何头以xamzmeta这个前缀开始都会被认为是用户的元数据,当用户检索时,它将会和对象一起被存储并返回。PUT请求头大小限制为8KiB。在PUT请求头中,用户定义的元数据大小限制为2KiB。 // headers.put("xamzmetatest", "oos"); Map querys new HashMap (); URL url new URL(requestUrl); String authorization v4Sign(headers, querys, UNSIGNEDPAYLOAD, url, method); headers.put("Authorization", authorization); HttpURLConnection connection (HttpURLConnection)new URL(requestUrl).openConnection(); connection.setRequestMethod(method); connection.setConnectTimeout(DEFAULTTIMEOUT); connection.setReadTimeout(DEFAULTTIMEOUT); headers.forEach(connection::setRequestProperty); return connection; } / 以下是签名计算相关方法 / private String v4Sign(Map headers, Map queryParameters, String bodyHash, URL endpointUrl, String httpMethod) { String host endpointUrl.getHost(); String serviceName parseServiceName(host); String regionName parseRegionName(host); // first get the date and time for the subsequent request, and convert // to ISO 8601 format for use in signature generation Date now new Date(); String dateTimeStamp ISO8601DATEFORMAT.format(now); // update the headers with required 'xamzdate' and 'host' values if (headers null) { headers new HashMap (); } headers.put("xamzdate", dateTimeStamp); headers.put("xamzcontentsha256", bodyHash); int port endpointUrl.getPort(); if (port > 1 && port ! 80 && port ! 443) { host host.concat(":" + Integer.toString(port)); } headers.put("Host", host); // canonicalize the headers; we need the set of header names as well as the // names and values to go into the signature process String canonicalizedHeaderNames getCanonicalizeHeaderNames(headers); String canonicalizedHeaders getCanonicalizedHeaderString(headers); // if any query string parameters have been supplied, canonicalize them String canonicalizedQueryParameters getCanonicalizedQueryString(queryParameters); // canonicalize the various components of the request String canonicalRequest getCanonicalRequest(endpointUrl, httpMethod, canonicalizedQueryParameters, canonicalizedHeaderNames, canonicalizedHeaders, bodyHash); // construct the string to be signed String dateStamp ISO8601DAYFORMAT.format(now); String scope dateStamp + "/" + regionName + "/" + serviceName + "/" + TERMINATOR; String stringToSign getStringToSign(SCHEME, ALGORITHM, dateTimeStamp, scope, canonicalRequest); // compute the signing key byte[] kSigning createSignatureKey(OOSSECRETKEY, dateStamp, regionName, serviceName); byte[] signature sign(stringToSign, kSigning, HMACSHA256); String credentialsAuthorizationHeader "Credential" + OOSACCESSKEY + "/" + scope; String signedHeadersAuthorizationHeader "SignedHeaders" + canonicalizedHeaderNames; String signatureAuthorizationHeader "Signature" + toHex(signature); String authorizationHeader SCHEME + "" + ALGORITHM + " " + credentialsAuthorizationHeader + ", " signedHeadersAuthorizationHeader + ", " + signatureAuthorizationHeader; return authorizationHeader; } private String getCanonicalizedQueryString(Map parameters) { if (parameters null parameters.isEmpty()) { return ""; } SortedMap sorted new TreeMap (); Iterator > pairs parameters.entrySet().iterator(); while (pairs.hasNext()) { Map.Entry pair pairs.next(); String key pair.getKey(); String value pair.getValue(); sorted.put(urlEncode(key, false), urlEncode(value, false)); } StringBuilder builder new StringBuilder(); pairs sorted.entrySet().iterator(); while (pairs.hasNext()) { Map.Entry pair pairs.next(); builder.append(pair.getKey()); builder.append(""); builder.append(pair.getValue()); if (pairs.hasNext()) { builder.append("&"); } } return builder.toString(); } private String getCanonicalizedHeaderString(Map headers) { if (headers null headers.isEmpty()) { return ""; } // step1: sort the headers by caseinsensitive order List sortedHeaders new ArrayList (); sortedHeaders.addAll(headers.keySet()); Collections.sort(sortedHeaders, String.CASEINSENSITIVEORDER); // step2: form the canonical header:value entries in sorted order. // Multiple white spaces in the values should be compressed to a single // space. StringBuilder buffer new StringBuilder(); for (String key : sortedHeaders) { buffer.append(key.toLowerCase().replaceAll("s+", " ") + ":" + headers.get(key).replaceAll("s+", " ")); buffer.append("n"); } return buffer.toString(); } private String getCanonicalizeHeaderNames(Map headers) { List sortedHeaders new ArrayList (); sortedHeaders.addAll(headers.keySet()); Collections.sort(sortedHeaders, String.CASEINSENSITIVEORDER); StringBuilder buffer new StringBuilder(); for (String header : sortedHeaders) { if (buffer.length() > 0) buffer.append(";"); buffer.append(header.toLowerCase()); } return buffer.toString(); } private String getCanonicalRequest(URL endpoint, String httpMethod, String queryParameters, String canonicalizedHeaderNames, String canonicalizedHeaders, String bodyHash) { String canonicalRequest; if (bodyHash null bodyHash.equals("")) { canonicalRequest httpMethod + "n" + getCanonicalizedResourcePath(endpoint) + "n" + queryParameters "n" + canonicalizedHeaders + "n" + canonicalizedHeaderNames; } else { canonicalRequest httpMethod + "n" + getCanonicalizedResourcePath(endpoint) + "n" + queryParameters "n" + canonicalizedHeaders + "n" + canonicalizedHeaderNames + "n" + bodyHash; } return canonicalRequest; } private String getStringToSign(String scheme, String algorithm, String dateTime, String scope, String canonicalRequest) { String stringToSign scheme + "" + algorithm + "n" + dateTime + "n" + scope + "n" + toHex(hash(canonicalRequest)); return stringToSign; } private byte[] createSignatureKey(String key, String dateStamp, String regionName, String serviceName) { byte[] kSecret (SCHEME + key).getBytes(); byte[] kDate sign(dateStamp, kSecret, HMACSHA256); byte[] kRegion sign(regionName, kDate, HMACSHA256); byte[] kService sign(serviceName, kRegion, HMACSHA256); byte[] kSigning sign(TERMINATOR, kService, HMACSHA256); return kSigning; } private byte[] sign(String stringData, byte[] key, String algorithm) { try { byte[] data stringData.getBytes("UTF8"); Mac mac Mac.getInstance(algorithm); mac.init(new SecretKeySpec(key, algorithm)); return mac.doFinal(data); } catch (Exception e) { throw new RuntimeException("Unable to calculate a request signature: " + e.getMessage(), e); } } private String getCanonicalizedResourcePath(URL endpoint) { if (endpoint null) { return "/"; } String path endpoint.getPath(); if (path null path.isEmpty()) { return "/"; } // String encodedPath urlEncode(path, true); String encodedPath path; if (encodedPath.startsWith("/")) { return encodedPath; } else { return "/".concat(encodedPath); } } private String urlEncode(String url, boolean keepPathSlash) { String encoded; try { encoded URLEncoder.encode(url, "UTF8"); } catch (UnsupportedEncodingException e) { throw new RuntimeException("UTF8 encoding is not supported.", e); } if (keepPathSlash) { encoded encoded.replace("%2F", "/"); } return encoded; } private String toHex(byte[] data) { StringBuilder r new StringBuilder(data.length 2); for (byte b : data) { r.append(HEXCODE[(b >> 4) & 0xF]); r.append(HEXCODE[(b & 0xF)]); } return r.toString(); } private byte[] hash(String text) { try { MessageDigest md MessageDigest.getInstance("SHA256"); md.reset(); md.update(text.getBytes("UTF8")); return md.digest(); } catch (Exception e) { throw new RuntimeException("Unable to compute hash while signing request: " + e.getMessage(), e); } } private String parseRegionName(String host) { String pattern "oos([w]).ctyunapi.cn$"; Pattern r Pattern.compile(pattern); Matcher m r.matcher(host); if (m.find()) { return m.group(1).toLowerCase(); } else { throw new RuntimeException("parse region error, please check endpoint."); } } private String parseServiceName(String host) { if (host.endsWith("iam.ctyunapi.cn")) { return "sts"; } else { return "s3"; } } }