美文网首页
微信小程序一步一步获取UnionID,实现自动登录

微信小程序一步一步获取UnionID,实现自动登录

作者: layasntx | 来源:发表于2019-10-10 10:18 被阅读0次

    思路:

    1、小程序端获取用户ID,发送至后台
    2、后台查询用户ID,如果找到了该用户,返回Token,没找到该用户,保存到数据库,并返回Token

    小程序端如何获取用户ID:

    小程序端 wx.getUserInfo() 可以获取到用户信息

    wx.getUserInfo()
    其中 encryptedData 解密之后可以得到微信 UnionID,那么如何解密 encryptedData
    解密 encryptedData
    微信提供的解密 DEMO 包含4个版本:C++,Node,PHP,Python,Python需要安装pycryptodome。
    解密 encryptedData 需要 iv 和 session_key,获取 session_key 需要访问 auth.code2Session 接口
    auth.code2Session接口
    访问 auth.code2Session 接口需要 appid 和 appSecret,直接保存在前端无疑是非常危险的,正确的做法是:
    1、小程序端调用 wx.login() 获取 code,调用 wx.getUserInfo() 获取 encryptedData 和 iv,发送 code、encryptedData 和 iv 到后台,
    2、后台访问 auth.code2Session 接口,获取session_key, 使用 iv 和 session_key,解密 encryptedData 获取 UnionID,依据 UnionID 查询数据库

    注意:调用 wx.getUserInfo() 需要用户授权

    app.js

    App({
      data: {
        canIUse: wx.canIUse('button.open-type.getUserInfo'), //版本兼容
        serverHost: 'http://localhost:8090/',
        token: null,
        userInfo: null,
      },
      onLaunch: function() {
        this.autoLogin();
      },
      //自动登录
      autoLogin: function() {
        var that = this;
        //查有没有缓存 token, 缓存可能被清空
        wx.getStorage({
          key: 'token',
          // 有token, 到后台检查 token 是否过期
          success(res) {
            console.log("token: " + res.data);
            that.checkToken(res.data);
          },
          // 没有缓存token, 需要登录
          fail(e) {
            console.log("not saved token, login...");
            that.userLogin();
          }
        })
      },
      //检查 token 是否过期
      checkToken: function(token) {
        var that = this;
        wx.request({
          url: that.data.serverHost + 'user/token/check',
          method: 'POST',
          data: {
            token: token,
          },
          header: {
            "Content-Type": "application/x-www-form-urlencoded"
          },
          success(res) {
            if (res.data.code == 10000) {
              console.log("token not expired");
            } else {
              console.log("token expired, refresh...");
              // 去后台刷新 token
              that.refreshToken();
            }
          },
          fail(e) {
            console.error(e);
            console.error("【check token failed, login...】");
            // 走登录流程
            that.userLogin();
          }
        })
      },
      //刷新 token
      refreshToken: function() {
        var that = this;
        //查有没有缓存 refreshtoken, 缓存可能被清空
        wx.getStorage({
          key: 'refreshtoken',
          // 有refreshtoken, 到后台刷新 token
          success(res) {
            console.log("refreshtoken: " + res.data);
            that.refreshToken2(res.data);
          },
          // 没有缓存refreshtoken, 需要登录
          fail(e) {
            console.log("not saved refreshtoken, login...");
            that.userLogin();
          }
        })
      },
      //去后台刷新 token
      refreshToken2: function(refreshtoken) {
        var that = this;
        wx.request({
          url: that.data.serverHost + 'user/token/refresh',
          method: 'POST',
          data: {
            refreshtoken: refreshtoken,
          },
          header: {
            "Content-Type": "application/x-www-form-urlencoded"
          },
          success(res) {
            if (res.data.code == 10000 && res.data.data.token) {
              console.log(res.data.data.token);
              that.saveToken(res.data.data.token)
            } else {
              console.log("refresh token failed, login...");
              that.userLogin();
            }
          },
          fail(e) {
            console.error(e);
            console.error("【refresh token failed, login...】");
            that.userLogin();
          }
        })
    
      },
      // wx.login 获取 code,
      // wx.getUserInfo 获取 encryptedData 和 iv
      // 去后台换取 token
      userLogin: function() {
        var that = this;
        // wx.login 获取 code,
        wx.login({
          success(res) {
            if (res.code) {
              console.log("code:" + res.code);
              that.userLogin2(res.code);
            } else {
              console.error("【wx login failed】");
            }
          },
          fail(e) {
            console.error(e);
            console.error("【wx login failed】");
          }
        })
    
      },
      // 检查授权, wx.getUserInfo
      userLogin2: function(code) {
        var that = this;
        // 检查是否授权
        wx.getSetting({
          success(res) {
            // 已经授权, 可以直接调用 getUserInfo 获取头像昵称
            if (res.authSetting['scope.userInfo']) {
              that.userLogin3(code);
            } else { //没有授权 
              if (that.data.canIUse) {
                // 高版本, 需要转到授权页面 
                wx.navigateTo({
                  url: '/pages/auth/auth?code=' + code,
                });
              } else {
                //低版本, 调用 getUserInfo, 系统自动弹出授权对话框
                that.userLogin3(code);
              }
            }
          }
        })
      },
      // wx.getUserInfo
      userLogin3: function(code) {
        var that = this;
        wx.getUserInfo({
          success: function(res) {
            console.log(res);
            if (res.userInfo) {
              that.data.userInfo = res.userInfo;
            }
            if (code && res.encryptedData && res.iv) {
              that.userLogin4(code, res.encryptedData, res.iv);
            } else {
              console.error("【wx getUserInfo failed】");
            }
          },
          fail(e) {
            console.error(e);
            console.error("【wx getUserInfo failed】");
          }
        })
      },
      //去后台获取用户 token
      userLogin4: function(code, data, iv) {
        var that = this;
        wx.request({
          url: that.data.serverHost + 'user/wxlogin',
          method: 'POST',
          data: {
            code: code,
            data: data,
            iv: iv,
          },
          header: {
            "Content-Type": "application/x-www-form-urlencoded"
          },
          success(res) {
            console.log(res)
            if (res.data.code == 10000) {
              if (res.data.data.token) {
                console.log(res.data.data.token);
                that.saveToken(res.data.data.token);
              } else {
                console.error("【userLogin token failed】")
              }
              if (res.data.data.refreshtoken) {
                console.log(res.data.data.refreshtoken);
                wx.setStorage({
                  key: "refreshtoken",
                  data: res.data.data.refreshtoken
                });
              } else {
                console.error("【userLogin refreshtoken failed】")
              }
            } else {
              console.error("【userLogin failed】")
            }
    
          },
          fail(e) {
            console.error(e);
            console.error("【userLogin failed】");
          }
        })
      },
      // 保存 token
      saveToken: function(token) {
        this.data.token = token;
        wx.setStorage({
          key: "token",
          data: token
        });
      },
      getUserInfo: function(call) {
        var that = this
        if (this.data.userInfo) {
          call(this.data.userInfo);
        } else {
          // 先从缓存查 userInfo, 缓存可能被清空,
          wx.getStorage({
            key: 'userInfo',
            success(res) {
              console.log(res.data);
              call(res.data);
              that.setData({
                userInfo: res.data
              });
            },
            fail(res) {
              console.log("not save userInfo, wx getUserInfo...");
              wx.getUserInfo({
                success(res) {
                  console.log(userInfo);
                  if (res.userInfo) {
                    call(res.userInfo);
                    that.setData({
                      userInfo: res.userInfo
                    });
                  }
                }
              })
            }
          })
        }
      },
    })
    

    auth.js

    const app = getApp()
    Page({
      data: {
        userInfo: {
          avatarUrl: '/image/user_avarta.png',
          nickName: '昵称'
        },
      },
      onLoad: function(param) {
        this.data.code = param.code
      },
      getUserInfo: function(res) {
        console.log(res.detail)
        app.data.userInfo = res.detail.userInfo
        this.setData({
          userInfo: res.detail.userInfo,
        })
        if (this.data.code && res.detail.encryptedData && res.detail.iv) {
          app.userLogin4(this.data.code, res.detail.encryptedData, res.detail.iv)
        } else {
          console.error("【getUserInfo失败】");
        }
      }
    })
    

    授权页面:auth.wxml

    <view class="container">
      <text class="prompt">授权登录</text>
      <view class="userinfo">
        <image class="userinfo-avatar" src="{{userInfo.avatarUrl}}" mode="cover"></image>
        <text class="userinfo-nickname">{{userInfo.nickName}}</text>
      </view>
      <button open-type="getUserInfo" bindgetuserinfo="getUserInfo" type="primary"> 授权登录 </button>
    </view>
    

    后端代码

    后端使用Python + Django 框架实现:
    安装 requests ,发送Http请求
    安装 pycryptodome,解密

    pip install requests 
    pip install pycryptodome
    

    此处仅给出View的代码

    import hashlib
    import time
    import json
    
    import requests
    from django.conf import settings
    from django.http import JsonResponse
    from django.views import View
    from django_redis import get_redis_connection
    
    from user.models import UserInfo
    from utils.WXBizDataCrypt import WXBizDataCrypt
    
    
    class WxLoginView(View):
        def post(self, request):
            post = request.POST
            code = post.get('code')
            if not code:
                return JsonResponse({'code': 10001, 'msg': 'missing parameter: code'})
    
            url = "https://api.weixin.qq.com/sns/jscode2session?appid={0}&secret={1}&js_code={2}&grant_type=authorization_code" \
                .format(settings.WX_APP_ID, settings.WX_APP_KEY, code)
            # 发送GET请求
            wx_res = requests.get(url)
            errcode = wx_res['errcode'] if 'errcode' in wx_res else None
            if errcode:
                return JsonResponse({'code': 13001, 'msg': 'wx_auth.code2Session:' + wx_res.errmsg})
    
            wx_session = json.loads(wx_res.text)
            unionid = wx_session['unionId'] if 'unionId' in wx_session else None
            decrypt = False
            user = None
            if not unionid:
                decrypt = True
            else:
                user = UserInfo.objects.get(wx_unionid=unionid)
                # 判断用户是否第一次登录
                if not user:
                    decrypt = True
            # 解密 encryptedData
            if decrypt:
                encrypted_data = post.get('data')
                iv = post.get('iv')
                if not all([encrypted_data, iv]):
                    return JsonResponse({'code': 10001, 'msg': 'missing parameter: data,iv'})
    
                session_key = wx_session['session_key'] if 'session_key' in wx_session else None
                if not session_key:
                    return JsonResponse({'code': 13001, 'msg': 'wx_auth.code2Session:' + 'no session_key'})
    
                pc = WXBizDataCrypt(settings.WX_APP_ID, session_key)
                wx_user = pc.decrypt(encrypted_data, iv)
                unionid = wx_user['unionId']
    
                user = UserInfo.objects.get(wx_unionid=unionid)
                # 判断用户是否第一次登录
                if not user:
                    # 微信用户第一次登录,创建用户
                    username = 'wx_' + unionid
                    nickname = wx_user['nickName']
                    avatar = wx_user['avatarUrl']
                    gender = wx_user['gender']
                    country = wx_user['country']
                    province = wx_user['province']
                    city = wx_user['city']
                    language = wx_user['language']
                    user = UserInfo.objects.create(username=username,
                                                   wx_unionid=unionid,
                                                   nickname=nickname,
                                                   avatar=avatar,
                                                   gender=gender,
                                                   country=country,
                                                   province=province,
                                                   city=city,
                                                   language=language,
                                                   )
    
            # 生成 token
            md5 = hashlib.md5()
            bstr = (unionid + str(time.time())).encode(encoding='utf-8')
            md5.update(bstr)
            token = md5.hexdigest()
            bstr = ("refresh" + unionid + str(time.time())).encode(encoding='utf-8')
            md5.update(bstr)
            refreshtoken = md5.hexdigest()
            # 存入Redis
            conn = get_redis_connection('default')
            conn.set(token, unionid)
            conn.expire(token, 5)
            conn.set(refreshtoken, unionid)
            conn.expire(refreshtoken, 3600 * 24 * 7)
            data = {'token': token, 'expire': 3600, 'refreshtoken': refreshtoken}
            return JsonResponse({'code': 10000, 'msg': 'ok', 'data': data})
    
    
    class TokenCheckView(View):
        def post(self, request):
            post = request.POST
            token = post.get('token')
            if not token:
                return JsonResponse({'code': 10001, 'msg': 'missing parameter: token'})
    
            conn = get_redis_connection('default')
            exist = conn.ttl(token)
            if exist < 0:
                return JsonResponse({'code': 10200, 'msg': 'token expired'})
            else:
                return JsonResponse({'code': 10000, 'msg': 'ok'})
    
    
    class TokenRefreshView(View):
        def post(self, request):
            post = request.POST
            refreshtoken = post.get('refreshtoken')
            if not refreshtoken:
                return JsonResponse({'code': 10001, 'msg': 'missing parameter: refreshtoken'})
    
            conn = get_redis_connection('default')
            unionid = conn.get(refreshtoken)
            if not unionid:
                return JsonResponse({'code': 10200, 'msg': 'refreshtoken expired'})
    
            # 生成 token
            md5 = hashlib.md5()
            bstr = unionid + str(time.time()).encode(encoding='utf-8')
            md5.update(bstr)
            token = md5.hexdigest()
            conn.set(token, unionid)
            conn.expire(token, 5)
            data = {'token': token}
            return JsonResponse({'code': 10000, 'msg': 'ok', 'data': data})
    

    注意:

    如果解压之后,没有获取到 UnionID ,请登录 微信开放平台 => 管理中心 => 绑定小程序

    源码下载

    相关文章

      网友评论

          本文标题:微信小程序一步一步获取UnionID,实现自动登录

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