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
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 + "×tamp=" + 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