From 587a1f5739219a0caf324e10f87d2a0c6ed11e3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=88=E4=B9=8B=E7=8D=A0?= <1224073217@qq.com> Date: Thu, 17 Sep 2020 23:04:04 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=98=BF=E9=87=8C?= =?UTF-8?q?=E4=BA=91=E3=80=81=E8=85=BE=E8=AE=AF=E4=BA=91API=E6=97=B6?= =?UTF-8?q?=E7=9A=84=E8=AF=B7=E6=B1=82=E7=AD=BE=E5=90=8D=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cn/hutool/crypto/AliYunSignature.java | 107 ++++++++++++++++++ .../hutool/crypto/TencentCloudSignature.java | 91 +++++++++++++++ 2 files changed, 198 insertions(+) create mode 100644 hutool-crypto/src/main/java/cn/hutool/crypto/AliYunSignature.java create mode 100644 hutool-crypto/src/main/java/cn/hutool/crypto/TencentCloudSignature.java diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/AliYunSignature.java b/hutool-crypto/src/main/java/cn/hutool/crypto/AliYunSignature.java new file mode 100644 index 0000000000..5f2dd2282f --- /dev/null +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/AliYunSignature.java @@ -0,0 +1,107 @@ +package cn.hutool.crypto; + +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.lang.Filter; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONObject; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Iterator; +import java.util.Map; +import java.util.TimeZone; +import java.util.TreeMap; + +/** + *

构建请求 阿里云API 时需要用到的签名

+ *

2020-09-17 21:02

+ * + * @author Dan + **/ +public class AliYunSignature { + + /** + * 初始化签名参数 + * 组建阿里云签名常用的公共请求参数 + * + * @param accessKeyId 访问密钥ID,AccessKey用于调用API + * @return 返回一个Map,包含阿里云签名的公共请求参数 + */ + public static Map signatureParameterInitialization(String accessKeyId) { + // 构建请求参数 + Map parameters = new TreeMap<>(); + // 第一部分由系统参数组成 + parameters.put("SignatureMethod", "HMAC-SHA1"); + parameters.put("SignatureNonce", IdUtil.objectId()); + parameters.put("AccessKeyId", accessKeyId); + parameters.put("SignatureVersion", "1.0"); + parameters.put("Timestamp", DateUtil.format(new DateTime(TimeZone.getTimeZone("GMT+:08:00")), "yyyy-MM-dd'T'HH:mm:ss'Z'")); + parameters.put("Format", "json"); + return parameters; + } + + /** + * 生成阿里云API请求签名信息 + * + * @param parameters 参与签名构建的参数信息 + * @param accessKeySecret 加密签名字符串和服务器端验证签名字符串的密钥 + * @param domain 产品域名 + * @return 返回构建的签名、参数、请求地址 + * { + * "signature":"构建的签名信息", + * "parameters":"请求参数信息", + * "requestUrl":"最终拼接的url地址" + * } + */ + public static JSONObject getAliYunSignature(Map parameters, String accessKeySecret, String domain) { + + // 过滤Map中为空的参数 + parameters = MapUtil.filter(parameters, (Filter>) o -> StrUtil.isNotBlank(o.getValue())); + + try { + // 构造待签名的字符串 + Iterator it = parameters.keySet().iterator(); + StringBuilder sortQueryStringTmp = new StringBuilder(); + while (it.hasNext()) { + String key = it.next(); + sortQueryStringTmp.append("&").append(specialUrlEncode(key)).append("=").append(specialUrlEncode(parameters.get(key))); + } + // 构建签名字符串 + String stringToSign = "GET&" + specialUrlEncode("/") + "&" + specialUrlEncode(sortQueryStringTmp.substring(1)); + // 构建签名 + Mac mac = Mac.getInstance("HmacSHA1"); + mac.init(new SecretKeySpec(StrUtil.format("{}&", accessKeySecret).getBytes(StandardCharsets.UTF_8), "HmacSHA1")); + byte[] signData = mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8)); + String sign = new sun.misc.BASE64Encoder().encode(signData); + // 最终构建的签名数据 + String signature = specialUrlEncode(sign); + JSONObject jsonObject = new JSONObject(); + // 签名最后也要做特殊URL编码 + jsonObject.set("signature", signature); + jsonObject.set("parameters", sortQueryStringTmp); + jsonObject.set("requestUrl", StrUtil.format("http://{}/?Signature={}{}", domain, signature, sortQueryStringTmp)); + return jsonObject; + } catch (Exception e) { + e.printStackTrace(); + return new JSONObject(); + } + } + + /** + * 构造待签名的请求串 + * 一个特殊的URL编码这个是POP特殊的一种规则 + * 即在一般的URLEncode后再增加三种字符替换:加号 (+)替换成 %20、星号 (*)替换成 %2A、 %7E 替换回波浪号 (~)参考代码如下 + * + * @param value 需要构造的url值 + * @return 返回构造结果 + * @throws Exception 抛出异常 + */ + private static String specialUrlEncode(String value) throws Exception { + return URLEncoder.encode(value, "UTF-8").replace("+", "%20").replace("*", "%2A").replace("%7E", "~"); + } +} diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/TencentCloudSignature.java b/hutool-crypto/src/main/java/cn/hutool/crypto/TencentCloudSignature.java new file mode 100644 index 0000000000..75b20ea684 --- /dev/null +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/TencentCloudSignature.java @@ -0,0 +1,91 @@ +package cn.hutool.crypto; + +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import javax.xml.bind.DatatypeConverter; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Map; +import java.util.TimeZone; + +/** + *

构建请求 腾讯云API 时需要用到的签名

+ *

2020-09-17 20:53

+ * + * @author Dan + **/ +public class TencentCloudSignature { + + /** + * 生成腾讯云API请求签名信息 + * + * @param parameters 参与签名构建的参数信息 + * @param endpoint 请求域名信息 [例: sms.tencentcloudapi.com] + * @param secretId 腾讯云账户密钥对secretId + * @param secretKey 腾讯云账户密钥对secretKey + * @return 返回构建的签名、参数、时间戳 + * { + * "signature":"构建的签名信息", + * "timestamp":"生成签名时的时间秒数" + * } + */ + public static JSONObject getTencentCloudSignature(Map parameters, String endpoint, String secretId, String secretKey) { + // 拼接规范请求串 + String canonicalRequest = StrUtil.format("POST\n/\n\n{}\ncontent-type;host\n{}", + StrUtil.format("content-type:application/json; charset=utf-8\nhost:{}\n", endpoint), + sha256Hex(JSONUtil.toJsonStr(parameters))); + + // 计算时间 + String timestamp = String.valueOf(DateUtil.currentSeconds()); + String date = DateUtil.format(new DateTime(TimeZone.getTimeZone("UTC")), "yyyy-MM-dd"); + String credentialScope = StrUtil.format("{}/{}/tc3_request", date, endpoint.split("\\.")[0]); + String stringToSign = StrUtil.format("TC3-HMAC-SHA256\n{}\n{}\n{}", + timestamp, credentialScope, + sha256Hex(canonicalRequest)); + + // 计算签名 + byte[] secretDate = hmac256(("TC3" + secretKey).getBytes(StandardCharsets.UTF_8), date); + byte[] secretService = hmac256(secretDate, endpoint.split("\\.")[0]); + byte[] secretSigning = hmac256(secretService, "tc3_request"); + String authorization = StrUtil.format("TC3-HMAC-SHA256 Credential={}/{}, SignedHeaders=content-type;host, Signature={}", + secretId, credentialScope, + DatatypeConverter.printHexBinary(hmac256(secretSigning, stringToSign)).toLowerCase()); + + // 构建返回信息 + JSONObject jsonObject = new JSONObject(); + jsonObject.set("signature", authorization); + jsonObject.set("timestamp", timestamp); + return jsonObject; + } + + private static byte[] hmac256(byte[] key, String msg) { + try { + Mac mac = Mac.getInstance("HmacSHA256"); + SecretKeySpec secretKeySpec = new SecretKeySpec(key, mac.getAlgorithm()); + mac.init(secretKeySpec); + return mac.doFinal(msg.getBytes(StandardCharsets.UTF_8)); + } catch (NoSuchAlgorithmException | InvalidKeyException e) { + e.printStackTrace(); + return null; + } + } + + private static String sha256Hex(String s) { + try { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + byte[] d = md.digest(s.getBytes(StandardCharsets.UTF_8)); + return DatatypeConverter.printHexBinary(d).toLowerCase(); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + return ""; + } + } +} -- Gitee From 5b5b271683114416eabb140e3a3ce125011e77de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=88=E4=B9=8B=E7=8D=A0?= <1224073217@qq.com> Date: Thu, 17 Sep 2020 23:04:58 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=98=BF=E9=87=8C?= =?UTF-8?q?=E4=BA=91=E3=80=81=E8=85=BE=E8=AE=AF=E4=BA=91API=E6=97=B6?= =?UTF-8?q?=E7=9A=84=E8=AF=B7=E6=B1=82=E7=AD=BE=E5=90=8D=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../crypto/test/AliYunSignatureTest.java | 102 +++++++++++++++++ .../test/TencentCloudSignatureTest.java | 106 ++++++++++++++++++ 2 files changed, 208 insertions(+) create mode 100644 hutool-crypto/src/test/java/cn/hutool/crypto/test/AliYunSignatureTest.java create mode 100644 hutool-crypto/src/test/java/cn/hutool/crypto/test/TencentCloudSignatureTest.java diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/AliYunSignatureTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/AliYunSignatureTest.java new file mode 100644 index 0000000000..f60bb5e449 --- /dev/null +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/AliYunSignatureTest.java @@ -0,0 +1,102 @@ +package cn.hutool.crypto.test; + +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.lang.Console; +import cn.hutool.core.util.IdUtil; +import cn.hutool.crypto.AliYunSignature; +import cn.hutool.json.JSONObject; +import org.junit.Test; + +import java.util.Map; +import java.util.TimeZone; +import java.util.TreeMap; + +/** + *

阿里云API签名测试

+ *

获取ID、SECRET: https://ram.console.aliyun.com/manage/ak?spm=5176.12818093.nav-right.dak.488716d00nyfCh

+ *

2020-09-17 21:07

+ * + * @author Dan + **/ +public class AliYunSignatureTest { + + /** + * 访问密钥ID 用于调用API + */ + private static final String ACCESSKEY_ID = ""; + + /** + * 密钥 + */ + private static final String ACCESSKEY_SECRET = ""; + + /** + * 短信发送 + */ + @Test + public void sendSms() { + JSONObject templateParam = new JSONObject(); + templateParam.set("code", "123456"); + templateParam.set("time", "5"); + // 构建请求参数 + Map parameters = AliYunSignature.signatureParameterInitialization(ACCESSKEY_ID); + // 第二部分由业务API参数组成 + parameters.put("Action", "SendSms"); + parameters.put("Version", "2017-05-25"); + parameters.put("RegionId", "cn-hangzhou"); + parameters.put("PhoneNumbers", "+8613844444444"); + parameters.put("SignName", "hutool"); + parameters.put("TemplateCode", "SMS_199222222"); + parameters.put("TemplateParam", templateParam.toString()); + parameters.put("OutId", ""); + parameters.put("SmsUpExtendCode", ""); + Console.log(AliYunSignature.getAliYunSignature(parameters, ACCESSKEY_SECRET, "dysmsapi.aliyuncs.com")); + } + + /** + * 获取云服务器信息 + */ + @Test + public void getEcs() { + Map parameters = new TreeMap() {{ + // 第一部分由系统参数组成 + put("SignatureMethod", "HMAC-SHA1"); + put("SignatureNonce", IdUtil.objectId()); + put("AccessKeyId", ACCESSKEY_ID); + put("SignatureVersion", "1.0"); + put("Timestamp", DateUtil.format(new DateTime(TimeZone.getTimeZone("GMT+:08:00")), "yyyy-MM-dd'T'HH:mm:ss'Z'")); + put("Format", "json"); + // 第二部分由业务API参数组成 + put("Action", "DescribeInstances"); + put("Version", "2014-05-26"); + put("RegionId", "cn-hangzhou"); + }}; + Console.log(AliYunSignature.getAliYunSignature(parameters, ACCESSKEY_SECRET, "ecs.aliyuncs.com")); + } + + /** + * 设置安全组 + */ + @Test + public void setSecurityGroup() { + Map parameters = new TreeMap() {{ + // 第一部分由系统参数组成 + put("SignatureMethod", "HMAC-SHA1"); + put("SignatureNonce", IdUtil.objectId()); + put("AccessKeyId", ACCESSKEY_ID); + put("SignatureVersion", "1.0"); + put("Timestamp", DateUtil.format(new DateTime(TimeZone.getTimeZone("GMT+:08:00")), "yyyy-MM-dd'T'HH:mm:ss'Z'")); + put("Format", "json"); + // 第二部分由业务API参数组成 + put("Action", "AuthorizeSecurityGroup"); + put("Version", "2014-05-26"); + put("IpProtocol", "tcp"); + put("PortRange","8000/9600"); + put("RegionId","cn-hangzhou"); + put("SecurityGroupId","sg-bp17zst8hsu4rpwy2l37"); + put("SourceCidrIp","0.0.0.0/0"); + }}; + Console.log(AliYunSignature.getAliYunSignature(parameters, ACCESSKEY_SECRET, "ecs.aliyuncs.com")); + } +} diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/TencentCloudSignatureTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/TencentCloudSignatureTest.java new file mode 100644 index 0000000000..db55047e2c --- /dev/null +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/TencentCloudSignatureTest.java @@ -0,0 +1,106 @@ +package cn.hutool.crypto.test; + +import cn.hutool.core.lang.Console; +import cn.hutool.crypto.TencentCloudSignature; +import cn.hutool.json.JSONArray; +import cn.hutool.json.JSONObject; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +/** + *

腾讯云API签名测试

+ *

2020-09-17 21:55

+ * + * @author Dan + **/ +public class TencentCloudSignatureTest { + + /** + * 访问密钥ID 用于调用API + */ + private static final String SECRET_ID = ""; + + /** + * 密钥 + */ + private static final String SECRET_KEY = ""; + + /** + * 短信发送 + */ + @Test + public void sendSms() { + // 需要发送的电话(需要+86) + JSONArray phoneNumberSet = new JSONArray(); + phoneNumberSet.add("+8613844444444"); + phoneNumberSet.add("+8613855555555"); + + // 模板参数 + JSONArray templateParam = new JSONArray(); + templateParam.add("123456"); + templateParam.add("3"); + + // 构建请求参数 + Map parameters = new HashMap(8) {{ + put("PhoneNumberSet", phoneNumberSet); + put("TemplateID", "777777"); + put("TemplateParamSet", templateParam); + put("SmsSdkAppid", "4444444444"); + put("Sign", "hutool"); + put("ExtendCode", ""); + put("SessionContext", ""); + put("SenderId", ""); + }}; + + // 构建签名信息 + JSONObject signature = TencentCloudSignature.getTencentCloudSignature(parameters, "sms.tencentcloudapi.com", SECRET_ID, SECRET_KEY); + Console.log(signature); + + // 构建链式请求 +// String result = HttpRequest.post("https://sms.tencentcloudapi.com/") +// .header("Content-Type", "application/json; charset=utf-8") +// .header("Host", "sms.tencentcloudapi.com") +// .header("Authorization", signature.getStr("signature")) +// .header("X-TC-Action", "SendSms") +// .header("X-TC-Timestamp", signature.getStr("timestamp")) +// .header("X-TC-Version", "2019-07-11") +// .header("X-TC-RequestClient", "SDK_JAVA_3.1.130") +// .header("X-TC-Region", "") +// .body(JSONUtil.toJsonStr(parameters).getBytes(StandardCharsets.UTF_8)) +// .timeout(-1).execute().body(); +// Console.log(result); + } + + /** + * 获取云服务器信息 + */ + @Test + public void getCvm() { + + // 构建请求参数 + Map parameters = new HashMap(2) {{ + // 不是必须参数 + put("Offset",0); + put("Limit",20); + }}; + + // 构建签名信息 + JSONObject signature = TencentCloudSignature.getTencentCloudSignature(parameters, "cvm.tencentcloudapi.com", SECRET_ID, SECRET_KEY); + Console.log(signature); + +// String result = HttpRequest.post("https://sms.tencentcloudapi.com/") +// .header("Content-Type", "application/json; charset=utf-8") +// .header("Host", "cvm.tencentcloudapi.com") +// .header("Authorization", signature.getStr("signature")) +// .header("X-TC-Action", "DescribeInstances") +// .header("X-TC-Timestamp", signature.getStr("timestamp")) +// .header("X-TC-Version", "2017-03-12") +// .header("X-TC-RequestClient", "SDK_JAVA_3.1.130") +// .header("X-TC-Region", "ap-shanghai") +// .body(JSONUtil.toJsonStr(parameters).getBytes(StandardCharsets.UTF_8)) +// .timeout(-1).execute().body(); +// Console.log(result); + } +} -- Gitee From f2eea324a0fe7306025b60dc337c5a2e76d8de41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=88=E4=B9=8B=E7=8D=A0?= <1224073217@qq.com> Date: Thu, 17 Sep 2020 23:06:46 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=98=BF=E9=87=8C?= =?UTF-8?q?=E4=BA=91=E3=80=81=E8=85=BE=E8=AE=AF=E4=BA=91API=E6=97=B6?= =?UTF-8?q?=E7=9A=84=E8=AF=B7=E6=B1=82=E7=AD=BE=E5=90=8D=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hutool-crypto/pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 46cb014300..6b6883aba3 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -27,6 +27,11 @@ hutool-core ${project.parent.version} + + cn.hutool + hutool-json + ${project.parent.version} + org.bouncycastle bcprov-jdk15to18 -- Gitee