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&timestamp=" + 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;
    }

}

 

posted on 2019-11-06 15:45  王洪洪  阅读(2160)  评论(0编辑  收藏  举报