一、小程序端获取用户ID,发送至后台
二、后台查询用户ID,若是找到了该用户,返回Token,没找到该用户,保存到数据库,并返回Token前端
小程序端 wx.getUserInfo() 能够获取到用户信息
其中 encryptedData 解密以后能够获得微信 UnionID,那么如何解密 encryptedData
微信提供的解密 DEMO 包含4个版本:C++,Node,PHP,Python,Python须要安装pycryptodome。
解密 encryptedData 须要 iv 和 session_key,获取 session_key 须要访问 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
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 }); } } }) } }) } }, })
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失败】"); } } })
<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
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