|
@@ -0,0 +1,176 @@
|
|
|
+package com.qmth.ops.biz.ai.client.baidu;
|
|
|
+
|
|
|
+import com.qmth.boot.tools.models.ByteArray;
|
|
|
+import okhttp3.Headers;
|
|
|
+import okhttp3.HttpUrl;
|
|
|
+import okhttp3.Request;
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
+import org.springframework.http.HttpHeaders;
|
|
|
+
|
|
|
+import javax.crypto.Mac;
|
|
|
+import javax.crypto.spec.SecretKeySpec;
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
+import java.util.*;
|
|
|
+
|
|
|
+/**
|
|
|
+ * The V1 implementation of Signer with the BCE signing protocol.
|
|
|
+ */
|
|
|
+public class BceV1Signer {
|
|
|
+
|
|
|
+ private static final Logger logger = LoggerFactory.getLogger(BceV1Signer.class);
|
|
|
+
|
|
|
+ public static final String X_BCE_DATE = "x-bce-date";
|
|
|
+
|
|
|
+ private static final String BCE_PREFIX = "x-bce-";
|
|
|
+
|
|
|
+ private static final String BCE_AUTH_VERSION = "bce-auth-v1";
|
|
|
+
|
|
|
+ // Default headers to sign with the BCE signing protocol.
|
|
|
+ private static final Set<String> defaultHeadersToSign = new HashSet<>();
|
|
|
+
|
|
|
+ private static BitSet URI_UNRESERVED_CHARACTERS = new BitSet();
|
|
|
+
|
|
|
+ private static String[] PERCENT_ENCODED_STRINGS = new String[256];
|
|
|
+
|
|
|
+ private static final String headerJoiner = "\n";
|
|
|
+
|
|
|
+ private static final String singedHeaderJoiner = ";";
|
|
|
+
|
|
|
+ private static final String queryStringJoiner = "&";
|
|
|
+
|
|
|
+ private static final int DEFAULT_EXPIRATION_IN_SECONDS = 1800;
|
|
|
+
|
|
|
+ public static final String DATE_FORMAT_PATTERN = "yyyy-MM-dd'T'HH:mm:ss'Z'";
|
|
|
+
|
|
|
+ static {
|
|
|
+ BceV1Signer.defaultHeadersToSign.add(HttpHeaders.HOST.toLowerCase());
|
|
|
+ //BceV1Signer.defaultHeadersToSign.add(HttpHeaders.CONTENT_TYPE.toLowerCase());
|
|
|
+ //BceV1Signer.defaultHeadersToSign.add(HttpHeaders.CONTENT_LENGTH.toLowerCase());
|
|
|
+ //BceV1Signer.defaultHeadersToSign.add("content-md5");
|
|
|
+
|
|
|
+ for (int i = 'a'; i <= 'z'; i++) {
|
|
|
+ URI_UNRESERVED_CHARACTERS.set(i);
|
|
|
+ }
|
|
|
+ for (int i = 'A'; i <= 'Z'; i++) {
|
|
|
+ URI_UNRESERVED_CHARACTERS.set(i);
|
|
|
+ }
|
|
|
+ for (int i = '0'; i <= '9'; i++) {
|
|
|
+ URI_UNRESERVED_CHARACTERS.set(i);
|
|
|
+ }
|
|
|
+ URI_UNRESERVED_CHARACTERS.set('-');
|
|
|
+ URI_UNRESERVED_CHARACTERS.set('.');
|
|
|
+ URI_UNRESERVED_CHARACTERS.set('_');
|
|
|
+ URI_UNRESERVED_CHARACTERS.set('~');
|
|
|
+
|
|
|
+ for (int i = 0; i < PERCENT_ENCODED_STRINGS.length; ++i) {
|
|
|
+ PERCENT_ENCODED_STRINGS[i] = String.format("%%%02X", i);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public static String sign(Request request, String accessKey, String accessSecret) {
|
|
|
+ String timestamp = request.header(X_BCE_DATE);
|
|
|
+ String authString =
|
|
|
+ BceV1Signer.BCE_AUTH_VERSION + "/" + accessKey + "/" + timestamp + "/" + DEFAULT_EXPIRATION_IN_SECONDS;
|
|
|
+
|
|
|
+ String signingKey = sha256Hex(accessSecret, authString);
|
|
|
+ // Formatting the URL with signing protocol.
|
|
|
+ String canonicalURI = getCanonicalURIPath(request.url().uri().getPath());
|
|
|
+ // Formatting the query string with signing protocol.
|
|
|
+ String canonicalQueryString = getCanonicalQueryString(request.url());
|
|
|
+ // Formatting the headers from the request based on signing protocol.
|
|
|
+ String canonicalHeader = getCanonicalHeaders(request.headers());
|
|
|
+ String signedHeaders = getSignedHeaders(request.headers());
|
|
|
+
|
|
|
+ String canonicalRequest =
|
|
|
+ request.method().toUpperCase() + "\n" + canonicalURI + "\n" + canonicalQueryString + "\n"
|
|
|
+ + canonicalHeader;
|
|
|
+
|
|
|
+ // Signing the canonical request using key with sha-256 algorithm.
|
|
|
+ String signature = sha256Hex(signingKey, canonicalRequest);
|
|
|
+
|
|
|
+ String authorizationHeader = authString + "/" + signedHeaders + "/" + signature;
|
|
|
+
|
|
|
+ //logger.debug("\nCanonicalRequest:\n{}\n-----------\nAuthorization:\n{}", canonicalRequest, authorizationHeader);
|
|
|
+
|
|
|
+ return authorizationHeader;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static String getCanonicalURIPath(String path) {
|
|
|
+ if (path == null) {
|
|
|
+ return "/";
|
|
|
+ } else if (path.startsWith("/")) {
|
|
|
+ return normalizePath(path);
|
|
|
+ } else {
|
|
|
+ return "/" + normalizePath(path);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static String getSignedHeaders(Headers headers) {
|
|
|
+ Set<String> headerStrings = new HashSet<>();
|
|
|
+ for (String name : headers.names()) {
|
|
|
+ name = name.toLowerCase();
|
|
|
+ if (name.startsWith(BCE_PREFIX) || defaultHeadersToSign.contains(name)) {
|
|
|
+ headerStrings.add(name);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ List<String> list = new ArrayList<>(headerStrings);
|
|
|
+ Collections.sort(list);
|
|
|
+ return StringUtils.join(list, singedHeaderJoiner);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static String getCanonicalHeaders(Headers headers) {
|
|
|
+ List<String> headerStrings = new LinkedList<>();
|
|
|
+ for (String name : headers.names()) {
|
|
|
+ String value = StringUtils.trimToEmpty(headers.get(name));
|
|
|
+ name = name.toLowerCase();
|
|
|
+ if (name.startsWith(BCE_PREFIX) || defaultHeadersToSign.contains(name)) {
|
|
|
+ headerStrings.add(normalize(name) + ':' + normalize(value));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ Collections.sort(headerStrings);
|
|
|
+ return StringUtils.join(headerStrings, headerJoiner);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static String sha256Hex(String signingKey, String stringToSign) {
|
|
|
+ try {
|
|
|
+ Mac mac = Mac.getInstance("HmacSHA256");
|
|
|
+ mac.init(new SecretKeySpec(signingKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
|
|
|
+ return ByteArray.fromArray(mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8))).toHexString()
|
|
|
+ .toLowerCase();
|
|
|
+ } catch (Exception e) {
|
|
|
+ throw new RuntimeException("Fail to generate the signature", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static String normalizePath(String path) {
|
|
|
+ return normalize(path).replace("%2F", "/");
|
|
|
+ }
|
|
|
+
|
|
|
+ private static String normalize(String value) {
|
|
|
+ StringBuilder builder = new StringBuilder();
|
|
|
+ for (byte b : value.getBytes(StandardCharsets.UTF_8)) {
|
|
|
+ if (URI_UNRESERVED_CHARACTERS.get(b & 0xFF)) {
|
|
|
+ builder.append((char) b);
|
|
|
+ } else {
|
|
|
+ builder.append(PERCENT_ENCODED_STRINGS[b & 0xFF]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return builder.toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ private static String getCanonicalQueryString(HttpUrl url) {
|
|
|
+ List<String> parameterStrings = new ArrayList<>();
|
|
|
+ for (String name : url.queryParameterNames()) {
|
|
|
+ if (HttpHeaders.AUTHORIZATION.equalsIgnoreCase(name)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ String value = url.queryParameter(name);
|
|
|
+ parameterStrings.add(normalize(name) + '=' + normalize(StringUtils.trimToEmpty(value)));
|
|
|
+ }
|
|
|
+ Collections.sort(parameterStrings);
|
|
|
+ return StringUtils.join(parameterStrings, queryStringJoiner);
|
|
|
+ }
|
|
|
+
|
|
|
+}
|