使用微信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的开发都需要在线上环境,或者你在本地设置域名击穿等设置,让外网能直接访问到你本地,最后吐槽一下:微信对开发者太不友好了~