1、ws.config签名 调用ticket等获取ws.config的签名,下面会调用方法再调用方法时需要再次按照调用方法的签名
wx.config({
debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: '', // 必填,公众号的唯一标识
timestamp: , // 必填,生成签名的时间戳
nonceStr: '', // 必填,生成签名的随机串
signature: '',// 必填,签名
jsApiList: [] // 必填,需要使用的JS接口列表
});
jsapi_ticket
生成签名之前必须先了解一下jsapi_ticket,jsapi_ticket是公众号用于调用微信JS接口的临时票据。正常情况下,jsapi_ticket的有效期为7200秒,通过access_token来获取。由于获取jsapi_ticket的api调用次数非常有限,频繁刷新jsapi_ticket会导致api调用受限,影响自身业务,开发者必须在自己的服务全局缓存jsapi_ticket 。
1、获取access_token(有效期7200秒,开发者必须在自己的服务全局缓存access_token):
http请求方式: GET
https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=appID&secret=appsecret
2、用第一步拿到的access_token 采用http GET方式请求获得jsapi_ticket(有效期7200秒,api 调用次数非常有限,频繁刷新api_ticket 会导致api调用受限,影响自身业务,开发者必须在自己的服务全局缓存jsapi_ticket):https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi
成功返回如下JSON:
{
"errcode":0,
"errmsg":"ok",
"ticket":"bxLdikRXVbTPdHSM05e5u5sUoXNKd8-41ZO3MhKoyN5OfkWITDGgnr2fwJ0m9E8NYzWKVZvdVtaUgWvsdshFKA",
"expires_in":7200
}
1
2
3
4
5
6
https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=27_1JoR00pdKD26t0IbR_kPzC5FbCChBLVmoRTkJPQ6b3SbHO2D-IfeaCe1-iBI-kFCjZ58QCSffv9IEVhv0PfmfCsT4ZAEDNcwfO8zYEtB05SOM-mY8pspfKJsz_V8LJnqhMWJO-R9ymZhBj00UWRdACAIKF&type=jsapi
获得jsapi_ticket之后,就可以生成JS-SDK权限验证的签名了。
https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=wx954f1899ef15d2ea&secret=9af198bddb8db015e9113ed7379cbcdf
微信 JS 接口签名校验工具:
https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=jsapisign
微信支付接口签名校验工具
https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=20_
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="https://res.wx.qq.com/open/js/jweixin-1.5.0.js"></script> <script src="jquery-3.4.1.min.js"></script> <script src="vconsole.min.js"></script> <script type="text/javascript"> var vConsole = new VConsole(); window.vConsole = new window.VConsole(); var appidG = ""; var timestampG = ""; var nonceStrG = ""; var signatureG = ""; $(function () { var signUrl = window.location.href.split('#')[0]; console.log("signUrl:" + signUrl); $.ajax({ url: "/index/test", method: "post", data: { signUrl: signUrl }, success: function (data) { console.log("data:" + data); var dataJson = JSON.parse(data); // console.log("dataJson.appid:"+dataJson.appid); console.log("wx.config() ---> 接收后台返回的参数"); appidG = dataJson.appid; timestampG = dataJson.timestamp; nonceStrG = dataJson.nonceStr; signatureG = dataJson.signature; wx.config({ debug: true, appId: dataJson.appid, timestamp: dataJson.timestamp, nonceStr: dataJson.nonceStr, signature: dataJson.signature, jsApiList: ['onMenuShareAppMessage', 'openBusinessView'] }) } }); }); function getLocation() { wx.ready(function () { wx.getLocation({ type: 'gcj02', // 默认为wgs84的gps坐标,如果要返回直接给openLocation用的火星坐标,可传入'gcj02' success: function (res) { var latitude = res.latitude; // 纬度,浮点数,范围为90 ~ -90 var longitude = res.longitude; // 经度,浮点数,范围为180 ~ -180。 var speed = res.speed; // 速度,以米/每秒计 var accuracy = res.accuracy; // 位置精度 alert('纬度:' + latitude + '------经度:' + longitude) }, fail: function (res) { console.log('未开启定位功能'); }, cancel: function (res) { console.log('用户拒绝授权获取地理位置'); } }); }); } /** * 跳转微信支付分 */ function goToWXScore() { console.log("appidG:" + appidG); console.log("timestampG:" + timestampG); console.log("nonceStrG:" + nonceStrG); console.log("signatureG:" + signatureG); $.ajax({ url: "/index/generateSignature", method: "post", data: { timestamp: timestampG, nonce_str:nonceStrG }, success: function (sign) { console.log("sign:" + sign); wx.ready(function () { wx.checkJsApi({ jsApiList: ['openBusinessView'], // 需要检测的JS接口列表 success: function (res) { // 以键值对的形式返回,可用的api值true,不可用为false // 如:{"checkResult":{"openBusinessView":true},"errMsg":"checkJsApi:ok"} if (res.checkResult.openBusinessView) { wx.invoke( 'openBusinessView', { businessType: 'wxpayScoreEnable', queryString: "mch_id=1518750531&service_id=00004000000000704283351234894845&out_request_no=1234323JKHDFE1243259×tamp=" + timestampG + "" + "&nonce_str=" + nonceStrG + "&sign_type=HMAC-SHA256&sign=" + sign + "" }, function (res) { // 从微信侧小程序返回时会执行这个回调函数 if (parseInt(res.err_code) === 0) { s // 返回成功 } else { // 返回失败 } }); } } }); }); } }); } </script> </head> <body> <div> <!--<button id="snap1" onclick="goToWXScore()">goToWXScore</button>--> <button id="pay" onclick="goToWXScore()" value="跳转支付分">跳转支付分</button> <button id="snap2" onclick="getLocation()" value="获取地区">获取地区</button> <!--<button id="snap3" onclick="onBridgeReady()">支付</button>--> </div> </body> </html>
后端接口
@ResponseBody @RequestMapping("/test") @Login(required = false) public String test(String signUrl) { System.out.println("signUrl:"+signUrl); //String res = HttpGetMethod.doGet("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=wx954f1899ef15d2ea&secret=acb8ad2a46765766c7881ea37237c1c7", "utf8", null); String jsapi_ticket = "HoagFKDcsGMVCIY2vOjf9kjcHCRqLoF8k77O2dyyZiMTaOwxLTQ9nHU3fa4jfiEj2XW4NsvQG53gZZgVMkQdJA"; //String url = "http://localhost/h5/weixinexer.html"; Map<String, String> ret = com.jd.mrd.m.nc.web.utils.Sign.sign(jsapi_ticket, signUrl); ret.put("appid","wx954f1899ef15d2ea"); for (Map.Entry entry : ret.entrySet()) { System.out.println(entry.getKey() + ", " + entry.getValue()); } return JSON.toJSONString(ret); } /** * 获取签名 * @param timestamp * @param nonce_str * @return * @throws Exception */ @ResponseBody @RequestMapping("/generateSignature") @Login(required = false) public String generateSignature(String timestamp,String nonce_str) throws Exception { HashMap<String, String> stringStringHashMap = new HashMap<String, String>(); stringStringHashMap.put("mch_id","1518750531"); stringStringHashMap.put("service_id","00004000000000704283351234894845"); stringStringHashMap.put("out_request_no","1234323JKHDFE1243259"); stringStringHashMap.put("timestamp",timestamp); stringStringHashMap.put("nonce_str",nonce_str); stringStringHashMap.put("sign_type","HMAC-SHA256"); String signature = WeChatUtils.generateSignature(stringStringHashMap, "acb8ad2a46765766c7881ea37237c1c7", WeChatConstant.SignType.HMACSHA256); return signature; } @RequestMapping(value = "generateImage") @ResponseBody @Login(required = false) public String generateImage(HttpServletRequest request, String base64) throws IOException { //String base64=request.getParameter("base64");; File directory = new File("");//设定为当前文件夹 URL resource = IndexController.class.getResource("/"); String clasFilePath = resource.getPath(); System.out.println(clasFilePath);//获取标准的路径 File file = new File(clasFilePath); String strParentDirectory = file.getParent(); System.out.println(strParentDirectory); file = new File(strParentDirectory); strParentDirectory = file.getParent(); System.out.println(strParentDirectory); strParentDirectory = strParentDirectory + "/h5/img"; System.out.println(strParentDirectory); String fileName = CommonUtils.generateUUID() + ".jpg"; String fileUrl = strParentDirectory + "/" + fileName; String imgStr = base64.split(",")[1]; Base64Utils.GenerateImage(imgStr, fileUrl); System.out.println(fileUrl); System.out.println(request.getServerName() + "/h5/img/" + fileName); return request.getServerName() + "/h5/img/" + fileName; // JSONObject json =new JSONObject(); // json.put("result",fileUrl); // response.setCharacterEncoding("utf-8"); // response.setContentType("application/json;charset=utf-8"); // PrintWriter out = null; // out = response.getWriter(); // out.write(json.toString()); // return json.toJSONString(); } @RequestMapping(value = "testPost") @ResponseBody @Login(required = false) public String testPost(HttpServletRequest request, String base64) { return ""; }
package com.jd.lestore.payment.common.utils; import com.jd.lestore.payment.common.constant.WeChatConstant; import com.jd.lestore.payment.common.constant.WeChatConstant.SignType; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.StringWriter; import java.security.MessageDigest; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Set; public class WeChatUtils { /** * 生成新的请求序列号 * * @return */ public static Integer getNewRequestSerial() { String timestamp = String.valueOf( System.currentTimeMillis()/1000 ); return Integer.valueOf(timestamp).intValue(); } public static String generateSignature(final Map<String, String> data, String key) throws Exception { return generateSignature(data, key, SignType.MD5); } public static String generateHMACSHA256Signature(final Map<String, String> data, String key) throws Exception { return generateSignature(data, key, SignType.HMACSHA256); } /** * 生成签名 * * @param data * @param key * @return * @throws Exception 规则 * 第一步,设所有发送或者接收到的数据为集合M,将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA。 * <p> * 特别注意以下重要规则: * <p> * ◆ 参数名ASCII码从小到大排序(字典序); * ◆ 如果参数的值为空不参与签名; * ◆ 参数名区分大小写; * ◆ 验证调用返回或微信主动通知签名时,传送的sign参数不参与签名,将生成的签名与该sign值作校验。 * ◆ 微信接口可能增加字段,验证签名时必须支持增加的扩展字段 * <p> * 第二步,在stringA最后拼接上key得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为大写,得到sign值signValue。 */ public static String generateSignature(final Map<String, String> data, String key, SignType signType) throws Exception { Set<String> keySet = data.keySet(); String[] keyArray = keySet.toArray(new String[keySet.size()]); Arrays.sort(keyArray); StringBuilder sb = new StringBuilder(); for (String k : keyArray) { if (k.equals(WeChatConstant.FIELD_SIGN)) { continue; } if (data.get(k).trim().length() > 0) // 参数值为空,则不参与签名 sb.append(k).append("=").append(data.get(k).trim()).append("&"); } sb.append("key=").append(key); if (SignType.MD5.equals(signType)) { return MD5(sb.toString()).toUpperCase(); } else if (SignType.HMACSHA256.equals(signType)) { System.out.println("HMACSHA256 original text: " + sb.toString()); return HMACSHA256(sb.toString(), key); } else { throw new Exception(String.format("Invalid sign_type: %s", signType)); } } /** * 生成 MD5 * * @param data 待处理数据 * @return MD5结果 */ public static String MD5(String data) throws Exception { MessageDigest md = MessageDigest.getInstance("MD5"); byte[] array = md.digest(data.getBytes("UTF-8")); StringBuilder sb = new StringBuilder(); for (byte item : array) { sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3)); } return sb.toString().toUpperCase(); } /** * 生成 HMACSHA256 * * @param data 待处理数据 * @param key 密钥 * @return 加密结果 * @throws Exception */ public static String HMACSHA256(String data, String key) throws Exception { Mac sha256_HMAC = Mac.getInstance("HmacSHA256"); SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256"); sha256_HMAC.init(secret_key); byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8")); StringBuilder sb = new StringBuilder(); for (byte item : array) { sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3)); } return sb.toString().toUpperCase(); } /** * 获取当前时间戳,单位秒 * * @return */ public static long getCurrentTimestamp() { return System.currentTimeMillis() / 1000; } /** * 获取当前时间戳,单位毫秒 * * @return */ public static long getCurrentTimestampMs() { return System.currentTimeMillis(); } /** * XML格式字符串转换为Map * * @param strXML XML字符串 * @return XML数据转换后的Map * @throws Exception */ public static Map<String, String> xmlToMap(String strXML) throws Exception { InputStream stream = null; try { Map<String, String> data = new HashMap<String, String>(); DocumentBuilderFactory documentBuilderFactory = setWechatPaySafeCode(); DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); stream = new ByteArrayInputStream(strXML.getBytes("UTF-8")); org.w3c.dom.Document doc = documentBuilder.parse(stream); doc.getDocumentElement().normalize(); NodeList nodeList = doc.getDocumentElement().getChildNodes(); for (int idx = 0; idx < nodeList.getLength(); ++idx) { Node node = nodeList.item(idx); if (node.getNodeType() == Node.ELEMENT_NODE) { org.w3c.dom.Element element = (org.w3c.dom.Element) node; data.put(element.getNodeName(), element.getTextContent()); } } return data; } finally { try { stream.close(); } catch (Exception ex) { ex.printStackTrace(); } } } private static DocumentBuilderFactory setWechatPaySafeCode( ) { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); // 禁 用 DOCTYPE try{ dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); //该 feature 的作用是配置是否包含外部的参数,包括外部 DTD 子集,设置false 禁用参数实体 dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); //该 feature 的功能指是否包含外部生成的实体,设置 false 禁用外部实体 dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); dbf.setXIncludeAware(false); dbf.setExpandEntityReferences(false); }catch ( Exception e ){ e.printStackTrace(); } return dbf; } /** * XML格式字符串转换为Map * * @param strXML XML字符串 * @return XML数据转换后的Map * @throws Exception */ public static Map<String, String> xmlClildToMap(String strXML) throws Exception { InputStream stream = null; try { Map<String, String> data = new HashMap<String, String>(); DocumentBuilderFactory documentBuilderFactory = setWechatPaySafeCode(); DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); stream = new ByteArrayInputStream(strXML.getBytes("UTF-8")); org.w3c.dom.Document doc = documentBuilder.parse(stream); doc.getDocumentElement().normalize(); NodeList nodeList = doc.getDocumentElement().getChildNodes(); for (int idx = 0; idx < nodeList.getLength(); ++idx) { Node node = nodeList.item(idx); if (node.getNodeType() == Node.ELEMENT_NODE) { org.w3c.dom.Element element = (org.w3c.dom.Element) node; if(element.hasChildNodes()){//对于三级子项处理 for(int i = 0 ; i < element.getChildNodes().getLength();i++){ Node nodeChild = element.getChildNodes().item(i); data.put(nodeChild.getNodeName(),nodeChild.getTextContent()); } }else { data.put(element.getNodeName(), element.getTextContent()); } } } return data; } finally { try { stream.close(); } catch (Exception ex) { ex.printStackTrace(); } } } /** * 将Map转换为XML格式的字符串 * * @param data Map类型数据 * @return XML格式的字符串 * @throws Exception */ public static String mapToXml(Map<String, String> data) throws Exception { DocumentBuilderFactory documentBuilderFactory = setWechatPaySafeCode(); DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); org.w3c.dom.Document document = documentBuilder.newDocument(); org.w3c.dom.Element root = document.createElement("xml"); document.appendChild(root); for (String key : data.keySet()) { String value = data.get(key); if (value == null) { value = ""; } value = value.trim(); org.w3c.dom.Element filed = document.createElement(key); filed.appendChild(document.createTextNode(value)); root.appendChild(filed); } TransformerFactory tf = TransformerFactory.newInstance(); Transformer transformer = tf.newTransformer(); DOMSource source = new DOMSource(document); transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); StringWriter writer = new StringWriter(); StreamResult result = new StreamResult(writer); transformer.transform(source, result); String output = writer.getBuffer().toString(); //.replaceAll("\n|\r", ""); try { writer.close(); } catch (Exception ex) { ex.printStackTrace(); } return output; } /** * 将Map转换为XML格式的字符串 * * @param data Map类型数据 * @return XML格式的字符串 * @throws Exception */ public static String mapToXmlROOT(Map<String, String> data,String xmlRoot) throws Exception { DocumentBuilderFactory documentBuilderFactory = setWechatPaySafeCode(); DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); org.w3c.dom.Document document = documentBuilder.newDocument(); document.setXmlStandalone(true); org.w3c.dom.Element root = document.createElement(xmlRoot); document.appendChild(root); for (String key : data.keySet()) { String value = data.get(key); if (value == null) { value = ""; } value = value.trim(); org.w3c.dom.Element filed = document.createElement(key); filed.appendChild(document.createTextNode(value)); root.appendChild(filed); } TransformerFactory tf = TransformerFactory.newInstance(); Transformer transformer = tf.newTransformer(); DOMSource source = new DOMSource(document); transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); StringWriter writer = new StringWriter(); StreamResult result = new StreamResult(writer); transformer.transform(source, result); String output = writer.getBuffer().toString(); //.replaceAll("\n|\r", ""); try { writer.close(); } catch (Exception ex) { ex.printStackTrace(); } return output; } }