关于公众号跳转小程序的签名问题
官方开发文档https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html#62
首先介绍如何获取签名(signature):
首先需要jsapi_ticket,而获取jsapi_ticket需要使用到access_token,获取access_token需要用到公众号的APPID和APPSECRET。
签名算法
签名生成规则**(签名生成规则的代码在下面)**如下:参与签名的字段包括noncestr(随机字符串), 有效的jsapi_ticket, timestamp(时间戳), url(当前网页的URL,不包含#及其后面部分) 。对所有待签名参数按照字段名的ASCII 码从小到大排序(字典序)后,使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串string1。这里需要注意的是所有参数名均为小写字符。对string1作sha1加密,字段名和字段值都采用原始值,不进行URL 转义。
即signature=sha1(string1)。 示例:
noncestr=Wm3WZYPz0wzccnW
jsapi_ticket=sM4AOVdWfPE4DxkXGEs8VMCPGGVi4C3VMP37wVUCFvkVAy_90u5h9nbSlYy3-Sl-HhTdfl2fzFy1AOcHKP7qg
timestamp=141458457
url=http://mp.weixin.qq.com?params=value
步骤1. 对所有待签名参数按照字段名的ASCII 码从小到大排序(字典序)后,使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串string1:
jsapi_ticket=sM4AOVdWfPE4DxkXGEs8VMCPGGVi4C3M0P37wVUCFvkVAy_90u5h9nbSlYy3-Sl-HhTdfl2fzFy1AOcHKP7q&noncestr=Wm3WZYTPzwzccnW×tamp=1414587457&url=http://mp.weixin.qq.com?params=value
步骤2. 对string1进行sha1签名,得到signature:0f9de62fce790f9a083d5c99e95740ceb9027ed
直接上代码,在我的苦心摸索下:
获取access_token和jsapi_ticket过程,调用签名生成规则最终的签名(signature):
String nonceStr = StrUtil.uuid();//一个随机数
long timeStampSec = System.currentTimeMillis()/1000;
String timestamp = String.format("%010d", timeStampSec);//时间戳需要用到十位的(也就是到秒)
String token_url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
String requestUrl = token_url.replace("APPID", "wxc35b08974f2aa28b").replace("APPSECRET","a17d1fe4e4b20c88af8ab019cd45e400");
String getTicketUrl = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi";
SmartLogger.info("url",Url);
// 发起GET请求获取凭证
try {
String access_token ="";
access_token = Jboot.getCache().get("access_token", "access_token");
if (access_token=="" || access_token == null ) {
URL url = new URL(requestUrl);
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
// 设置请求方式(GET/POST)
conn.setRequestMethod("GET");
// 从输入流读取返回内容
InputStream inputStream = conn.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String str = null;
StringBuffer buffer = new StringBuffer();
while ((str = bufferedReader.readLine()) != null) {
buffer.append(str);
}
// 释放资源
bufferedReader.close();
inputStreamReader.close();
inputStream.close();
conn.disconnect();
JSONObject jsonObject = JSONObject.fromObject(buffer.toString());
access_token = jsonObject.getString("access_token");
Jboot.getCache().put("access_token", "access_token", access_token,7200);
}
String JsApiTicket ="";
JsApiTicket = Jboot.getCache().get("JsApiTicket", "JsApiTicket");
if (JsApiTicket=="" || JsApiTicket == null) {
String requestGetTicketUrl = getTicketUrl.replace("ACCESS_TOKEN", access_token);
URL url1 = new URL(requestGetTicketUrl);
HttpsURLConnection conn1 = (HttpsURLConnection) url1.openConnection();
// 设置请求方式(GET/POST)
conn1.setRequestMethod("GET");
// 从输入流读取返回内容
InputStream inputStream1 = conn1.getInputStream();
InputStreamReader inputStreamReader1 = new InputStreamReader(inputStream1, "utf-8");
BufferedReader bufferedReader1 = new BufferedReader(inputStreamReader1);
String str1 = null;
StringBuffer buffer1 = new StringBuffer();
while ((str1 = bufferedReader1.readLine()) != null) {
buffer1.append(str1);
}
// 释放资源
bufferedReader1.close();
inputStreamReader1.close();
inputStream1.close();
conn1.disconnect();
JSONObject jsonObject1 = JSONObject.fromObject(buffer1.toString());
JsApiTicket = jsonObject1.getString("ticket");
Jboot.getCache().put("JsApiTicket","JsApiTicket",JsApiTicket,7200);
}
String jsSdkSign = WxJsSignUtil.getJsSdkSign(nonceStr, JsApiTicket, timestamp + "", Url);
SmartLogger.info("url",nonceStr+"======"+jsSdkSign +"======"+timestamp+"======"+JsApiTicket);
renderJson(Ret.ok().set("noncestr",nonceStr).set("signature",jsSdkSign).set("timestamp",timestamp).set("JsApiTicket",JsApiTicket));
}catch (Exception e){
SmartLogger.error("error","公众号跳转小程序签名错误");
renderFailJson();
}
签名生成规则的代码:
/**
* 获取jsSdkSign
* @param noncestr
* @param ticket
* @param timestamp
* @param url
* @return
*/
public static String getJsSdkSign(String noncestr,String ticket,String timestamp,String url){
/* 加密/校验流程如下:
*1. 将token、timestamp、nonce三个参数进行字典序排序
* 2. 将三个参数字符串拼接成一个字符串进行sha1加密
* 3. 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信 */
// 1.排序
String sortString = sort(url, timestamp, ticket, noncestr);
// 2.sha1加密
String signature = sha1(sortString);
return signature;
}
/**
* @Author zhaohp
* @Date 2018/12/10 19:50
* @Param [signature, timestamp, ticket, echostr]
* @Return java.lang.String
* @Description: 对所有待签名参数按照字段名的ASCII 码从小到大排序
*/
public static String sort(String url, String timestamp, String ticket, String echostr) {
return "jsapi_ticket=" + ticket + "&noncestr=" + echostr + "×tamp=" + timestamp + "&url=" + url;
}
/**
* @Author zhaohp
* @Date 2018/12/10 19:51
* @Param [str]
* @Return java.lang.String
* @Description: 将字符串进行sha1加密
*/
public static String sha1(String str) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-1");
digest.update(str.getBytes());
byte messageDigest[] = digest.digest();
// 创建 16进制字符串
StringBuffer hexString = new StringBuffer();
// 字节数组转换为 十六进制 数
for (int i = 0; i < messageDigest.length; i++) {
String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
if (shaHex.length() < 2) {
hexString.append(0);
}
hexString.append(shaHex);
}
return hexString.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return "";
}
值得注意的是:
生成的access_token和jspapi_ticket的有效期是7200秒,需要在你的项目中进行缓存,如果第三方不使用中控服务器,而是选择各个业务逻辑点各自去刷新access_token,那么就可能会产生冲突,导致服务不稳定。
请求的服务器域名需要在微信公众后台中:开发—>基本配置 中设置IP白名单