美文网首页
微信小程序登录会话流程学习

微信小程序登录会话流程学习

作者: 极乐叔 | 来源:发表于2018-05-18 11:24 被阅读190次

    背景

    微信小程序的使用可以快速的基于场景进行用户圈的建立推广,其中根据业务需要使用用户信息以及授权过程,主要用到的有微信在开发工具中已经封装好的 api 函数:wx.loginwx.getUserInfowx.checkSessionwx.getStorageSyncwx.request

    1. 关键词

    • 开放数据
      调用 wx.getUserInfo(此接口有调整,使用该接口将不再出现授权弹窗,使用 <button open-type="getUserInfo" ></button>引导用户主动进行授权操作) 成功时 返回的用户开放数据,可以在任意时候获取,但其中的加密数据encryptedData需要使用用户的session_key作为秘钥进行解密才能得到原始完整数据。为了防止返回的数据被人篡改伪造,开发者可以将 signature、rawData 发送到开发者服务器进行校验。服务器利用用户对应的 session_key 使用相同的算法计算出签名 signature2 ,比对 signature 与 signature2 即可校验数据的完整性。返回结果主要包括:
    参数             类型                          说明
        userInfo         OBJECT        用户信息对象,不包含 openid 等敏感信息
        rawData          String        不包括敏感信息的原始数据字符串,用于计算签名。
        signature        String        使用 sha1( rawData + sessionkey ) 得到字符串,用于校验用户信息,参考文档 signature。
        encryptedData    String        包括敏感数据在内的完整用户信息的加密数据,详细见加密数据解密算法
        iv               String        加密算法的初始向量,详细见加密数据解密算法
    //调用成功后返回结果示例
    {
    encryptedData:"6exNeBvACX+EpzLNF2vYRhM0Z1tNZnBOYP0Qh6jQ4Ofv+69qY/RGVAB34aj4f364mkjAAD5pgDDJ41hGkkr/IaHJGioI3EhGSEQyrAfE1mPovV9s6Prg4rKedfBUerG4jEgXbS0GeERMtJ15tqEGz7qzcA49c1D1obZTuneImZq1vB1lXo3oUbnmK7LUAxQcgCQA==",
    errMsg:"getUserInfo:ok",
    iv:"X6PuFoueBpfgyjV9l9D6A==",
    rawData:"{"nickName":"xxx","gender":1,"language":"zh_CN","city":"","province":"","country":"Iceland","avatarUrl":"https://wx.qlogo.cn/mmsen/vi_s/0daYVYsscPNxEvALzBTsJarXwUmlxjZJZMHPM6NNLegH4wLRcRlsA1x4bsJg/132"}",
    signature:"c32wba325164258b9a332b6fdsdew2e520081348",
    userInfo:
        {
            avatarUrl:"https://wx.qlogo.cn/mmsen/vi_s/0daYVYsscPNxEvALzBTsJarXwUmlxjZJZMHPM6NNLegH4wLRcRlsA1x4bsJg/132",
            city:"",
            country:"Iceland",
            gender:1,
            language:"zh_CN",
            nickName:"xxx",
            province:""
         }
    }
    
    
    • 敏感数据获取(加密数据encryptedData签名signature)

    对于encryptedData,开发者如需要获取敏感数据,需要对接口返回的加密数据( encryptedData )进行对称解密
    也就是需要在开发者服务器上利用session_key解密获得的encryptedData,其结构如下:

    {
        "openId": "OPENID",
        "nickName": "NICKNAME",
        "gender": GENDER,
        "city": "CITY",
        "province": "PROVINCE",
        "country": "COUNTRY",
        "avatarUrl": "AVATARURL",
        "unionId": "UNIONID",
        "watermark":                      //数据水印
        {
            "appid":"APPID",              //敏感数据归属appid,开发者可校验此参数与自身appid是否一致  
            "timestamp":TIMESTAMP         //敏感数据获取的时间戳, 开发者可以用于数据时效性校验
        }
    }
    
    
    • 自定义登录态
      环境:开发者服务器上
      数据:利用接口调用获得的开放数据或者解密后的敏感数据以及session_key
      操作:利用数据建立用户在小程序的唯一标识,以及发送自己定义的登录状态信息记录session(也可以是其他的信息记录)给客户端,session_key作为秘钥,必须保存在开发者服务器上(以前接口https://api.weixin.qq.com/sns... 可以在小程序开发设置--服务器配置里设置为合法地址,现在不行了,只能使用第三方服务器进行接口调用)
      使用: 用户非首次登陆——>查询本地缓存中的自定义session——>获取到session向开发者服务器查询——>服务器上此session未过期可用——>用户可正常业务请求(这只是登陆情况比较普遍正常的一种)

    2.登录

    官方给的图示:(大概步骤可以看官方文档)

    </figure>

    基本:其中使用wx.login()可以获得开发者服务器向微信接口服务器请求获得session_key等数据时所需要的参数code,开发者服务器以code+appid+app secret(小程序秘钥,管理后台可以获得)换取 用户唯一标识openid 和 会话密钥session_key。但每一次调用wx.login()都会更新微信接口服务器上的session_key。

    • 首次登录
      获取登录凭证code——>发送至开发者服务器构造接口( appid + app secret + code) 请求 微信接口服务器数据——>返回用户数据密钥session_key 和唯一标识openid给开发者服务器——>根据session_key 和 openid等生成自定义的第三方session,存储session_key至开发者服务器,不能将session_key发送至客户端,防止敏感数据泄露——>将第三方自定义登录信息session发送至客户端——>保存自定义第三方登录态信息session到本地缓存,登录成功,可以设置一个登录标识flag,使flag=true表示首次登录成功,进行相关的处理操作。
      其中,登录校验凭证接口(在开发者服务器中调用,小程序中无法使用此接口)为:
      https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code

      appid 小程序唯一标识
      secret 小程序的 app secret
      js_code 是wx.login登录时获取的凭证code
      grant_type 填写为 authorization_code

      具体伪代码如下:

    /**
     * 我把这一步封装成一个函数,以后再调用那些需要登录后才有权限访问的后台服务时.
     * 可以使用使用这个函数操作,基本逻辑,还可以进行扩展
     * @url      : 开发者服务器登录服务接口 可设置默认值
     * @key      : 存入本地缓存的第三方自定义登录session的键值
     * @callback : 需要用到登录服务的回调函数(或函数集合)
     */
    function  Login(key,url,callback){
         // 首先使用wx.login获取登录凭证code并发送给开发者服务器
         wx.login({
          success: function(res) {
            if (res.code) {
              //发送code给开发者服务器以获取session_key和openid
              wx.request({
                url:url,
                data: {
                  code: res.code
                },
                //发送至开发者服务器成功
                success: function(res) {
                    //开发者服务器获得session_key和openid成功并返回了
                    //第三方自定义session或者解密数据等json格式数据res给客户端
                    if(res.has3rdSession){
                        //登录成功,将第三方session存入本地缓存
                        console.log("登录成功")
                        wx.setStorageSync(key,res.3rdSession)
                        //设置登录标识为true
                        this.setData{
                            flag:true
                        }
                        //进行后续回调处理
                        callback(res)
                        ....
                    }
                    else{
                        //没有返回第三方自定义session,登录失败
                        console.log("登录失败")
                        ....
                    }
                }
              })
            } else {
              //没有获取到登录凭证code,登录失败
              console.log('登录失败!' + res.errMsg)
              ....
            }
          }
        })
    }
    
    
    • 非首次登录wx.getStorageSync()查询本地缓存中是否有我们自定义的session登录态信息,有的话证明不是第一次登录,此时先查询微信接口服务器上的用户session_key是否过期,如果过期了,不管我们自己定义的session如何,都要重新登录,设置登录标识flag为false;如果没有过期,那么可以向开发者服务器发送sessio查询用户信息,如果开发者服务器告诉我们自己定义的session过期了,那重新登录,设置登录标识flag为false,如果没有过期,则查询是否有这个session来获得用户的相关信息,具体伪代码如下:
    /**
     - 我把这一步封装成一个函数,以后再调用那些需要登录后才有权限访问的后台服务时.
     - 可以使用使用这个函数操作,基本逻辑,还可以进行扩展
     - @url      : 开发者服务器服务接口
     - @key      : 自己定义的session存储在本地缓存中的键名
     - @callback : 需要用到登录服务的回调函数(或函数集合)
     - @others   : 其他需要用到的数据等参数
     */
    function doRequestWithSession(key,url,callback,others)
    {
        try {
          var value = wx.getStorageSync(key)
          if (value) {
              // 存在自定义登录session,非首次登录
              // 查询 用户密钥session_key是否过期
              wx.checkSession({
                  success: function(){
                    //session_key 未过期,并且在本生命周期一直有效
                    //向开发者服务器发送自定义session查询用户信息
                    wx.request({
                          url: url, 
                          data: value,  //本地缓存的自定义Session值 可以加上其他需要的数据others等
                          success: function(res) {
                             if(res.hasSession){
                                //查询成功,开发者服务器上有我们自定义的session
                                //根据返回结果进行对应的操作,先判断session是否过期
                                //如果我们自定义的session已经过期
                                if(res.expired){
                                    console.log("session过期,重新登录")
                                    //设置登录标识为false
                                    this.setData{
                                        flag:false
                                    }
                                    wx.login() //重新登录 或者 返回错误码
                                    ....
                                }
                                else{
                                //没有过期,返回的结果则可以是用户的相关信息
                                //进行相关操作 登录成功
                                   console.log("欢迎回来")
                                   callback(res)
                                   ....  
                                }    
                              }
                              else
                              {
                                  //开发者服务器上没有我们自定义的session,重新登录
                                  console.log("无此session,重新登录")
                                  //设置登录标识为false
                                    this.setData{
                                        flag:false
                                    }
                                  wx.login() //重新登录 或者 返回错误码
                                  ....
                              }             
                          }
                    })
                  },
                  fail: function(){
                    // session_key 已经失效,需要重新执行登录流程
                    //设置登录标识为false
                    this.setData{
                        flag:false
                    }
                    wx.login() //重新登录 或者 返回错误码
                    ....
                  }
              })    
          }
          else{
              //本地缓存中没有自定义的session登录态信息,则进行首次登录或者后续处理
              //设置登录标识为false
              this.setData{
                 flag:false
              }
              wx.login() //登录 或者 返回错误码
              ....
          }
        } catch (e) {
          // 异常处理
          ....
        }
     }
    
    
    • 条件判断登录
      需要用到登录权限的服务时,可以使用条件判断
      伪代码:
    if(this.data.flag){
        //已经登录过,非首次登录
        doRequestWithSession(key,url,callback,others)
        ....
    }
    else{
        //没有登录过,可以进一步检查本地缓存是否有3rdSession
        do other things
        ....
    }
    
    

    3.获取安全的原始数据

    在第一部分已经说过敏感数据和开放数据,可以用 wx.getUserInfo获得开放数据,但是没有登录获取解密密钥session_key是无法解密敏感数据的,所以开放数据中,没有登录的话,我们只能获得用户昵称、头像、城市、性别、国籍等,并没有可以标识用户的唯一id。所以如何拿到我们的安全的原始数据,只有:获取开放数据——>获得登录权限,开发者服务器获得密钥session_key校验签名解密数据——>返回解密数据,包含有openid。伪代码:

    a.wxml 文件:

    //wx.getUserInfo接口有调整,使用该接口将不再出现授权弹窗,要使用 <button>组件引导用户主动进行授权操作,使用此接口要注意:
    //当用户未授权过,调用该接口将直接报错;当用户授权过,可以使用该接口获取用户信息
    //例子
    <button open-type='getUserInfo' bindgetuserinfo='showUserInfo'>登录</button>
    
    

    a.js 文件:

    Page({
      data:{},
      ...
      //点击登录按钮后触发
      showUserInfo:function(e){
        //获取需要解密的数据
        var encryptedData = e.detail.encryptedData
        /*
        *@key      第三方自定义登录session存在本地缓存的key值
        *@url      解密敏感数据的开发者服务器服务接口
        *@callback 获得解密数据后的回调函数
        *@encryptedData 当前获得的未解密数据
        */
        doRequestWithSession(key,url,callback,encryptedData)   
      },
    })
    
    

    4.后台

    后台的服务有很多,可以做API平台进行统一管理:

    1. 登录接口 :domain/api/login
    2. 验证签名解密接口 :domain/api/decrypt
    3. 其他

    其中的解密代码示例 (官方4种语言版本:Python、php、c++、node):

    <?php 
    class WXBizDataCrypt
    {
        private $appid;
        private $sessionKey;
    
        /**
         * 构造函数
         * @param $sessionKey string 用户在小程序登录后获取的会话密钥
         * @param $appid string 小程序的appid
         */
        public function __construct( $appid, $sessionKey)
        {
            $this->sessionKey = $sessionKey;
            $this->appid = $appid;
        }
    
        /**
         * 检验数据的真实性,并且获取解密后的明文.
         * @param $encryptedData string 加密的用户数据
         * @param $iv string 与用户数据一同返回的初始向量
         * @param $data string 解密后的原文
         *
         * @return int 成功0,失败返回对应的错误码
         */
        public function decryptData( $encryptedData, $iv, &$data )
        {
            if (strlen($this->sessionKey) != 24) {
                return ErrorCode::$IllegalAesKey;
            }
            $aesKey=base64_decode($this->sessionKey);
    
            if (strlen($iv) != 24) {
                return ErrorCode::$IllegalIv;
            }
            $aesIV=base64_decode($iv);
    
            $aesCipher=base64_decode($encryptedData);
    
            $result=openssl_decrypt( $aesCipher, "AES-128-CBC", $aesKey, 1, $aesIV);
    
            $dataObj=json_decode( $result );
            if( $dataObj  == NULL )
            {
                return ErrorCode::$IllegalBuffer;
            }
            if( $dataObj->watermark->appid != $this->appid )
            {
                return ErrorCode::$IllegalBuffer;
            }
            $data = $result;
            return ErrorCode::$OK;
        }
    
    }
    
    $appid = 'wx4f4bc4dec97d474b';
    $sessionKey = 'tiihtNczf5v6AKRyjwEUhQ==';
    
    $encryptedData="CiyLU1Aw2KjvrjMdj8YKliAjtP4gsMZM
                    QmRzooG2xrDcvSnxIMXFufNstNGTyaGS
                    9uT5geRa0W4oTOb1WT7fJlAC+oNPdbB+
                    3hVbJSRgv+4lGOETKUQz6OYStslQ142d
                    NCuabNPGBzlooOmB231qMM85d2/fV6Ch
                    evvXvQP8Hkue1poOFtnEtpyxVLW1zAo6
                    /1Xx1COxFvrc2d7UL/lmHInNlxuacJXw
                    u0fjpXfz/YqYzBIBzD6WUfTIF9GRHpOn
                    /Hz7saL8xz+W//FRAUid1OksQaQx4CMs
                    8LOddcQhULW4ucetDf96JcR3g0gfRK4P
                    C7E/r7Z6xNrXd2UIeorGj5Ef7b1pJAYB
                    6Y5anaHqZ9J6nKEBvB4DnNLIVWSgARns
                    /8wR2SiRS7MNACwTyrGvt9ts8p12PKFd
                    lqYTopNHR1Vf7XjfhQlVsAJdNiKdYmYV
                    oKlaRv85IfVunYzO0IKXsyl7JCUjCpoG
                    20f0a04COwfneQAGGwd5oa+T8yO5hzuy
                    Db/XcxxmK01EpqOyuxINew==";
    
    $iv = 'r7BXXKkLb8qrSNn05n0qiA==';
    
    $pc = new WXBizDataCrypt($appid, $sessionKey);
    $errCode = $pc->decryptData($encryptedData, $iv, $data );
    
    if ($errCode == 0) {
        print($data . "\n");
    } else {
        print($errCode . "\n");
    }
    ?>
    
    

    接触不深 ,权当记录。错漏处还望多多指正~

    作者:linkin
    链接:微信小程序登录会话流程学习 - 个人文章 - SegmentFault 思否
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

    参考阅读:

    1. 一斤代码:微信小程序中用户登录和登录态维护
    2. 官方文档

    相关文章

      网友评论

          本文标题:微信小程序登录会话流程学习

          本文链接:https://www.haomeiwen.com/subject/lhtgdftx.html