前言:在大一期末的时候,想着把短信认证添加到自己的期末作业里。起初,短信接口在javaee中用的很顺利。但是最后在Android中却一直跑不起来,无奈之下只能放弃。在暑假里自己抽空研究了下,同时也咨询了阿里的客服,最终还是完成了在Android上实现了阿里的短信服务。
问题产生:我们都知道Android是用java开发的,既然阿里提供了java的SDK,那么我们是否能在Android上使用呢?
08-08 20:05:41.584 13676-13676/com.example.xhy.alismsdemo W/System.err: com.aliyuncs.exceptions.ClientException: SignatureDoesNotMatch : Specified signature is not matched with our calculation. server string to sign is...
08-08 20:05:41.584 13676-13676/com.example.xhy.alismsdemo W/System.err: RequestId ...
如果直接调用,就会产生如上的问题,经过个人的查证和老师的咨询,发现应该是安卓环境签名算法的问题。
解决方法:因为这是签名算法的问题,所以我们要从根本上解决这个问题,就只能重写他的签名算法。
在这里,感谢阿里的工单工程师,提供了HTTP签名算法。(https://help.aliyun.com/document_detail/56189.html)
附上源码:
public class SignDemo {
public static void main(String[] args) throws Exception {
String accessKeyId = "testId";
String accessSecret = "testSecret";
java.text.SimpleDateFormat df = new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
df.setTimeZone(new java.util.SimpleTimeZone(0, "GMT"));// 这里一定要设置GMT时区
java.util.Map<String, String> paras = new java.util.HashMap<String, String>();
// 1. 系统参数
paras.put("SignatureMethod", "HMAC-SHA1");
paras.put("SignatureNonce", java.util.UUID.randomUUID().toString());
paras.put("AccessKeyId", accessKeyId);
paras.put("SignatureVersion", "1.0");
paras.put("Timestamp", df.format(new java.util.Date()));
paras.put("Format", "XML");
// 2. 业务API参数
paras.put("Action", "SendSms");
paras.put("Version", "2017-05-25");
paras.put("RegionId", "cn-hangzhou");
paras.put("PhoneNumbers", "15300000001");
paras.put("SignName", "阿里云短信测试专用");
paras.put("TemplateParam", "{\"customer\":\"test\"}");
paras.put("TemplateCode", "SMS_71390007");
paras.put("OutId", "123");
// 3. 去除签名关键字Key
if (paras.containsKey("Signature"))
paras.remove("Signature");
// 4. 参数KEY排序
java.util.TreeMap<String, String> sortParas = new java.util.TreeMap<String, String>();
sortParas.putAll(paras);
// 5. 构造待签名的字符串
java.util.Iterator<String> it = sortParas.keySet().iterator();
StringBuilder sortQueryStringTmp = new StringBuilder();
while (it.hasNext()) {
String key = it.next();
sortQueryStringTmp.append("&").append(specialUrlEncode(key)).append("=").append(specialUrlEncode(paras.get(key)));
}
String sortedQueryString = sortQueryStringTmp.substring(1);// 去除第一个多余的&符号
StringBuilder stringToSign = new StringBuilder();
stringToSign.append("GET").append("&");
stringToSign.append(specialUrlEncode("/")).append("&");
stringToSign.append(specialUrlEncode(sortedQueryString));
String sign = sign(accessSecret + "&", stringToSign.toString());
// 6. 签名最后也要做特殊URL编码
String signature = specialUrlEncode(sign);
System.out.println(paras.get("SignatureNonce"));
System.out.println("\r\n=========\r\n");
System.out.println(paras.get("Timestamp"));
System.out.println("\r\n=========\r\n");
System.out.println(sortedQueryString);
System.out.println("\r\n=========\r\n");
System.out.println(stringToSign.toString());
System.out.println("\r\n=========\r\n");
System.out.println(sign);
System.out.println("\r\n=========\r\n");
System.out.println(signature);
System.out.println("\r\n=========\r\n");
// 最终打印出合法GET请求的URL
System.out.println("http://dysmsapi.aliyuncs.com/?Signature=" + signature + sortQueryStringTmp);
}
public static String specialUrlEncode(String value) throws Exception {
return java.net.URLEncoder.encode(value, "UTF-8").replace("+", "%20").replace("*", "%2A").replace("%7E", "~");
}
public static String sign(String accessSecret, String stringToSign) throws Exception {
javax.crypto.Mac mac = javax.crypto.Mac.getInstance("HmacSHA1");
mac.init(new javax.crypto.spec.SecretKeySpec(accessSecret.getBytes("UTF-8"), "HmacSHA1"));
byte[] signData = mac.doFinal(stringToSign.getBytes("UTF-8"));
return new sun.misc.BASE64Encoder().encode(signData);
}
}
不难看出,只要运行了这段代码,我们就能得到完整的GET请求HTTP。
因此,只要再调用URL请求,我们就可以发送我们的短信啦。
在这里,给出我的代码
package com.example.xhy.alismsdemo;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class MainALiSms {
public static void SendSms(String phone, String code) throws Exception {
String accessKeyId = "你的KeyId"; //这里要填写自己的KeyId
String accessSecret = "你的Secret"; //这里要填写自己的Secret
java.text.SimpleDateFormat df = new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
df.setTimeZone(new java.util.SimpleTimeZone(0, "GMT"));
java.util.Map<String, String> paras = new java.util.HashMap<String, String>();
paras.put("SignatureMethod", "HMAC-SHA1");
paras.put("SignatureNonce", java.util.UUID.randomUUID().toString());
paras.put("AccessKeyId", accessKeyId);
paras.put("SignatureVersion", "1.0");
paras.put("Timestamp", df.format(new java.util.Date()));
paras.put("Format", "XML");
paras.put("Action", "SendSms");
paras.put("Version", "2017-05-25");
paras.put("RegionId", "cn-hangzhou");
paras.put("PhoneNumbers", phone);
paras.put("SignName", "你的SignName"); //这里要填写自己的SignName
paras.put("TemplateParam", "{\"code\":\"" + code + "\"}");//这里根据具体情况而定
paras.put("TemplateCode", "你的TemplateCode"); //这里要填写自己的TemplateCode
paras.put("OutId", "123");
if (paras.containsKey("Signature"))
paras.remove("Signature");
java.util.TreeMap<String, String> sortParas = new java.util.TreeMap<String, String>();
sortParas.putAll(paras);
java.util.Iterator<String> it = sortParas.keySet().iterator();
StringBuilder sortQueryStringTmp = new StringBuilder();
while (it.hasNext()) {
String key = it.next();
sortQueryStringTmp.append("&").append(specialUrlEncode(key)).append("=").append(specialUrlEncode(paras.get(key)));
}
String sortedQueryString = sortQueryStringTmp.substring(1);
StringBuilder stringToSign = new StringBuilder();
stringToSign.append("GET").append("&");
stringToSign.append(specialUrlEncode("/")).append("&");
stringToSign.append(specialUrlEncode(sortedQueryString));
String sign = sign(accessSecret + "&", stringToSign.toString());
String signature = specialUrlEncode(sign);
System.out.println(paras.get("SignatureNonce"));
System.out.println("\r\n=========\r\n");
System.out.println(paras.get("Timestamp"));
System.out.println("\r\n=========\r\n");
System.out.println(sortedQueryString);
System.out.println("\r\n=========\r\n");
System.out.println(stringToSign.toString());
System.out.println("\r\n=========\r\n");
System.out.println(sign);
System.out.println("\r\n=========\r\n");
System.out.println(signature);
System.out.println("\r\n=========\r\n");
System.out.println("http://dysmsapi.aliyuncs.com/?Signature=" + signature + sortQueryStringTmp);
String u = "http://dysmsapi.aliyuncs.com/?Signature=" + signature + sortQueryStringTmp;
System.out.println(u);
try {
URL url = new URL(u);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.connect();
BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
String line;
StringBuilder sb = new StringBuilder();
while ((line = br.readLine()) != null) {
sb.append(line);
}
br.close();
connection.disconnect();
System.out.println(sb.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
public static String specialUrlEncode(String value) throws Exception {
return java.net.URLEncoder.encode(value, "UTF-8").replace("+", "%20").replace("*", "%2A").replace("%7E", "~");
}
public static String sign(String accessSecret, String stringToSign) throws Exception {
javax.crypto.Mac mac = javax.crypto.Mac.getInstance("HmacSHA1");
mac.init(new javax.crypto.spec.SecretKeySpec(accessSecret.getBytes("UTF-8"), "HmacSHA1"));
byte[] signData = mac.doFinal(stringToSign.getBytes("UTF-8"));
return new sun.misc.BASE64Encoder().encode(signData);
}
}
当然,大家也不要忘记把BASE64和两个阿里云提供的jar包导进去。
这样就大功告成啦。
现在想想是不是很简单呢O(∩_∩)O
当然还有种更简单的方法,就是在后端直接调用阿里帮我们打包好的接口。
我的项目Demo:https://download.youkuaiyun.com/download/xhy1999/10598249
需要的jar包:https://download.youkuaiyun.com/download/xhy1999/10591967