OrderHive 平台获取签名

OrderHive平台对接 获取签名

测试获取签名后添加订阅

import lombok.AllArgsConstructor;
import lombok.Data;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

import javax.validation.constraints.NotBlank;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * @Author liguangcheng
 * @Date 2022/9/12 09:48
 * @Vision 1.0
 **/
public class test {
    private static final okhttp3.MediaType JSON = okhttp3.MediaType.parse("application/json; charset=utf-8");
    private static final String orderhiveHost = "api.orderhive.com";
    private static final String orderhiveServiceName = "execute-api";
    //平台提供的 ID Token / Refresh Token /App Id
    public static final String ID_TOKEN = "idToken";
    public static final String REFRESH_TOKEN = "refreshToken";
    public static final String APP_ID = "appId";
    //平台回调接口地址
    public static final String WEBHOOKS_URL = "平台回调接口地址";

    public static void main(String[] args)throws Exception {
        //需要先Refresh Token POST https://api.orderhive.com/setup/refreshtokenviaidtoken
        //得到access_key_id  / secret_key / session_token  / region
        //需要注意Refresh Token得到的数据是有效期45分钟 且30内不能重复获取 否则或报错 Tokens data that is received by Refresh Token API is valid upto 30 minutes please try after ...
        RefreshTokenRequest refreshTokenRequest = new RefreshTokenRequest(ID_TOKEN, REFRESH_TOKEN);
        String requestJson = JsonUtils.deserializer(refreshTokenRequest); 
        String url = "https://api.orderhive.com/setup/refreshtokenviaidtoken";
        OkHttpClient okHttpClient = new OkHttpClient();
        Request tokenRequest = new Request.Builder().url(url).post(RequestBody.create(requestJson, JSON))
                .addHeader("Content-Type", "application/json")
                .build();
        Response response = okHttpClient.newCall(tokenRequest).execute();
        String responseStr = response.body().string();
        RefreshTokenResponse tokenResponse = JsonUtils.serializable(responseStr, RefreshTokenResponse.class);

        //通过得到的access_key_id  / secret_key / session_token  / region 加上请求方法 请求地址 请求参数 获取签名
        Date now = new Date();
        String method = "POST";
        String uri = "/setup/subscription/add";
        SubscriptionRequest orderTagged = new SubscriptionRequest(APP_ID, WEBHOOKS_URL, Arrays.asList("orderTagged"));
        String addSubscriptionJson = JsonUtils.deserializer(orderTagged);
        String authorization = getAuthorization(tokenResponse, now, addSubscriptionJson, uri, method, null);
        //得到签名后去请求
        String result = performOrderhiveRequest(tokenResponse, now, uri, authorization, method, requestJson);
    }
  
  //获取签名
    private static String getAuthorization(RefreshTokenResponse tokenResponse, Date now, String requestJson, String uri, String method, TreeMap<String, String> queryParameters) {

        //AWS4-HMAC-SHA256 Credential=ASIAJCOK74M5JWLWMCDQ/20171213/ap-southeast-1/execute-api/aws4_request, SignedHeaders=content-type;host;id_token;x-amz-date;x-amz-security-token, Signature=8e3dbc663f97508406c4825b74a647765022ae021fa224754701722b7bcf2288

        String accessKeyId = tokenResponse.getAccessKeyId();
        String secretKey = tokenResponse.getSecretKey();
        String sessionToken = tokenResponse.getSessionToken();
        String region = tokenResponse.getRegion();
        String idToken = tokenResponse.getIdToken();
        String xAmzDate = getTimeStamp(now);
        TreeMap<String, String> awsHeaders = new TreeMap<String, String>();
        awsHeaders.put("content-type", JSON.toString());
        awsHeaders.put("host", orderhiveHost);
        awsHeaders.put("id_token", idToken);
        awsHeaders.put("x-amz-date", xAmzDate);
        awsHeaders.put("x-amz-security-token", sessionToken);
        Map<String, String> headers = new AWSV4Auth.Builder(accessKeyId, secretKey)
                .regionName(region)
                .serviceName(orderhiveServiceName)
                .httpMethodName(method)
                .canonicalURI(uri)
                .now(now)
                .queryParametes(queryParameters)
                .awsHeaders(awsHeaders)
                .payload(requestJson)
                .debug()
                .build()
                .getHeaders();

        String authorization = headers.get("Authorization");
        return authorization;
    }
  
  //请求平台
    private static String performOrderhiveRequest(RefreshTokenResponse tokenResponse, Date now, String uri, String authorization, String method, String requestJson) throws Exception {
        OkHttpClient okHttpClient = new OkHttpClient();
        Request.Builder builder = new Request.Builder().url("https://" + orderhiveHost + uri)
                // .post(RequestBody.create(requestJson, JSON))
                .addHeader("Content-Type", JSON.toString())
                .addHeader("host", orderhiveHost)
                .addHeader("id_token", tokenResponse.getIdToken())
                .addHeader("x-amz-date", getTimeStamp(now))
                .addHeader("x-amz-security-token", tokenResponse.getSessionToken())
                .addHeader("Authorization", authorization)
                .addHeader("Accept", "application/json");
        Request build;
        switch (method) {
            case "GET":
                build = builder
                        .get()
                        .build();
                break;
            case "POST":
                build = builder
                        .post(RequestBody.create(requestJson, JSON))
                        .build();
                break;
            case "PUT":
                build = builder
                        .put(RequestBody.create(requestJson, JSON))
                        .build();
                break;
            default:
                throw new Exception("Unknown method called");
        }
        Response execute = okHttpClient.newCall(build).execute();
        String string = execute.body().string();
        return string;
    }


    private static String getTimeStamp(Date date) {
        DateFormat dateFormat = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
        dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));//server timezone
        return dateFormat.format(date);
    }

    @AllArgsConstructor
    @Data
    public static class RefreshTokenRequest {
        @JsonProperty("id_token")
        private String idToken;
        @JsonProperty("refresh_token")
        private String refreshToken;
    }

    @AllArgsConstructor
    @Data
    public static class RefreshTokenResponse {
        @NotBlank
        @JsonProperty("access_key_id")
        private String accessKeyId;
        @NotBlank
        @JsonProperty("secret_key")
        private String secretKey;
        @NotBlank
        @JsonProperty("session_token")
        private String sessionToken;
        @NotBlank
        @JsonProperty("id_token")
        private String idToken;
        @NotBlank
        @JsonProperty("refresh_token")
        private String refreshToken;
        @NotBlank
        @JsonProperty("region")
        private String region;
    }
}

签名工具类

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * AWSV4Auth
 *
 * @Author liguangcheng
 * @Date 2022/8/19 10:36
 * @Vision 1.0
 **/
public class AWSV4Auth {
    private AWSV4Auth() {
    }

    public static class Builder {
        private Date now;
        private String accessKeyID;
        private String secretAccessKey;
        private String regionName;
        private String serviceName;
        private String httpMethodName;
        private String canonicalURI;
        private TreeMap<String, String> queryParametes;
        private TreeMap<String, String> awsHeaders;
        private String payload;
        private boolean debug = false;

        public Builder(String accessKeyID, String secretAccessKey) {
            this.accessKeyID = accessKeyID;
            this.secretAccessKey = secretAccessKey;
        }

        public Builder now(Date now) {
            this.now = now;
            return this;
        }

        public Builder regionName(String regionName) {
            this.regionName = regionName;
            return this;
        }

        public Builder serviceName(String serviceName) {
            this.serviceName = serviceName;
            return this;
        }

        public Builder httpMethodName(String httpMethodName) {
            this.httpMethodName = httpMethodName;
            return this;
        }

        public Builder canonicalURI(String canonicalURI) {
            this.canonicalURI = canonicalURI;
            return this;
        }

        public Builder queryParametes(TreeMap<String, String> queryParametes) {
            this.queryParametes = queryParametes;
            return this;
        }

        public Builder awsHeaders(TreeMap<String, String> awsHeaders) {
            this.awsHeaders = awsHeaders;
            return this;
        }

        public Builder payload(String payload) {
            this.payload = payload;
            return this;
        }

        public Builder debug() {
            this.debug = false;
            return this;
        }

        public AWSV4Auth build() {
            return new AWSV4Auth(this);
        }
    }

    private Date now;
    private String accessKeyID;
    private String secretAccessKey;
    private String regionName;
    private String serviceName;
    private String httpMethodName;
    private String canonicalURI;
    private TreeMap<String, String> queryParametes;
    private TreeMap<String, String> awsHeaders;
    private String payload;
    private boolean debug = false;

    /* Other variables */
    private final String HMACAlgorithm = "AWS4-HMAC-SHA256";
    private final String aws4Request = "aws4_request";
    private String strSignedHeader;
    private String xAmzDate;
    private String currentDate;

    private AWSV4Auth(Builder builder) {
        now = builder.now;
        accessKeyID = builder.accessKeyID;
        secretAccessKey = builder.secretAccessKey;
        regionName = builder.regionName;
        serviceName = builder.serviceName;
        httpMethodName = builder.httpMethodName;
        canonicalURI = builder.canonicalURI;
        queryParametes = builder.queryParametes;
        awsHeaders = builder.awsHeaders;
        payload = builder.payload;
        debug = builder.debug;

        /* Get current timestamp value.(UTC) */
        xAmzDate = getTimeStamp();
        currentDate = getDate();
    }

    /**
     * 任务 1:针对签名版本 4 创建规范请求
     *
     * @return
     */
    private String prepareCanonicalRequest() {
        StringBuilder canonicalURL = new StringBuilder("");

        /* Step 1.1 以HTTP方法(GET, PUT, POST, etc.)开头, 然后换行. */
        canonicalURL.append(httpMethodName).append("\n");

        /* Step 1.2 添加URI参数,换行. */
        canonicalURI = canonicalURI == null || canonicalURI.trim().isEmpty() ? "/" : canonicalURI;
        if (canonicalURI.contains("?")) {
            int i = canonicalURI.indexOf("?");
            canonicalURI = canonicalURI.substring(0, i);
        }
        canonicalURL.append(canonicalURI).append("\n");

        /* Step 1.3 添加查询参数,换行. */
        StringBuilder queryString = new StringBuilder("");
        if (queryParametes != null && !queryParametes.isEmpty()) {
            for (Map.Entry<String, String> entrySet : queryParametes.entrySet()) {
                String key = entrySet.getKey();
                String value = entrySet.getValue();
                queryString.append(key).append("=").append(encodeParameter(value)).append("&");
            }

            queryString.deleteCharAt(queryString.lastIndexOf("&"));

            queryString.append("\n");
        } else {
            queryString.append("\n");
        }
        canonicalURL.append(queryString);

        /* Step 1.4 添加headers, 每个header都需要换行. */
        StringBuilder signedHeaders = new StringBuilder("");
        if (awsHeaders != null && !awsHeaders.isEmpty()) {
            for (Map.Entry<String, String> entrySet : awsHeaders.entrySet()) {
                String key = entrySet.getKey();
                String value = entrySet.getValue();
                signedHeaders.append(key).append(";");
                canonicalURL.append(key).append(":").append(value).append("\n");
            }
            canonicalURL.append("\n");
        } else {
            canonicalURL.append("\n");
        }

        /* Step 1.5 添加签名的headers并换行. */
        strSignedHeader = signedHeaders.substring(0, signedHeaders.length() - 1); // 删掉最后的 ";"
        canonicalURL.append(strSignedHeader).append("\n");

        /* Step 1.6 对HTTP或HTTPS的body进行SHA256处理. */
        payload = payload == null ? "" : payload;
        canonicalURL.append(generateHex(payload));

        if (debug) {
            System.out.println("##Canonical Request:\n" + canonicalURL.toString());
        }

        return canonicalURL.toString();
    }

    /**
     * 任务 2:创建签名版本 4 的待签字符串
     *
     * @param canonicalURL
     * @return
     */
    private String prepareStringToSign(String canonicalURL) {
        String stringToSign = "";

        /* Step 2.1 以算法名称开头,并换行. */
        stringToSign = HMACAlgorithm + "\n";

        /* Step 2.2 添加日期,并换行. */
        stringToSign += xAmzDate + "\n";

        /* Step 2.3 添加认证范围,并换行. */
        stringToSign += currentDate + "/" + regionName + "/" + serviceName + "/" + aws4Request + "\n";

        /* Step 2.4 添加任务1返回的规范URL哈希处理结果,然后换行. */
        stringToSign += generateHex(canonicalURL);

        if (debug) {
            System.out.println("##String to sign:\n" + stringToSign);
        }

        return stringToSign;
    }

    /**
     * 任务 3:为 AWS Signature 版本 4 计算签名
     *
     * @param stringToSign
     * @return
     */
    private String calculateSignature(String stringToSign) {
        try {
            /* Step 3.1 生成签名的key */
            byte[] signatureKey = getSignatureKey(secretAccessKey, currentDate, regionName, serviceName);

            /* Step 3.2 计算签名. */
            byte[] signature = HmacSHA256(signatureKey, stringToSign);

            /* Step 3.2.1 对签名编码处理 */
            String strHexSignature = bytesToHex(signature);
            return strHexSignature;
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }

    /**
     * 任务 4:将签名信息添加到请求并返回headers
     *
     * @return
     */
    public Map<String, String> getHeaders() {
        awsHeaders.put("x-amz-date", xAmzDate);

        /* 执行任务 1: 创建aws v4签名的规范请求字符串. */
        String canonicalURL = prepareCanonicalRequest();

        /* 执行任务 2: 创建用来认证的字符串 4. */
        String stringToSign = prepareStringToSign(canonicalURL);

        /* 执行任务 3: 计算签名. */
        String signature = calculateSignature(stringToSign);

        if (signature != null) {
            Map<String, String> header = new HashMap<String, String>(0);
            header.put("x-amz-date", xAmzDate);
            header.put("Authorization", buildAuthorizationString(signature));

            if (debug) {
                System.out.println("##Signature:\n" + signature);
                System.out.println("##Header:");
                for (Map.Entry<String, String> entrySet : header.entrySet()) {
                    System.out.println(entrySet.getKey() + " = " + entrySet.getValue());
                }
                System.out.println("================================");
            }
            return header;
        } else {
            if (debug) {
                System.out.println("##Signature:\n" + signature);
            }
            return null;
        }
    }

    /**
     * 连接前几步处理的字符串生成Authorization header值.
     *
     * @param strSignature
     * @return
     */
    private String buildAuthorizationString(String strSignature) {
        return HMACAlgorithm + " "
                + "Credential=" + accessKeyID + "/" + getDate() + "/" + regionName + "/" + serviceName + "/" + aws4Request + ", "
                + "SignedHeaders=" + strSignedHeader + ", "
                + "Signature=" + strSignature;
    }

    /**
     * 将字符串16进制化.
     *
     * @param data
     * @return
     */
    private String generateHex(String data) {
        MessageDigest messageDigest;
        try {
            messageDigest = MessageDigest.getInstance("SHA-256");
            messageDigest.update(data.getBytes("UTF-8"));
            byte[] digest = messageDigest.digest();
            return String.format("%064x", new java.math.BigInteger(1, digest));
        } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 以给定的key应用HmacSHA256算法处理数据.
     *
     * @param data
     * @param key
     * @return
     * @throws Exception
     * @reference: http://docs.aws.amazon.com/general/latest/gr/signature-v4-examples.html#signature-v4-examples-java
     */
    private byte[] HmacSHA256(byte[] key, String data) throws Exception {
        String algorithm = "HmacSHA256";
        Mac mac = Mac.getInstance(algorithm);
        mac.init(new SecretKeySpec(key, algorithm));
        return mac.doFinal(data.getBytes("UTF8"));
    }

    /**
     * 生成AWS 签名
     *
     * @param key
     * @param date
     * @param regionName
     * @param serviceName
     * @return
     * @throws Exception
     * @reference http://docs.aws.amazon.com/general/latest/gr/signature-v4-examples.html#signature-v4-examples-java
     */
    private byte[] getSignatureKey(String key, String date, String regionName, String serviceName) throws Exception {
        byte[] kSecret = ("AWS4" + key).getBytes("UTF8");
        byte[] kDate = HmacSHA256(kSecret, date);
        byte[] kRegion = HmacSHA256(kDate, regionName);
        byte[] kService = HmacSHA256(kRegion, serviceName);
        byte[] kSigning = HmacSHA256(kService, aws4Request);
        return kSigning;
    }

    final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();

    /**
     * 将字节数组转换为16进制字符串
     *
     * @param bytes
     * @return
     */
    private String bytesToHex(byte[] bytes) {
        char[] hexChars = new char[bytes.length * 2];
        for (int j = 0; j < bytes.length; j++) {
            int v = bytes[j] & 0xFF;
            hexChars[j * 2] = hexArray[v >>> 4];
            hexChars[j * 2 + 1] = hexArray[v & 0x0F];
        }
        return new String(hexChars).toLowerCase();
    }

    /**
     * 获取yyyyMMdd'T'HHmmss'Z'格式的当前时间
     *
     * @return
     */
    private String getTimeStamp() {
        DateFormat dateFormat = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
        dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));//server timezone
        return dateFormat.format(now);
    }

    /**
     * 获取yyyyMMdd格式的当前日期
     *
     * @return
     */
    private String getDate() {
        DateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
        dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));//server timezone
        return dateFormat.format(now);
    }

    /**
     * UTF-8编码
     *
     * @param param
     * @return
     */
    private String encodeParameter(String param) {
        try {
            return URLEncoder.encode(param, "UTF-8");
        } catch (Exception e) {
            return URLEncoder.encode(param);
        }
    }


}


参考博客
orderhive文档

posted @ 2022-09-12 10:31  Crush_1018  阅读(46)  评论(0)    收藏  举报