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


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 插件开发备注
Aug 27 Javascript
js类型检查实现代码
Oct 29 Javascript
jQuery-1.9.1源码分析系列(十)事件系统之事件体系结构
Nov 19 Javascript
JavaScript获取IP获取的是IPV6 如何校验
Jun 12 Javascript
JS实现获取当前URL和来源URL的方法
Aug 24 Javascript
Vue.js每天必学之计算属性computed与$watch
Sep 05 Javascript
Js获取当前日期时间及格式化代码
Sep 17 Javascript
作为老司机使用 React 总结的 11 个经验教训
Apr 08 Javascript
element-ui使用导航栏跳转路由的用法详解
Aug 22 Javascript
vue解决弹出蒙层滑动穿透问题的方法
Sep 22 Javascript
vue使用recorder.js实现录音功能
Nov 22 Javascript
TS 类型收窄教程示例详解
Sep 23 Javascript
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
第十三节--对象串行化
2006/11/16 PHP
用PHP读取flv文件的播放时间长度
2009/09/03 PHP
php 需要掌握的东西 不做浮躁的人
2009/12/28 PHP
6个超实用的PHP代码片段
2015/08/10 PHP
smarty高级特性之过滤器的使用方法
2015/12/25 PHP
Jquery乱码的一次解决过程 图解教程
2010/02/20 Javascript
主页面中的两个iframe实现鼠标拖动改变其大小
2013/04/16 Javascript
jquery 提交值不为空的元素示例代码
2013/05/10 Javascript
js中键盘事件实例简析
2015/01/10 Javascript
vue2.0结合DataTable插件实现表格动态刷新的方法详解
2017/03/17 Javascript
JS实现两周内自动登录功能
2017/03/23 Javascript
Angular 4 指令快速入门教程
2017/06/07 Javascript
vue修改vue项目运行端口号的方法
2017/08/04 Javascript
详解如何去除vue项目中的#——History模式
2017/10/13 Javascript
vue实现点击选中,其他的不选中方法
2018/09/05 Javascript
IE8中jQuery.load()加载页面不显示的原因
2018/11/15 jQuery
vue中实现Monaco Editor自定义提示功能
2019/07/05 Javascript
vue实现全屏滚动效果(非fullpage.js)
2020/03/07 Javascript
[02:10]DOTA2 TI10勇士令状玩法及不朽Ⅰ展示:焕新世界,如你所期
2020/05/29 DOTA
python里使用正则表达式的组嵌套实例详解
2017/10/24 Python
APIStar:一个专为Python3设计的API框架
2018/09/26 Python
教你一步步利用python实现贪吃蛇游戏
2019/06/27 Python
Django stark组件使用及原理详解
2019/08/22 Python
使用OpCode绕过Python沙箱的方法详解
2019/09/03 Python
Pytorch之保存读取模型实例
2019/12/30 Python
Python开发之pip安装及使用方法详解
2020/02/21 Python
Python 定义只读属性的实现方式
2020/03/05 Python
Python常用外部指令执行代码实例
2020/11/05 Python
python中count函数知识点浅析
2020/12/17 Python
俄罗斯EPL钻石珠宝店:ЭПЛ
2019/10/22 全球购物
在校学生职业规划范文
2014/01/08 职场文书
红头文件任命书范本
2014/06/05 职场文书
80后婚前协议书范本
2014/10/24 职场文书
一个独生女的故事观后感
2015/06/04 职场文书
python 下载文件的几种方式分享
2021/04/07 Python
idea 在springboot中使用lombok插件的方法
2021/08/02 Java/Android