使用微信jssdk進行h5分享
記錄一下自己在開發jssdk過程和遇到的坑,最大的坑我覺得是:後臺返回簽名和驗證工具簽名一致,任然報錯invalid signature
,解決方法看文末。
一、首先,要嚴格按照微信文檔步驟執行前面幾個步驟,不然後面你會發現很多莫名其妙的坑。這裏主要是4個步驟:
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 標準模塊加載方法加載
在這裏介紹一種vue文件引入js文件的方法 - - 混入 (mixins)
/* loadExternalAssetMixin.js */
const loadExternalAssetMixin = {
methods:{
loadScript(src, callback) {
if (!(typeof callback === 'function')) {
callback = function() {}
}
var check = document.querySelectorAll(`script[src="${src}"]`)
if (check.length > 0) {
check[0].addEventListener('load', function() {
callback()
})
callback()
return
}
var script = document.createElement('script')
var head = document.getElementsByTagName('head')[0]
script.type = 'text/javascript'
script.charset = 'UTF-8'
script.src = src
if (script.addEventListener) {
script.addEventListener('load', function() {
callback()
}, false)
} else if (script.attachEvent) {
script.attachEvent('onreadystatechange', function() {
var target = window.event.srcElement
if (target.readyState === 'loaded') {
callback()
}
})
}
head.appendChild(script)
}
}
}
export default loadExternalAssetMixin
引入方式:
/* xxx.vue */
<script>
import loadExternalAssetMixin from '../util/loadExternalAssetMixin'
export default {
mixins: [ loadExternalAssetMixin],
mounted () {
this.loadScript('http://res.wx.qq.com/open/js/jweixin-1.4.0.js', () => {
// 回調
})
}
}
</script>
步驟三:通過config接口注入權限驗證配置
所有需要使用JS-SDK的頁面必須先注入配置信息,否則將無法調用(同一個url僅需調用一次,對於變化url的SPA的web app可在每次url變化時進行調用,目前Android微信客戶端不支持pushState的H5新特性,所以使用pushState來實現web app的頁面會導致簽名失敗,此問題會在Android6.2中修復)。
wx.config({
debug: true, // 開啓調試模式,調用的所有api的返回值會在客戶端alert出來,若要查看傳入的參數,可以在pc端打開,參數信息會通過log打出,僅在pc端時纔會打印。
appId: '', // 必填,公衆號的唯一標識
timestamp: , // 必填,生成簽名的時間戳
nonceStr: '', // 必填,生成簽名的隨機串
signature: '',// 必填,簽名
jsApiList: [] // 必填,需要使用的JS接口列表
});
簽名算法見文末的附錄1,所有JS接口列表見文末的附錄2
附錄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)
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.出於安全考慮,開發者必須在服務器端實現簽名的邏輯。
如出現invalid signature 等錯誤詳見附錄5常見錯誤及解決辦法。
簽名方法需要放在後臺,不放在vue前端,而是前端向後臺請求signature
簽名方法:
/* signature.js */
const config = require('../config')
const crypto = require('crypto')
const API = require('../wechat/api')
const api = new API(config.wechat.appid, config.wechat.appsecret)
// 生成隨機字符串
let createNonceStr = function () {
return Math.random().toString(36).substr(2, 15);
}
// 生成時間戳
let createTimestamp = function () {
return parseInt(new Date().getTime() / 1000) + '';
}
//對所有待簽名參數按照字段名的ASCII 碼從小到大排序(字典序)後,使用URL鍵值對的格式(即key1 = value1 & key2=value2…)拼接成字符串string
let raw = function (args) {
let keys = Object.keys(args)
keys = keys.sort()
let string = ''
keys.forEach(k => {
string += '&' + k + '=' + args[k]
})
string = string.substr(1)
return string
}
//生成簽名
class Signature {
static async sign (ctx) {
const { jsapi_ticket } = await api.ensureTicket() //獲取到的jsapi_ticket, 具體可看https://github.com/Laumlin/wechat-public
let ret = {
jsapi_ticket: jsapi_ticket,
noncestr: createNonceStr(),
timestamp: createTimestamp(),
url: ctx.request.body.url
}
const string = raw(ret)
let hash = crypto.createHash('sha1')
hash.update(string)
ret.signature = hash.digest('hex')
ctx.response.body = ret
}
}
module.exports = Signature
步驟四:通過ready接口處理成功驗證
wx.ready(function(){
// config信息驗證後會執行ready方法,所有接口調用都必須在config接口獲得結果之後,config是一個客戶端的異步操作,所以如果需要在頁面加載時就調用相關接口,則須把相關接口放在ready函數中調用來確保正確執行。對於用戶觸發時才調用的接口,則可以直接調用,不需要放在ready函數中。
});
<script>
export default {
mounted() {
this.loadScript('http://res.wx.qq.com/open/js/jweixin-1.4.0.js', () => {
console.log('import jssdk success')
let url = `${NODE_URL}${this.$store.state.redirectURL}` // 獲取當前頁面的url
this.$axios({
method: 'post',
url:`${MP_BASEURL}/getSignature`,
data: {
'url': url
}
}).then(res => {
wx.config({
debug: false, // 開啓調試模式,調用的所有api的返回值會在客戶端alert出來,若要查看傳入的參數,可以在pc端打開,參數信息會通過log打出,僅在pc端時纔會打印。
appId: this.$store.state.wechatAppId, // 必填,公衆號的唯一標識
timestamp: res.data.timestamp, // 必填,生成簽名的時間戳
nonceStr: res.data.noncestr, // 必填,生成簽名的隨機串
signature: res.data.signature,// 必填,簽名
jsApiList: ['updateAppMessageShareData', 'updateTimelineShareData','onMenuShareTimeline', 'onMenuShareAppMessage'] // 必填,需要使用的JS接口列表
})
wx.ready(()=>{
this.setWechatConfig(this.config)
})
})
})
},
computed: {
config () {
return {
title: 'share_title',
desc: 'share_description',
img:'',
img_title:'share_img_title',
link: `${NODE_URL}${this.$route.fullPath}`
}
},
},
methods: {
onComplete (way) {
return () => {
// 成功回調
}
},
setWechatConfig (newVal) {
// 自定義“分享給朋友”及“分享到QQ”按鈕的分享內容
wx.onMenuShareAppMessage({
title: newVal.title, // 分享標題
desc: newVal.desc, // 分享描述
link: newVal.link, // 分享鏈接,該鏈接域名或路徑必須與當前頁面對應的公衆號JS安全域名一致
imgUrl: newVal.img, // 分享圖標
type: 'link',
dataUrl: '',
success: this.onComplete('wechatFriend')
})
// 自定義“分享到朋友圈”及“分享到QQ空間”按鈕的分享內容
wx.onMenuShareTimeline({
title: newVal.title, // 分享標題
link: newVal.link, // 分享鏈接,該鏈接域名或路徑必須與當前頁面對應的公衆號JS安全域名一致
imgUrl: newVal.img, // 分享圖標
success: this.onComplete('wechatTimeline')
})
},
}
}
</script>
填坑:
url
必須是當前網址的url
,不包含‘#’後面部分,不能是本地地址http://localhost:8080/
之類的,而且url的domain必須在微信安全域名之中。這也是我之前很頭痛的一件事,後臺返回的簽名和微信簽名檢工具一樣,任然報錯,一直報invalid signature
的錯誤,找到半天,簽名和驗證工具簽名一致,只能說明你簽名時的url和wx校驗時的url不一致,原來用local域的url生成的簽名wx.config
檢驗不通過,最後在線上環境,url
的域名是可以直接訪問到的,簽名終於通過了。 還有一點需要注意的是url中如果有中文,只需要對中文作encodeURIComponent()處理,’/'等符號不用。
總結:微信公衆號和jssdk的開發都需要在線上環境,或者你在本地設置域名擊穿等設置,讓外網能直接訪問到你本地,最後吐槽一下:微信對開發者太不友好了~