微信小程序一步一步獲取UnionID,實現自動登陸

思路:

一、小程序端獲取用戶ID,發送至後臺
二、後臺查詢用戶ID,若是找到了該用戶,返回Token,沒找到該用戶,保存到數據庫,並返回Token前端

小程序端如何獲取用戶ID:

小程序端 wx.getUserInfo() 能夠獲取到用戶信息

其中 encryptedData 解密以後能夠獲得微信 UnionID,那麼如何解密 encryptedData

微信提供的解密 DEMO 包含4個版本:C++,Node,PHP,Python,Python須要安裝pycryptodome。
解密 encryptedData 須要 iv 和 session_key,獲取 session_key 須要訪問 auth.code2Session 接口
auth.code2Session接口
訪問 auth.code2Session 接口須要 appid 和 appSecret,直接保存在前端無疑是很是危險的,正確的作法是:
一、小程序端調用 wx.login() 獲取 code,調用 wx.getUserInfo() 獲取 encryptedData 和 iv,發送 code、encryptedData 和 iv 到後臺,
二、後臺訪問 auth.code2Session 接口,獲取session_key, 使用 iv 和 session_key,解密 encryptedData 獲取 UnionID,依據 UnionID 查詢數據庫git

注意:調用 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,解密github

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 ,請登陸 微信開放平臺 => 管理中心 => 綁定小程序redis

源碼下載

相關文章
相關標籤/搜索