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


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 相关文章推荐
javascript 贪吃蛇实现代码
Nov 22 Javascript
jQuery实现可关闭固定于底(顶)部的工具条菜单效果
Nov 06 Javascript
javascript实现uploadify上传格式以及个数限制
Nov 23 Javascript
Bootstrap每天必学之导航条
Nov 27 Javascript
Bootstrap实现弹性搜索框
Jul 11 Javascript
jQuery validate插件功能与用法详解
Dec 15 Javascript
jquery dialog获取焦点的方法
Feb 09 Javascript
简单实现js选项卡切换效果
Feb 09 Javascript
Javascript(es2016) import和require用法和区别详解
Aug 11 Javascript
vue移动端监听滚动条高度的实现方法
Sep 03 Javascript
解决ng-repeat产生的ng-model中取不到值的问题
Oct 02 Javascript
koa+mongoose实现简单增删改查接口的示例代码
May 13 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
PHP去除数组中重复的元素并按键名排序函数
2008/08/18 PHP
提高PHP性能的编码技巧以及性能优化详细解析
2013/08/24 PHP
THINKPHP支持YAML配置文件的设置方法
2015/03/17 PHP
PHP的cookie与session原理及用法详解
2019/09/27 PHP
经常用到的JavasScript事件的翻译
2007/04/09 Javascript
分享一道笔试题[有n个直线最多可以把一个平面分成多少个部分]
2012/10/12 Javascript
TypeScript具有的几个不同特质
2015/04/07 Javascript
JavaScript 基础函数_深入剖析变量和作用域
2016/05/18 Javascript
NodeJS远程代码执行
2016/08/28 NodeJs
详解微信小程序胶囊按钮返回|首页自定义导航栏功能
2019/06/14 Javascript
vue 中 命名视图的用法实例详解
2019/08/14 Javascript
详解webpack引用jquery(第三方模块)的三种办法
2019/08/21 jQuery
[46:47]2014 DOTA2国际邀请赛中国区预选赛5.21 LGD-CDEC VS NE
2014/05/22 DOTA
python类参数self使用示例
2014/02/17 Python
eclipse创建python项目步骤详解
2019/05/10 Python
Python使用itchat模块实现群聊转发,自动回复功能示例
2019/08/26 Python
Python新手学习函数默认参数设置
2020/06/03 Python
PIP和conda 更换国内安装源的方法步骤
2020/09/21 Python
基于Python爬取股票数据过程详解
2020/10/21 Python
python实现b站直播自动发送弹幕功能
2021/02/20 Python
CSS3地图动态实例代码(圆圈向外扩散)
2018/06/15 HTML / CSS
HTML5输入框下拉菜单功能的示例代码
2020/09/08 HTML / CSS
美国户外生活方式品牌:Eddie Bauer
2016/12/28 全球购物
PatPat阿根廷:妈妈们的购物平台
2019/05/30 全球购物
亚洲航空公司官方网站:AirAsia
2019/11/25 全球购物
社区端午节活动方案
2014/01/28 职场文书
《李时珍夜宿古寺》教学反思
2014/04/09 职场文书
公司建议书怎么写
2014/05/15 职场文书
地球一小时宣传标语
2014/06/24 职场文书
小学开学标语
2014/07/01 职场文书
部门活动策划方案
2014/08/16 职场文书
自习课吵闹检讨书范文
2014/09/26 职场文书
官僚主义现象查摆问题整改措施
2014/10/04 职场文书
工会经费申请报告
2015/05/15 职场文书
致运动员的广播稿
2015/08/19 职场文书
2016党性教育学习心得体会
2016/01/21 职场文书