微信开发之签名校验及获取openId
我们要用微信jsapi,以及获取用户openid,就要进行签名校验。
先捋一下应用jssdk的整个流程:
步骤一:绑定域名
先登录微信公众平台进入“公众号设置”的“功能设置”里填写“JS接口安全域名”。
备注:登录后可在“开发者中心”查看对应的接口权限。
步骤二:引入JS文件
在需要调用JS接口的页面引入如下JS文件,(支持https):http://res.wx.qq.com/open/js/jweixin-1.2.0.js
备注:支持使用 AMD/CMD 标准模块加载方法加载
步骤三:通过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
步骤四:通过ready接口处理成功验证
wx.ready(function(){
// config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。
});
步骤五:通过error接口处理失败验证
wx.error(function(res){
// config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。
});
步骤一,二,四,五这里不在说明,主要讲的是第三步,通过config接口注入权限验证配置。
首先,准备好认证过的微信服务号(认证请看我的另一篇博客“微信公众号认证及支付开通流程”),假设公众号appid为wx0123456789abcdef,公众号开发者密码AppSecret为12345678(32位)。
引用wechat.js文件,该文件为微信jssdk相关配置,getsign()方法是调用服务器get接口,jsApiList中是需要使用的js接口 列表
function getsign(){ $.get('/signature?url='+window.location.href.split('#')[0],function(data){ var json=JSON.parse(data); Timestamp=json.Timestamp; Signature=json.Signature; wx.config({ beta:true, debug: true, appId: AppId, //'<%= AppId %>', timestamp: Timestamp, //'<%= Timestamp %>', nonceStr: Noncestr, //'<%= Noncestr %>', signature: Signature, //'<%= Signature %>', jsApiList: [ 'checkJsApi' ] }); wx.ready(function() { }); wx.error(function (res) { alert("调用微信jsapi返回的状态:"+res.errMsg); }); }); }
下面是服务器端代码实现,这里我用的是node来实现的(还未入门),这里随机字符串应该是随机的,我懒省事就直接写了一个,SHA1加密代码是网上copy的
var http = require("http") var https=require("https") var fs = require("fs") var process=require("process"); var urllib = require('url'); var jsapi_ticket=''; var access_token=''; https.createServer(function(req,res){ var response=res; var path = urllib.parse(req.url); //2小时获取一次jsticket setInterval(function(){ jsapi_ticket=''; access_token=''; if(path.pathname=='/signature'){ //获取token gettoken(path,response); }else if(path.pathname == "/"){ sendFile(res,"/join_cyiot.html") }else{ sendFile(res,path.pathname) } },72000000) if(path.pathname=='/signature'){ gettoken(path,response); }else if(path.pathname == "/"){ sendFile(res,"/join_cyiot.html") }else if(path.pathname == "/getOpenId"){ //根据code获取openid var code=path.query.substr(path.query.indexOf('code=')+5); var ip = req.headers['x-forwarded-for']||req.ip||req.connection.remoteAddress||req.socket.remoteAddress||eq.connection.socket.remoteAddress||''; if(ip.split(',').length>0){ ip = ip.split(',')[0] } ip=ip.substr(ip.indexOf('f:')+2); https.get('https://api.weixin.qq.com/sns/oauth2/access_token?appid=wx0123456789abcdef&secret=12345678&code='+code+'&grant_type=authorization_code',function(req,res){ var openid=''; req.on('data',function(data){ openid+=data; }); req.on('end',function(){ var openidobj=JSON.parse(openid); openidobj.ip=ip; response.end(JSON.stringify(openidobj)) }) }) }else{ sendFile(res,path.pathname) } }).listen(8007); function gettoken(path,response){ https.get('https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=wx0123456789abcdef&secret=12345678',function(req,res){ var jsondata=''; req.on('data',function(data){ jsondata+=data; }); req.on('end',function(){ var json=JSON.parse(jsondata); access_token=json.access_token; if(jsapi_ticket==''||access_token==''){ //获取jsticket https.get('https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token='+json.access_token+'&type=jsapi',function(req,res){ jsondata=''; req.on('data',function(data){ jsondata+=data; }); req.on('end',function(){ var jsonTicket= JSON.parse(jsondata); jsapi_ticket=jsonTicket.ticket; //时间戳,秒 var Timestamp=parseInt(new Date().getTime()/1000); var Noncestr="asdffdsadfasf";//随机字符串 //算签名并返回 var Signature=SHA2("jsapi_ticket="+jsonTicket.ticket+"&noncestr="+Noncestr+"×tamp="+Timestamp+"&"+path.query); response.end(JSON.stringify({Timestamp:Timestamp,Signature:Signature,jsonTicket:jsonTicket,url:path.query})); }); }); }else{ //时间戳,秒 var Timestamp=parseInt(new Date().getTime()/1000); var Noncestr="asdffdsadfasf";//随机字符串 //算签名并返回 var Signature=SHA2("jsapi_ticket="+jsapi_ticket+"&noncestr="+Noncestr+"×tamp="+Timestamp+"&"+path.query); response.end(JSON.stringify({Timestamp:Timestamp,Signature:Signature,jsonTicket:jsapi_ticket,url:path.query})); } }); }); } function sendFile(res,path){ var path = process.cwd()+path; fs.readFile(path,function(err,stdout,stderr){ if(!err){ var data = stdout; var type = path.substr(path.lastIndexOf(".")+1,path.length) res.writeHead(200,{'Content-type':"text/"+type}); res.write(data); } res.end(); }) } // SHA1 function add(x, y) { return((x & 0x7FFFFFFF) + (y & 0x7FFFFFFF)) ^ (x & 0x80000000) ^ (y & 0x80000000); } function SHA1hex(num) { var sHEXChars = "0123456789abcdef"; var str = ""; for(var j = 7; j >= 0; j--) str += sHEXChars.charAt((num >> (j * 4)) & 0x0F); return str; } function AlignSHA1(sIn) { var nblk = ((sIn.length + 8) >> 6) + 1, blks = new Array(nblk * 16); for(var i = 0; i < nblk * 16; i++) blks[i] = 0; for(i = 0; i < sIn.length; i++) blks[i >> 2] |= sIn.charCodeAt(i) << (24 - (i & 3) * 8); blks[i >> 2] |= 0x80 << (24 - (i & 3) * 8); blks[nblk * 16 - 1] = sIn.length * 8; return blks; } function rol(num, cnt) { return(num << cnt) | (num >>> (32 - cnt)); } function ft(t, b, c, d) { if(t < 20) return(b & c) | ((~b) & d); if(t < 40) return b ^ c ^ d; if(t < 60) return(b & c) | (b & d) | (c & d); return b ^ c ^ d; } function kt(t) { return(t < 20) ? 1518500249 : (t < 40) ? 1859775393 : (t < 60) ? -1894007588 : -899497514; } function SHA1(sIn) { var x = AlignSHA1(sIn); var w = new Array(80); var a = 1732584193; var b = -271733879; var c = -1732584194; var d = 271733878; var e = -1009589776; for(var i = 0; i < x.length; i += 16) { var olda = a; var oldb = b; var oldc = c; var oldd = d; var olde = e; for(var j = 0; j < 80; j++) { if(j < 16) w[j] = x[i + j]; else w[j] = rol(w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16], 1); t = add(add(rol(a, 5), ft(j, b, c, d)), add(add(e, w[j]), kt(j))); e = d; d = c; c = rol(b, 30); b = a; a = t; } a = add(a, olda); b = add(b, oldb); c = add(c, oldc); d = add(d, oldd); e = add(e, olde); } SHA1Value = SHA1hex(a) + SHA1hex(b) + SHA1hex(c) + SHA1hex(d) + SHA1hex(e); return SHA1Value.toUpperCase(); } function SHA2(sIn) { return SHA1(sIn).toLowerCase(); }
根据请求返回的随机字符串,时间戳,加上签名和appid进行校验即可,整个流程不算复杂,但是自己做下来报了非常多次错误,大小写以及一些细节都要注意,格式与上述代码中一致即可。
获取openid的js代码
function getQueryString(name) { var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i"); var r = window.location.search.substr(1).match(reg); if (r != null) return unescape(r[2]); return null; } var code = getQueryString("code"); var redirecturl=encodeURIComponent('微信后台配置的授权域名') ; if(!code){ window.location.href='https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx0123456789abcdef&redirect_uri='+redirecturl+'&response_type=code&scope=snsapi_base&state=0#wechat_redirect' }else{ $.get('/getOpenId?code='+code+'',function(data){ window.localStorage.setItem('openidobj',data); }) }
上述方法中微信提供的获取code的接口中,scope参数为应用授权作用域,它的值有两种,snsapi_base (不弹出授权页面,直接跳转,只能获取用户openid),snsapi_userinfo (弹出授权页面,可通过openid拿到昵称、性别、所在地。并且, 即使在未关注的情况下,只要用户授权,也能获取其信息 )
其他也就没啥了,按照微信官方文档,一步步细心点就可以了。