小程序登录态管理的方法示例


Posted in Javascript onNovember 13, 2018

所谓的登录态其实就是客户端发送请求的时候携带的token(通常叫做令牌),当用户输入账号密码,验证成功之后,服务端生成一个token传递给客户端,客户端在后续的请求中携带这个token,服务器进行校验,校验成功则处理客户端的请求,校验失败则要求客户端重新去登陆。

在web项目中,我们通常使用session来管理这一过程。

客户端首次访问请求的时候,服务端返回一个sessionId作为cookie给客户端,往后客户端每次请求都带上这个cookie与服务端进行通信,当执行完登陆操作以后,服务端将用户数据存入到session中;随后的每次请求,服务端都从cookie中取出sessionId,利用sessionId去查询session,利用session中是否含有用户信息来判断用户是否有登陆。

关于cookie与session的关系,请先看笔者之前的一篇文章:浅谈cookie和session

一.小程序的登录态

要明白小程序跟传统的web项目的不同之处在于它不依托于浏览器,所以它没有cookie,自然无法用session来管理登录态。这给我们的编码造成了不小麻烦。但是其实我们可以通过在请求头中加入键为JESSIONID(或者SESSION),值为sessionId的cookie来模拟这种操作。同时在服务端响应给小程序的时候,若sessionId有发生变化则再回传给客户端。

还有一个要注意的是,小程序也有自己的登录态,那就是session_key的生命周期,session_key是小程序中为了加密数据而提供的一个密钥,具有一定的生命周期。查看小程序官方文档,可以知道它是在服务端调用code2Session获取的。可以通过小程序的wx.checkSession()来校验小程序端的登录态是否过期。

弄清楚了上述两点,我们的要解决的问题包括。

1.校验小程序的登录态

2.校验服务端的登录态,即是否能从session中拿到用户数据。

3.任何一方的登录态过期,都调用登陆的相关代码,注意登陆的相关代码包含小程序端和服务端。后续会说。

4.用户信息如何储存。在web项目里,我们是将用户信息存放在session里,这样在服务端就可以直接用,而借助jsp的某些标签,在jsp页面我们也可以直接从session中拿出用户数据。但现在是小程序,在服务端我们依然可以从session中获取用户数据,但是在客户端,必须等待服务端的回传。这样每次请求都响应用户数据的做法显然不是很合理的,所以我们可以将用户数据保存在微信的缓存里。

5.拦截器问题,在web项目中,我们会在服务端给每个controller写拦截器,拦截器一般是判断登录态,判断成功则执行controller中的代码,失败的话,我们一般会重定向到登陆页面,或者执行完登陆代码后重定向到某个特定页面(微信站中这样做的)。但是这种做法在小程序中是无效的,小程序是动静分离的,我们不可能从服务端去重定向到小程序的特定页面,也不可能从服务端去调用小程序的wx.login()方法。所以,我们把这种拦截校验的发起从服务端移到小程序端。让小程序主动发起这种校验,也就是第二点的检查服务端登录态。

二.小程序登录态的方案

经过上面的分析,我们整理出小程序登录态的方案。

1.在需要用户登录态的页面,首先从缓存中获取用户数据userInfo,若无数据,则跳4

2.调用wx.checkSession()检查小程序端的登录态是否过期,若没过期,跳3,若过期,跳4

3.调用服务端的代码检查session是否过期(即检查服务端的登录态),若没过期则拿到用户数据继续执行后续的操作。若过期,则跳4.

4.登录操作,登录操作分为如下几个步骤。

--a.小程序端调用wx.login()接口得到code。(code只能使用一次)

--b.服务端利用这个code访问code2Session接口得到session_key和open_id,并将session_key和open_id存入到session中。

--c.服务端执行登录操作,主要是通过open_id去数据库中寻找用户数据,若无则新增用户到数据库,若有则取出用户数据。

--d.将用户数据userInfo,session_key,open_id等数据都存放到session中,方便服务端下次拿。

--e.将用户数据userInfo,连同session的sessionId一起响应给小程序端。

--f.小程序端得到用户数据和userInfo后更新缓存中的userInfo(包括JESSIONID的值sessionId)

上述过程可以用微信官方的这张图来表示。

小程序登录态管理的方法示例

这边的自定义登录态就是sessionId,自定义登录态与session_key,openid关联就是将session_key,openid存入到session中。

下面我们来看具体的代码吧。

1.因为很多页面需要取到用户的数据才能继续操作,所以我们在app.js里面写一个getUseInfo方法,供各子页面调用,方法如下。

//获取用户信息,传递的是一个回调函数,获取到用户信息后执行回调函数,传入的参数是userInfo
 getUserInfo: function (cb) {
 const _this = this ;
 wx.checkSession({
 success: function () {
  let userInfo = wx.getStorageSync( 'userInfo' ); //先从内存中获取userInfo
  if (userInfo.result == 1 ) {
  _this.refreshSession(cb);
  } else {
  _this.userLogin(cb);
  }
 },
 fail: function () {
  _this.userLogin(cb);
 }
 })
 },

上述方法的参数是一个回调函数,不同的页面在获取了userInfo以后传入不同的回调函数,回调函数的参数就是要获取的userInfo。

首先,调用wx.checkSession()方法判定小程序端登录态是否失效,失效的话则去执行userLogin(cb)操作,未失效则从缓存中去拿userInfo数据。在userInfo中,我们主要存放的是userName,userFace等用户数据和SESSION,还有一个标志位result,用于判断userInfo缓存数据是否失效。

然后,如果我们能从缓存中拿到用户数据,就要 检验服务端的登录态是否通过。访问refreshSession(cb)方法。代码如下

//检查服务端session是否过期
 refreshSession: function (cb) {
 const _this = this ;
 let userInfo = wx.getStorageSync( 'userInfo' );
 wx.request({
 url: _this.domain + _this.api.xcxCheckSessionReq,
 method: 'GET' ,
 header: {
  'Cookie' : 'JSESSIONID=' + userInfo.SESSION + ';SESSION=' + userInfo.SESSION,
 },
 success: function (res) {
  if (res.data == 1) {
  _this.globalData.userInfo = userInfo;
  typeof cb == "function" && cb(_this.globalData.userInfo);
  } else {
  wx.removeStorageSync( 'userInfo' );
  _this.userLogin(cb);
  }
 },
 fail: function () {
  wx.removeStorageSync( 'userInfo' );
  _this.userLogin(cb);
 }
 })
 },

此处,调用服务端的接口来验证服务端的session是否已经过期,服务端的代码如下:

public String xcxCheckSession() {
  Integer result;
  HttpServletRequest req = ServletActionContext.getRequest();
  HttpSession s = req.getSession();
  if (s.getAttribute( "c_userId" )!= null ){
  result=1;
  } else {
  result=0;
  }
  OutPutMsg.outPutMsg(result.toString());
  return null ;
 }

其中OutPutMsg方法就是将结果响应给客户端。

上述代码根据小程序端传过来的JSESSIONID或者SESSION的值,利用servlet的特性,根据这个值去获取session,再判断session中是否有用户信息。从而完成服务端的登录态校验。其实原理跟我们在服务端使用拦截器校验session是否过期是一样的。

若服务端登录态校验失败,则需要清空缓存中的userInfo信息,然后去执行userLogin(cb)方法,进行登录。

2.登录操作涉及到小程序端和服务端,小程序端的代码如下:

userLogin: function (cb) {
 const _this = this ;
 wx.login({
 success: function (res) {
  //获取code然后去访问服务端登录接口,code主要是为了换openId和session_key。
  if (res.code) {
  wx.request({
  url: _this.domain + _this.api.loginCheckReq,
  method: 'POST' ,
  header: {
  'Content-Type' : _this.globalData.postHeader
  },
  data: {
  jsCode: res.code,
  },
  success: function (res) {
  //登录成功
  if (res.data.result == 1) {
   wx.getUserInfo({
   withCredentials: true ,
   success: function (result) {
   res.data.wechatUserInfo = result.userInfo;
   _this.globalData.userInfo = res.data;
   _this.globalData.userInfo.face = '/uploadFiles/' + res.data.userFace;
   typeof cb == "function" && cb(_this.globalData.userInfo)
   wx.setStorageSync( 'userInfo' , _this.globalData.userInfo); //将用户数据存入内存
   },
   fail: function () {
   _this.globalData.userInfo = res.data;
   _this.globalData.userInfo.face = res.data.prefix + '/uploadFiles/' + res.data.userFace;
   typeof cb == "function" && cb(_this.globalData.userInfo)
   wx.setStorageSync( 'userInfo' , _this.globalData.userInfo);
   }
   })
  }
  }
  })
  }
 }
 })
 },

首先小程序端访问wx.login()接口获取code,然后调用服务端的登录代码。服务端的登录伪代码如下:

public String xcxLogin(){
  Integer result;
  Map<String,Object>map= new HashMap<String, Object>();
  try {
  HttpServletRequest req = ServletActionContext.getRequest();
  String jsCode = req.getParameter( "jsCode" );
  String url = "https://api.weixin.qq.com/sns/jscode2session?appid="
   + ConfigUtil.XCX_APP_ID + "&secret="
   + ConfigUtil.XCX_APP_SECRET + "&js_code=" + jsCode
   + "&grant_type=authorization_code" ;
  String urlDetail = URLConnectionUtil.getUrlDetail(url); //访问小程序接口,获取openId,session_key
  JSONObject jsonObject = JSONObject.fromObject(urlDetail);
  String openId=jsonObject.getString( "openid" );
  String session_key=jsonObject.getString( "session_key" );
  TUser user=getUserByOpenId(openId);
  if (user== null ){
   //新增用户,插入到数据库
   TUser userTmp= new TUser();
   user.setOpenId(openId);
   addUser(userTmp);
   user=userTmp;
  }
  session.put( "user" , user); //将user信息放入session
  session.put( "session_key" , session_key); //将session_key放入session
  map.put( "user" , user); //将user信息响应给小程序端
  map.put( "SESSION" , req.getSession().getId()); //将sessionId响应给小程序端
  result= 1 ; //登录操作成功的标志位
  } catch (Exception e) {
  e.printStackTrace();
  }
  map.put( "result" , result);
  JSONObject resInfo=JsonUtil.mapToJsonObject(map);
  OutPutMsg.outPutMsg(resInfo.toString()); //将数据响应给小程序端
  return null ;
 }

先根据code去拿到openId和session_key,然后从数据库去查询是否有这个openId的客户,没有的话直接执行新增操作,然后将user信息(包含openId)和session_key信息存入session,方便服务端下次直接获取。再把user信息和sessionId回传给小程序端。

小程序端拿到这些信息,就可以把他们缓存起来,以备下次使用啦。

3.最后,凡事需要用户登录才能进入的页面,我们都让他调用getUserInfo(cb),并传入cb回调方法,比如。

onShow: function () {
 const _this = this ;
 app.getUserInfo( function (userInfo) {
 _this.setData({
  userInfo: userInfo,
 })
 });
 },

三.其他注意点

关于上述代码的userLogin()部分,目前主流的有两种。

1.使用wx.login()静默授权,获取用户的openId(),不要求用户绑定手机号,只在涉及到需要用户手机号的时候才让用户来绑定手机号。只需要在userInfo中预留一个标记用户是否有绑定手机号的字段即可。本文介绍的是采用这种登录方式。

2.必须要用户登录输入手机号及验证码才算登录成功,则将userLogin处的逻辑改为跳转至登录页面。然后服务端的判断逻辑则改为通过手机号和验证码来确认用户是否登录成功。其他部分的逻辑不变,这也是目前比较主流的做法

3:可以简单的理解wx.login()接口是静默授权,它能得到用户的openId;而wx.getUserInfo()需要用户授权,可以获取到用户的头像,昵称等信息。还可以通过wx.getUserInfo()获取到unionId等私密信息,但是必须得在已经调用过wx.login()且登录态尚未过期的前提下。

四.unionId机制

如果开发者拥有多个移动应用、网站应用、和公众帐号(包括小程序),可通过 UnionID 来区分用户的唯一性,因为只要是同一个微信开放平台帐号下的移动应用、网站应用和公众帐号(包括小程序),用户的 UnionID 是唯一的。换句话说,同一用户,对同一个微信开放平台下的不同应用,unionid是相同的。

绑定了开发者帐号的小程序,可以通过下面 4 种途径获取 UnionID。

1.调用接口 wx.getUserInfo,从解密数据中获取 UnionID。注意本接口需要用户授权,请开发者妥善处理用户拒绝授权后的情况。

2.如果开发者帐号下存在同主体的公众号,并且该用户已经关注了该公众号。开发者可以直接通过 wx.login + code2Session 获取到该用户 UnionID,无须用户再次授权。

3.如果开发者帐号下存在同主体的公众号或移动应用,并且该用户已经授权登录过该公众号或移动应用。开发者也可以直接通过 wx.login + code2Session 获取到该用户 UnionID ,无须用户再次授权。

4.小程序端调用云函数时,当满足 UnionID 获取条件时可在云函数中通过 cloud.getWXContext 获取 UnionID

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
JQuery 国际象棋棋盘 实现代码
Jun 26 Javascript
自己动手实现jQuery Callbacks完整功能代码详解
Nov 25 Javascript
jQuery实现数字加减效果汇总
Dec 16 Javascript
简单谈谈node.js 版本控制 nvm和 n
Oct 15 Javascript
基于javascript实现彩票随机数生成(升级版)
Apr 17 Javascript
CSS3 media queries结合jQuery实现响应式导航
Sep 30 Javascript
浅谈struts1 &amp; jquery form 文件异步上传
May 25 jQuery
Vue项目webpack打包部署到服务器的实例详解
Jul 17 Javascript
js实现单张图片平移切换效果
Oct 11 Javascript
react同构实践之实现自己的同构模板
Mar 13 Javascript
Vue开发之封装上传文件组件与用法示例
Apr 25 Javascript
vue+django实现下载文件的示例
Mar 24 Vue.js
Vuex 使用 v-model 配合 state的方法
Nov 13 #Javascript
vue代码分割的实现(codesplit)
Nov 13 #Javascript
详解vuex之store拆分即多模块状态管理(modules)篇
Nov 13 #Javascript
JS获取当前时间的实例代码(昨天、今天、明天)
Nov 13 #Javascript
checkbox在vue中的用法小结
Nov 13 #Javascript
React父子组件间的传值的方法
Nov 13 #Javascript
JS中使用cavas截图网页并解决跨域及模糊问题
Nov 13 #Javascript
You might like
PHP中的array数组类型分析说明
2010/07/27 PHP
php获取后台Job管理的实现代码
2011/06/10 PHP
wordpress自定义url参数实现路由功能的代码示例
2013/11/28 PHP
PHP编程风格规范分享
2014/01/15 PHP
php精度计算的问题解析
2019/06/21 PHP
laravel http 自定义公共验证和响应的方法
2019/09/29 PHP
javascript 对表格的行和列都能加亮显示
2008/12/26 Javascript
利用JavaScript检测CPU使用率自己写的
2014/03/22 Javascript
字段太多jquey快速清空表单内容方法
2014/08/21 Javascript
js鼠标点击图片切换效果实现代码
2015/11/19 Javascript
基于javascript实现根据身份证号码识别性别和年龄
2016/01/22 Javascript
初识NodeJS服务端开发入门(Express+MySQL)
2017/04/07 NodeJs
jQuery使用正则验证15/18身份证的方法示例
2017/04/27 jQuery
JavaScript 基础表单验证示例(纯Js实现)
2017/07/20 Javascript
67 个节约开发时间的前端开发者的工具、库和资源
2017/09/12 Javascript
jquery一键控制checkbox全选、反选或全不选
2017/10/16 jQuery
Element中的Cascader(级联列表)动态加载省\市\区数据的方法
2019/03/27 Javascript
跟混乱的页面弹窗说再见
2019/04/11 Javascript
js实现计时器秒表功能
2019/12/16 Javascript
解决vue无法侦听数组及对象属性的变化问题
2020/07/17 Javascript
Python实现的归并排序算法示例
2017/11/21 Python
python实现自动发送邮件发送多人、群发、多附件的示例
2018/01/23 Python
Python对象属性自动更新操作示例
2018/06/15 Python
python滑块验证码的破解实现
2019/11/10 Python
python实现自动化报表功能(Oracle/plsql/Excel/多线程)
2019/12/02 Python
Python selenium的基本使用方法分析
2019/12/21 Python
使用PyCharm官方中文语言包汉化PyCharm
2020/11/18 Python
Python: glob匹配文件的操作
2020/12/11 Python
全球领先的在线cosplay服装商店:RoleCosplay
2020/01/18 全球购物
员工年终演讲稿
2014/01/03 职场文书
微型企业创业投资计划书
2014/01/10 职场文书
班干部演讲稿
2014/04/24 职场文书
拾金不昧感谢信范文
2015/01/21 职场文书
社区重阳节活动总结
2015/03/24 职场文书
会议通知
2015/04/15 职场文书
2016元旦主持人经典开场白台词
2015/12/03 职场文书