微信開放平臺之JSSDK分享
微信分享開發流程:
步驟一:繫結域名
先登入微信公眾平臺進入“公眾號設定”的“功能設定”裡填寫“JS介面安全域名”。
步驟二:引入JS檔案
在需要呼叫JS介面的頁面引入如下JS檔案,(支援https):http://res.wx.qq.com/open/js/jweixin-1.4.0.js
如需進一步提升服務穩定性,當上述資源不可訪問時,可改訪問:http://res2.wx.qq.com/open/js/jweixin-1.4.0.js (支援https)。
備註:支援使用 AMD/CMD 標準模組載入方法載入
步驟三:通過config介面注入許可權驗證配置
見微信官方文件 https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115
//微信分享 //呼叫後臺介面獲取url簽名 shareWXCTerminal: async function(){ let shareParams = { appId: 'xxxxxxxxxxxxxxxxxx', url: encodeURIComponent(location.href.split('#')[0]) } const res= await http.post("/weshare/getwxfxSign/ashx", shareParams) if(res.data.code==200&&res.data.data!=''){ var getMsg=res.data.data wx.config({ //注意駝峰命名 debug: false, //生產環境需要關閉debug模式 appId: getMsg.appId, //appId通過微信服務號後臺檢視 timestamp: getMsg.timestamp, //生成簽名的時間戳 nonceStr: getMsg.nonceStr, //生成簽名的隨機字串 signature: getMsg.signature, //簽名 jsApiList: ['onMenuShareTimeline', 'onMenuShareAppMessage', 'onMenuShareQQ','onMenuShareQZone'] }); wx.ready(function() { wx.onMenuShareTimeline({ title:'wenjuan', // 分享標題 link: location.href.split('#')[0]+'#'+location.href.split('#')[1],// 分享連結 imgUrl: 'http://scrm.taikang.com/d6e18c2231ec926ebae2cc95a55ec3c3(0c95d4edab6d4f5397931f936c731b11).jpg', // 分享圖示 success:function(res){ } }), wx.onMenuShareAppMessage({ title:'標題', // 分享標題 desc: 'mianshuu', //分享描述 link: location.href.split('#')[0]+'#'+location.href.split('#')[1],// 分享連結 imgUrl: 'http://scrm.taikang.com/d6e18c2231ec926ebae2cc95a55ec3c3(0c95d4edab6d4f5397931f936c731b11).jpg', // 分享圖示 success:function(res){ } }), wx.onMenuShareQZone({ title:'標題', // 分享標題 desc: 'mianshuu', //分享描述 link: location.href.split('#')[0]+'#'+location.href.split('#')[1],// 分享連結 imgUrl: 'http://scrm.taikang.com/d6e18c2231ec926ebae2cc95a55ec3c3(0c95d4edab6d4f5397931f936c731b11).jpg', // 分享圖示 success:function(res){ } }), wx.onMenuShareQQ({ title:'標題', // 分享標題 desc: 'mianshuu', //分享描述 link: location.href.split('#')[0]+'#'+location.href.split('#')[1],// 分享連結 imgUrl: 'http://scrm.taikang.com/d6e18c2231ec926ebae2cc95a55ec3c3(0c95d4edab6d4f5397931f936c731b11).jpg', // 分享圖示 success:function(res){ } }) }); } },
後臺步驟
附錄1-JS-SDK使用許可權簽名演算法
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):../15/54ce45d8d30b6bf6758f68d2e95bc627.html
2.用第一步拿到的access_token 採用http GET方式請求獲得jsapi_ticket(有效期7200秒,開發者必須在自己的服務全域性快取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
}
獲得jsapi_ticket之後,就可以生成JS-SDK許可權驗證的簽名了。
簽名演算法
簽名生成規則如下:參與簽名的欄位包括noncestr(隨機字串), 有效的jsapi_ticket, timestamp(時間戳), url(當前網頁的URL,不包含#及其後面部分) 。對所有待簽名引數按照欄位名的ASCII 碼從小到大排序(字典序)後,使用URL鍵值對的格式(即key1=value1&key2=value2…)拼接成字串string1。這裡需要注意的是所有引數名均為小寫字元。對string1作sha1加密,欄位名和欄位值都採用原始值,不進行URL 轉義。
即signature=sha1(string1)。 示例:
noncestr=Wm3WZYTPz0wzccnW
jsapi_ticket=sM4AOVdWfPE4DxkXGEs8VMCPGGVi4C3VM0P37wVUCFvkVAy_90u5h9nbSlYy3-Sl-HhTdfl2fzFy1AOcHKP7qg
timestamp=1414587457
url=http://mp.weixin.qq.com?params=value
步驟1. 對所有待簽名引數按照欄位名的ASCII 碼從小到大排序(字典序)後,使用URL鍵值對的格式(即key1=value1&key2=value2…)拼接成字串string1:
jsapi_ticket=sM4AOVdWfPE4DxkXGEs8VMCPGGVi4C3VM0P37wVUCFvkVAy_90u5h9nbSlYy3-Sl-HhTdfl2fzFy1AOcHKP7qg&noncestr=Wm3WZYTPz0wzccnW×tamp=1414587457&url=http://mp.weixin.qq.com?params=value
步驟2. 對string1進行sha1簽名,得到signature:
0f9de62fce790f9a083d5c99e95740ceb90c27ed
注意事項
1.簽名用的noncestr和timestamp必須與wx.config中的nonceStr和timestamp相同。
2.簽名用的url必須是呼叫JS介面頁面的完整URL。
3.出於安全考慮,開發者必須在伺服器端實現簽名的邏輯。
程式碼示例:
@PostMapping("/getwxfxSign/ashx")
public BaseBean getSignature(@RequestBody Map<String,String> param) throws UnsupportedEncodingException {
String appId = param.get("appId");
String url = param.get("url");
//前端請求中的 url 是 encodeURIComponent(location.href.split('#')[0])處理過的
//後端相應的要 解碼
String decodeUrl = URLDecoder.decode(url, "UTF-8");
SigVO sign = this.sign(appId, decodeUrl);
return BaseBean.success(sign);
}
private SigVO sign(String appid, String url) {
//32位隨機字串
String nonceStr = WeShareUtils.CreateNoncestr();
//時間戳
String timeStamp = WeShareUtils.GetTimeStamp();
//簽名
String signature = StringUtils.EMPTY;
//獲取jsapi_ticket
Map<String, String> resultMap = new HashMap<>();
getJsapiTicketCatch(appid,resultMap);
String jsapiTicket = resultMap.get("tick");
if (StringUtils.isBlank(jsapiTicket)) {
return null;
}
//注意 引數欄位全為小寫
String s1 = "jsapi_ticket=" + jsapiTicket + "&noncestr="
+ nonceStr + "×tamp=" + timeStamp + "&url=" + url;
try {
MessageDigest crypt = MessageDigest.getInstance("SHA-1");
crypt.reset();
crypt.update(s1.getBytes("UTF-8"));
signature = WeShareUtils.byteToHex(crypt.digest());
} catch (Exception e) {
e.printStackTrace();
}
return SigVO.builder()
.appId(appid)
.timestamp(timeStamp)
.nonceStr(nonceStr)
.signature(signature)
.build();
}
private void getJsapiTicketCatch(String appid, Map<String, String> resultMap) {
long expr = 90 * 60 * 1000L;
long ex = 5 * 1000L;
String value = String.valueOf(System.currentTimeMillis() + ex);
boolean lock = redisLock.lock(appid, value);
//獲取鎖
if (lock) {
String tick = redisTemplate.opsForValue().get("zqs"+appid);
if (StringUtils.isNotBlank(tick)) {
resultMap.put("tick",tick);
} else {
//token 通過appid
BigAuthorizationInfo authorInfoByAppidService = authorizedService.getAuthorInfoByAppidService(appid);
if (authorInfoByAppidService == null
|| StringUtils.isBlank(authorInfoByAppidService.getAuthorizer_access_token())) {
resultMap.put("tick",null);
}else {
String token = authorInfoByAppidService.getAuthorizer_access_token();
String jsapiTicket = WeShareUtils.GetJsapiTicket(token);
if (StringUtils.isBlank(jsapiTicket)) {
resultMap.put("tick",null);
}else {
resultMap.put("tick",jsapiTicket);
redisTemplate.opsForValue().set("zqs"+appid, jsapiTicket, expr,TimeUnit.MILLISECONDS);
}
}
}
//釋放鎖
redisLock.unlock(appid, value);
}
}
上述程式碼未實現定時重新整理憑證jsapiTicket 而是通過redis設定失效時間 如果redis中沒有需要的憑證
則主動呼叫獲取jsapiTicket 的介面 獲取後存入redis中 未避免併發導致 介面呼叫超出限制次數 所以加鎖處理
微信官方的驗證簽名是否正確的網頁工具地址:https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=jsapisign
工具類:
package com.taikang.controller.share.WeShareUtil;
import com.alibaba.fastjson.JSONObject;
import com.taikang.constant.WeChatContants;
import com.taikang.controller.share.TicketJson;
import com.taikang.utils.weChat.WeChatUtils;
import java.util.Formatter;
import java.util.Random;
public class WeShareUtils {
public static String GetJsapiTicket(String token) {
String getTickUrl = WeChatContants.GET_TICK_URL;
String url = String.format(getTickUrl, token);
String result = WeChatUtils.getUrl(url);
TicketJson ticketJson = JSONObject.parseObject(result, TicketJson.class);
int errcode = ticketJson.getErrcode();
if (errcode==0){
return ticketJson.getTicket();
}
return null;
}
public static String GetTimeStamp() {
return Long.toString(System.currentTimeMillis() / 1000);
}
public 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;
}
public static String CreateNoncestr() {
String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
StringBuilder res = new StringBuilder();
Random rd = new Random();
for (int i = 0; i < 32; i++) {
res.append(chars.charAt(rd.nextInt(chars.length() - 1)));
}
return res.toString();
}
}