微信 JS-SDK 簽名驗證

doc: http://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html
demo:http://demo.open.weixin.qq.com/jssdk/
sandbox:http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=jsapisignjavascript

生成簽名以前必須先了解一下jsapi_ticket,jsapi_ticket是公衆號用於調用微信JS接口的臨時票據。正常狀況下,jsapi_ticket的有效期爲7200秒,經過access_token來獲取。因爲獲取jsapi_ticket的api調用次數很是有限,頻繁刷新jsapi_ticket會致使api調用受限,影響自身業務,開發者必須在本身的服務全局緩存jsapi_ticket 。
參考如下文檔獲取access_token(有效期7200秒,開發者必須在本身的服務全局緩存access_token):
用第一步拿到的access_token 採用http GET方式請求得到jsapi_ticket(有效期7200秒,開發者必須在本身的服務全局緩存jsapi_ticket):
https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapihtml

注意事項

1.設置JS接口安全域名
2.簽名用的noncestr和timestamp必須與wx.config中的nonceStr和timestamp相同。
3.簽名用的url必須是調用JS接口頁面的完整URL。
4.出於安全考慮,開發者必須在服務器端實現簽名的邏輯。
java

JAVA實現

public class WxSign {
    @SuppressWarnings({ "unchecked", "unchecked" })
    public static void main(String[] args) throws Exception {
        /*
        String  access_token= WeChat.getAccessToken();
        String jsapi_ticket = WeChat.getJsApiTicket(access_token);
        */
        //System.out.println("access_token : "+access_token+ " jsapi_ticket: " +jsapi_ticket);
        String jsapi_ticket="jsapi_ticket";
        String url = "http://cmsplus.com.cn";
        Map<String, String> ret = sign(jsapi_ticket, url);
        for (Map.Entry entry : ret.entrySet()) {
            //System.out.println(entry.getKey() + "======== " + entry.getValue());
        }
        System.out.println("signature:  "+ret.get("signature") +  ": timestamp " +ret.get("timestamp"));
        System.out.println(createLinkString(ret));
    };

    //對全部待簽名參數按照字段名的ASCII 碼從小到大排序(字典序)後
     /** 
     * 把數組全部元素排序,並按照「參數=參數值」的模式用「&」字符拼接成字符串
     * @param params 須要排序並參與字符拼接的參數組
     * @return 拼接後字符串
     */
    public static String createLinkString(Map<String, String> params) {
        List<String> keys = new ArrayList<String>(params.keySet());
        Collections.sort(keys);
        String prestr = "";
        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            String value = params.get(key);
            if (i == keys.size() - 1) {//拼接時,不包括最後一個&字符
                prestr = prestr + key + "=" + value;
            } else {
                prestr = prestr + key + "=" + value + "&";
            }
        }
        return prestr;
    }
    
    public static Map<String, String> sign(String jsapi_ticket, String url) {
        Map<String, String> ret = new HashMap<String, String>();
        String nonce_str = create_nonce_str();
        String timestamp = create_timestamp();
        String string1;
        String signature = "";
        // 注意這裏參數名必須所有小寫,且必須有序
        string1 = "jsapi_ticket=" + jsapi_ticket + "&noncestr=" + nonce_str + "&timestamp=" + timestamp + "&url=" + url;
        //System.out.println(string1);
        try {
            MessageDigest crypt = MessageDigest.getInstance("SHA-1");
            crypt.reset();
            crypt.update(string1.getBytes("UTF-8"));
            signature = byteToHex(crypt.digest());
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        ret.put("url", url);
        ret.put("jsapi_ticket", jsapi_ticket);
        ret.put("nonceStr", nonce_str);
        ret.put("timestamp", timestamp);
        ret.put("signature", signature);
        return ret;
    }

    private static String byteToHex(final byte[] hash) {
        Formatter formatter = new Formatter();
        for (byte b : hash) {
            formatter.format("%02x", b);
        }
        String result = formatter.toString();
        formatter.close();
        return result;
    }

    private static String create_nonce_str() {
        return UUID.randomUUID().toString();
    }

    private static String create_timestamp() {
        return Long.toString(System.currentTimeMillis() / 1000);
    }
}
public class WeChat {
    private static final String ACCESSTOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential";
    private static final String PAYFEEDBACK_URL = "https://api.weixin.qq.com/payfeedback/update";
    /**
     * 獲取access_token
     * 
     * @return
     * @throws Exception
     */
    public static String getAccessToken() throws Exception {
        String appid = ConfKit.get("AppId");
        String secret = ConfKit.get("AppSecret");
        String jsonStr = HttpKit.get(ACCESSTOKEN_URL.concat("&appid=") + appid + "&secret=" + secret);
        Map<String, Object> map = JSONObject.parseObject(jsonStr);
        return map.get("access_token").toString();
    }
    
    /**
     * 獲取jsapi_ticket
     * 
     * @return
     * @throws Exception
     */
    public static String getJsApiTicket(String accessToken) throws Exception {
        String jsonStr = HttpKit.get("https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token="+accessToken+"&type=jsapi");
        Map<String, Object> map = JSONObject.parseObject(jsonStr);
        return map.get("ticket").toString();
    }

    /**
     * 獲取access_token
     * 
     * @return
     * @throws Exception
     */
    public static String getAccessToken(String appid, String secret) throws Exception {
        String jsonStr = HttpKit.get(ACCESSTOKEN_URL.concat("&appid=") + appid + "&secret=" + secret);
        Map<String, Object> map = JSONObject.parseObject(jsonStr);
        return map.get("access_token").toString();
    }

    /**
     * 支付反饋
     * 
     * @param openid
     * @param feedbackid
     * @return
     * @throws Exception
     */
    public static boolean payfeedback(String openid, String feedbackid) throws Exception {
        Map<String, String> map = new HashMap<String, String>();
        String accessToken = getAccessToken();
        map.put("access_token", accessToken);
        map.put("openid", openid);
        map.put("feedbackid", feedbackid);
        String jsonStr = HttpKit.get(PAYFEEDBACK_URL, map);
        Map<String, Object> jsonMap = JSONObject.parseObject(jsonStr);
        return "0".equals(jsonMap.get("errcode").toString());
    }

    /**
     * 判斷是否來自微信, 5.0 以後的支持微信支付
     * 
     * @param request
     * @return
     */
    public static boolean isWeiXin(HttpServletRequest request) {
        String userAgent = request.getHeader("User-Agent");
        if (StringUtils.isNotBlank(userAgent)) {
            Pattern p = Pattern.compile("MicroMessenger/(\\d+).+");
            Matcher m = p.matcher(userAgent);
            String version = null;
            if (m.find()) {
                version = m.group(1);
            }
            return (null != version && NumberUtils.toInt(version) >= 5);
        }
        return false;
    }

javascriptgit

wx.config({
        debug: false,
        appId: '${appId}',
        timestamp: '${timestamp}',
        nonceStr: '${nonceStr}',
        signature: '${signature}',
        jsApiList: [
        'checkJsApi',
        'onMenuShareTimeline',
        'onMenuShareAppMessage',
        'onMenuShareQQ',
        'onMenuShareWeibo',
        'hideMenuItems',
        'showMenuItems',
        'hideAllNonBaseMenuItem',
        'showAllNonBaseMenuItem',
        'translateVoice',
        'startRecord',
        'stopRecord',
        'onRecordEnd',
        'playVoice',
        'pauseVoice',
        'stopVoice',
        'uploadVoice',
        'downloadVoice',
        'chooseImage',
        'previewImage',
        'uploadImage',
        'downloadImage',
        'getNetworkType',
        'openLocation',
        'getLocation',
        'hideOptionMenu',
        'showOptionMenu',
        'closeWindow',
        'scanQRCode',
        'chooseWXPay',
        'openProductSpecificView',
        'addCard',
        'chooseCard',
        'openCard'
        ]
        });
        wx.ready(function () {
            var shareData = {
                title: '這是活動的介紹頁',
                desc: '這裏是發送給好友的時候的簡介',
                link: 'http://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html',
                imgUrl: 'http://demo.open.weixin.qq.com/jssdk/images/p2166127561.jpg',
            };
             //wx.onMenuShareAppMessage(shareData);
             wx.onMenuShareAppMessage({
                  title: '互聯網之子',
                  desc: '在長大的過程當中,我才慢慢發現,我身邊的全部事,別人跟我說的全部事,那些所謂原本如此,註定如此的事,它們其實沒有非得如此,事情是能夠改變的。更重要的是,有些事既然錯了,那就該作出改變。',
                  link: 'http://movie.douban.com/subject/25785114/',
                  imgUrl: 'http://demo.open.weixin.qq.com/jssdk/images/p2166127561.jpg',
                  trigger: function (res) {
                    // 不要嘗試在trigger中使用ajax異步請求修改本次分享的內容,由於客戶端分享操做是一個同步操做,這時候使用ajax的回包會尚未返回
                    alert('用戶點擊發送給朋友');
                  },
                  success: function (res) {
                    alert('已分享');
                  },
                  cancel: function (res) {
                    alert('已取消');
                  },
                  fail: function (res) {
                    alert(JSON.stringify(res));
                  }
                });
            wx.onMenuShareTimeline(shareData);
            // 要隱藏的菜單項,只能隱藏「傳播類」和「保護類」按鈕,全部menu項見附錄3
            wx.hideMenuItems({
                menuList: [
                           'menuItem:copyUrl'
                          ] 
            });
            //hide
            //wx.hideAllNonBaseMenuItem();
            //wx.hideOptionMenu();
        });
        wx.error(function (res) {
            alert("error: "+ res.errMsg);
        });

實現隱藏複製連接,分享的時候改變分析的地址與內容等,其餘接口按照文檔來吧,沒什麼複雜滴。github

其餘語言實現

Nodejs實現:http://www.57kan.com/show/index/id/15907
https://github.com/willian12345/wechat-JS-SDK-demo
PHP實現:https://github.com/wjfz/weixin-jssdkajax

Refer:
微信js sdk invalid signature簽名錯誤 問題解決
http://my.oschina.net/u/2308739/blog/371414json

相關文章
相關標籤/搜索