微信官方文档有小程序的登录流程时序图,本文围绕这张图从0到1做更具体的解析。微信小程序的登录看上去好像很复杂,实际上只是不同的接口调用、字段返回,整个过程有3方在参与:小程序、开发者服务器、微信服务器。
首先,看一下微信官方时序图:
根据上面的图,可以分为以下几步:
第1步 - 小程序:小程序前端调用wx.login得到code,再传给开发者服务器。
code是临时登录凭证,有效期为5分钟、只能使用一次,每次调用wx.login,都会返回不同的code。把code、appid、appsecret一起传到开发者服务器,由它调用微信接口换取相关信息。如果需要检验账号和密码,那么除了上述这3个参数,还可以加上账号密码,一起请求开发者服务器。
appid和appsecret需要在「小程序后台->开发->开发管理->开发设置->开发者ID」找到。这2者必须属于当前小程序,如果拿其它小程序的开发者ID则会出现报错。
appid和appsecret:
开发者服务器接口地址必须为https协议,域名必须经过ICP备案,同时需要在「小程序后台->开发->开发管理->开发设置->服务器域名」 提前配置(测试环境除外)。为了安全起见,建议用POST的请求方式。
开发者服务器域名配置:
wx.request的超时时间默认为60秒,为了避免用户因网络异常或服务器问题等待回包太久,可以在app.json设置wx.request的超时时间,超时则触发fail回调。
"networkTimeout": { "request": 3000 }
代码示例(为了方便演示,把密钥放到了前端请求):
wx.login({ success: (res) => { var code = res.code; var app_id = "你的app_id"; var app_secret = "你的app_secret"; wx.request({ url: "开发者服务器接口地址", method: "POST", header: { "content-type": "application/x-www-form-urlencoded" }, data: { //完整写法是app_id:app_id,这是简写 app_id, app_secret, code }, success: (res) => { console.log("打印返回结果", res) } }) } })
第2步 - 开发者服务器:调用微信服务器接口,得到openid、session_key、unionid。unionid需要满足一定条件才能返回,在本文不展开。在这个步骤,如果出现错误的话,微信服务器会返回errcode(错误码)。此外,在这个过程校验账号和密码之后,可以把账号密码等信息和openid进行绑定。
微信服务器接口地址:
https://api.weixin.qq.com/sns/jscode2session?appid=<your_appid>&secret=<your_appsecret>&js_code=<your_code>&grant_type=authorization_code
服务器代码示例:
$app_id=$_POST['app_id']; $app_secret=$_POST['app_secret']; $code=$_POST['code']; $url='https://api.weixin.qq.com/sns/jscode2session?appid='.$app_id.'&secret='.$app_secret.'&js_code='.$code.'&grant_type=authorization_code'; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HEADER,0); curl_setopt($ch, CURLOPT_POST, 1); $output = curl_exec($ch); curl_close($ch); echo $output;
控制台打印:
从上面可以看到,微信服务器返回了openid和session_key。openid为用户在当前小程序的唯一标识,不同用户在小程序的openid不同,用户删除小程序、清空缓存不改变openid;session_key为开发者服务器和微信服务器的会话密钥,用于加解密数据,随着用户越频繁地使用小程序,session_key的有效期会越长。
第3步 - 开发者服务器:根据微信服务器返回的信息,自定义登录态。openid和session_key因为涉及到数据隐私,所以不直接发往小程序,而是在开发者服务器根据这2者自定义登录态,也就是定义token,再通过wx.setStorage把token写入本地数据缓存。至于怎么定义token,不同语言和不同项目的写法不一样,在这不再展开。
不同小程序有各自的本地缓存空间,彼此之间是分隔的,每个缓存空间大小是10M,如果超出10M再写入数据时,会触发fail回调。当然同一个设备的不同微信用户之间的缓存也是分隔的。
在第1步的代码,我们加上存储token的语句:
wx.login({ success: (res) => { var code = res.code; var app_id = "你的app_id"; var app_secret = "你的app_secret"; wx.request({ url: "开发者服务器接口地址", method: "POST", header: { "content-type": "application/x-www-form-urlencoded" }, data: { app_id, app_secret, code }, success: (res) => { console.log("打印返回结果", res); //存储到全局变量中,方便调用 this.globalData.token = res.token; //把token放在本地缓存 wx.setStorage({ key: "token", data: res.token }) } }) } })
当用户再次打开小程序时,就带上storage请求开发者服务器(在app.js定义),判断登录态有没有过期,而不用每次打开小程序都要重新登录:
onLaunch: function () { var that = this; //从本地缓存同步获取token var token = wx.getStorageSync("token"); if (token && token.length) { //判断是否有token //验证token是否有效 wx.request({ url: "开发者服务器检验token的接口地址", method: "POST", header: { "content-type": "application/x-www-form-urlencoded" }, data: { token }, success: (res) => { if (res.status == "correct") { //假设token正确 //返回登录状态,存储到全局变量中,方便调用 this.globalData.token = res.token; } else { that.login() //重新登录,假设wx.login放到login() } } }) } else { //如果没有token that.login() //重新登录,假设wx.login放到login() } }
当然,如果你的登录态有效期以session_key为准,那么可以用wx.checkSession检查session_key有没有过期。wx.checkSession示例:
onLaunch: function() { wx.checkSession({ success() { //session_key没过期,返回登录状态 }, fail() { // session_key过期,重新登录 } }) }
Q&A
Q1:wx.login需要用户点击按钮授权吗?
A:不需要,这个行为是静默的,用户层面不会有感知。
Q2:wx.login返回的code只有5分钟的时效性,这样的好处是什么?
A:对于侵略者来说,想要在5分钟之内列举所有code,然后频繁请求开发者服务器试图获取真实用户信息,这无疑难度加大、容易被识别。
Q3:AppId和AppSecret都是隐私数据吗?会不会涉及安全风险?
A:AppId是公开信息,不会涉及安全风险,用户查询AppId的路径为「小程序右上角->小程序名称->更多资料->AppId」;AppSecret是隐私数据,不应该被公开泄露,一旦泄露需要到小程序后台重置。
本文作者:朱顺意
声明:本文为 脚本之家专栏作者 投稿,未经允许请勿转载。