django + jwt 完成微信小程序身份验证,步骤如下
环境说明:
1、小程序只需要拿到openid,其他信息不存储。
2、Django自带的User类不适合。需要对django user 进行扩展
流程
1.使用微信小程序登录和获取用户信息Api接口
2.把Api获取的用户资料和code发送给django后端
3.通过微信接口把code换取成openid
4.后端将openid作为用户名和密码
5.后端通过JSON web token方式登录,把token和用户id传回小程序
6.小程序将token和用户id保存在storage中
7.下次请求需要验证用户身份的页面时,在请求中加入token这个字段
微信小程序前端框架使用iview-weapp
https://github.com/TalkingData/iview-weapp
第一步注册微信小程序
第二步下载微信开发工具
http://www.ionic.wang/weixin/devtools/download.html
第三步新建小程序项目
项目结构:
修改app.js
新增小程序登录页面
打开项目pages目录-->login 文件夹
修改login.js login.json login.wxml login.wxss
login.js 代码如下
// pages/login/login.js const app = getApp() const { $Toast } = require(\'../../dist/base/index\') const { $Message } = require(\'../../dist/base/index\'); Page({ /** * 页面的初始数据 */ data: { yhxyVisible: false, ystkVisible: false }, /** * 生命周期函数--监听页面加载 */ onLoad: function (options) { wx.getUserInfo({ success: function (res) { var userInfo = res.userInfo var nickName = userInfo.nickName console.log(nickName) app.globalData.userInfo.nickName = nickName var avatarUrl = userInfo.avatarUrl app.globalData.userInfo.avatarUrl = avatarUrl var gender = userInfo.gender //性别 0:未知、1:男、2:女 app.globalData.userInfo.gender = gender var province = userInfo.province var city = userInfo.city var country = userInfo.country } }) }, /** * 生命周期函数--监听页面初次渲染完成 */ onReady: function () { }, /** * 生命周期函数--监听页面显示 */ onShow: function () { }, /** * 生命周期函数--监听页面隐藏 */ onHide: function () { }, /** * 生命周期函数--监听页面卸载 */ onUnload: function () { }, /** * 页面相关事件处理函数--监听用户下拉动作 */ onPullDownRefresh: function () { }, /** * 页面上拉触底事件的处理函数 */ onReachBottom: function () { }, /** * 用户点击右上角分享 */ onShareAppMessage: function () { }, //手机号登录 mobileLogin(e) { $Toast({ content: \'登录中...\', type: \'loading\', duration: 0, mask: false }); console.log(e.detail.errMsg) console.log(e.detail.iv) console.log(e.detail.encryptedData) wx.login({ success: res => { console.log(res) //请求后端换取openid的接口 wx.request({ url: \'http://199394.wezoz.com/landlorde/app/mobileLogin/\', method: \'POST\', data: { //将code和用户基础信息传到后端 jscode: res.code, iv: e.detail.iv, encryptedData: e.detail.encryptedData, nickname: app.globalData.userInfo.nickName, avatar_url: app.globalData.userInfo.avatarUrl, gender: app.globalData.userInfo.gender }, success: res => { //获取到openid作为账号 console.log("res=============", res) console.log(app.globalData.userInfo) if (res.data.code == 200 && res.data.msg == "ok") { //this.reFreshUserProfile() wx.setStorageSync(\'token\', res.data.token) app.globalData.isLogin = true app.globalData.hasUserInfo = true app.globalData.token = res.data.token $Toast.hide(); wx.redirectTo({ url: \'../index/index?id=1\' }) } else { wx.showToast({ title: \'网络发生错误请稍后再试!\', icon: \'error\', duration: 2000 }) } } }) } }) }, //微信登录 wxlogin() { $Toast({ content: \'登录中...\', type: \'loading\', duration: 0, mask: false }); wx.login({ success: res => { console.log(res) //请求后端换取openid的接口 wx.request({ url: \'http://199394.wezoz.com/landlorde/app/wxlogin/\', method: \'POST\', data: { //将code传到后端 jscode: res.code }, success: res => { //获取到openid作为账号 console.log("res=============", res) console.log(app.globalData.userInfo) if (res.data.code == 200 && res.data.msg == "ok") { //this.reFreshUserProfile() wx.setStorageSync(\'token\', res.data.token) app.globalData.isLogin = true app.globalData.hasUserInfo = true app.globalData.token = res.data.token $Toast.hide(); wx.redirectTo({ url: \'../index/index?id=1\' }) } else { wx.showToast({ title: \'登录失败请稍后再试!\', icon: \'error\', duration: 2000 }) } } }) } }) }, yhxy() { this.setData({ yhxyVisible: true }); }, yhxyok() { this.setData({ yhxyVisible: false }); }, yhxycancel() { this.setData({ yhxyVisible: false }); }, ystk() { this.setData({ ystkVisible: true }); }, ystkok() { this.setData({ ystkVisible: false }); }, ystkcancel() { this.setData({ ystkVisible: false }); }, })
login.json
{ "navigationBarTitleText": "登录", "usingComponents": { "i-button": "../../dist/button/index", "i-panel": "../../dist/panel/index", "i-avatar": "../../dist/avatar/index", "i-row": "../../dist/row/index", "i-col": "../../dist/col/index", "i-toast": "../../dist/toast/index", "i-modal": "../../dist/modal/index", "i-message": "../../dist/message/index" } }
login.wxml
<!--pages/login/login.wxml--> <view class="container"> <image class="logStyle" src="http://img11.360buyimg.com/mobilecms/s700x256_jfs/t20539/229/82605291/66559/df347eb5/5af96a18N9451b1a1.jpg"></image> <i-button bindgetphonenumber="mobileLogin" style="background: #ff6700;" class="btnLoginStyle" open-type="getPhoneNumber" type="warning" shape="circle" size="large">手机快捷登录</i-button> <text class="weChatLogin" bindtap="wxlogin">微信登录</text> </view> <view class="company"> <view>登录/注册代表您已阅读并同意 <text class="descStyle" bindtap="yhxy">用户协议</text>和 <text class="descStyle" bindtap="ystk">隐私条款</text> </view> </view> <i-toast id="toast" /> <i-modal title="用户协议" visible="{{ yhxyVisible }}" bind:ok="yhxyok" bind:cancel="yhxycancel"> <view>1.用户协议用户协议用户协议</view> <view>2.用户协议用户协议用户协议</view> <view>3.用户协议用户协议用户协议</view> </i-modal> <i-modal title="隐私条款" visible="{{ ystkVisible }}" bind:ok="ystkok" bind:cancel="ystkcancel"> <view>1.隐私条款隐私条款隐私条款</view> <view>2.隐私条款隐私条款隐私条款</view> <view>3.隐私条款隐私条款隐私条款</view> </i-modal>
login.wxss
/* pages/login/login.wxss */ .item { height: 100px; text-align: center; } .container { display: block; width: 200px; height: 100px; margin: 0 auto; position: relative; text-align: center; } .logStyle { width: 100px; height: 100px; background-color: #eeeeee; margin-top: 90rpx; margin-bottom: 50rpx; border-radius: 50%; } .i-btn-warning{ background: #ff6700 !important; } .descStyle { color: #ff6700; } .company { position: absolute; bottom: 30rpx; width: 100%; display: flex; justify-content: center; font-size: 12px; } .weChatLogin{ font-size: 14px; }
第四步新建django项目
可参照微信开发入门篇一 https://www.cnblogs.com/wangcongxing/p/11546780.html
第五步处理微信登录
打开django项目,结构如下
authentication.py
from rest_framework import exceptions from rest_framework.authentication import BaseAuthentication from rest_framework_jwt.settings import api_settings class UserAuthentication(BaseAuthentication): def authenticate(self, request): if \'token\' in request.data: try: token = request.data[\'token\'] jwt_decode_handler = api_settings.JWT_DECODE_HANDLER user_dict = jwt_decode_handler(token) return (user_dict, token) except Exception as ex: raise exceptions.AuthenticationFailed(detail={\'code\': 401, \'msg\': \'skey已过期\'}) else: raise exceptions.AuthenticationFailed(detail={\'code\': 400, \'msg\': \'缺少token\'}) def authenticate_header(self, request): return \'skey\'
WXBizDataCrypt.py
import base64 import json from Crypto.Cipher import AES class WXBizDataCrypt: def __init__(self, appId, sessionKey): self.appId = appId self.sessionKey = sessionKey def decrypt(self, encryptedData, iv): # base64 decode sessionKey = base64.b64decode(self.sessionKey) encryptedData = base64.b64decode(encryptedData) iv = base64.b64decode(iv) cipher = AES.new(sessionKey, AES.MODE_CBC, iv) decrypted = json.loads(self._unpad(cipher.decrypt(encryptedData))) if decrypted[\'watermark\'][\'appid\'] != self.appId: raise Exception(\'Invalid Buffer\') return decrypted def _unpad(self, s): return s[:-ord(s[len(s)-1:])]
urls.py
from django.contrib import admin from django.urls import path from django.urls import path, include from app import views from app import test_view urlpatterns = [ # 微信小程序登录 path(\'wxlogin/\', views.code2Session), # 微信手机号码登录 path(\'mobileLogin/\', views.mobileLogin), # 通过token获取用转换用户信息 path(\'checkToken/\', views.checkToken), ]
models.py
from django.db import models # Create your models here. from django.contrib.auth.models import AbstractUser from django.db import models class NewUser(AbstractUser): nickname = models.CharField(max_length=225, verbose_name="昵称", default="") avatar_url = models.CharField(max_length=225, verbose_name="头像", default="") gender = models.CharField(max_length=225, verbose_name="性别", default="") session_key = models.CharField(max_length=225, verbose_name="session_key", default="") mobilePhoneNumber = models.CharField(max_length=225, verbose_name="手机号码", default="")
views.py
from django.shortcuts import render from django.views.decorators.csrf import csrf_exempt # Create your views here. from django.http import HttpResponse from django.contrib.auth.models import Permission, User from django.contrib import auth from django.views.decorators.http import require_http_methods from rest_framework.views import APIView from rest_framework_jwt.settings import api_settings from django.core import serializers from django.http import JsonResponse, HttpResponse, HttpResponseRedirect from django.shortcuts import render, redirect import uuid, datetime, json, time from datetime import timedelta from dateutil.relativedelta import relativedelta from wechatpy import WeChatClient import json, re, urllib3 import os import json from wechatpy import WeChatPay from django.views.decorators.csrf import csrf_exempt import time import random from django.core.cache import cache # 引入缓存模块 from wechatpy.utils import timezone from django.db import transaction from django import forms from django.db.models import F import requests from django.contrib.auth.models import Permission, User from django.db import connection from django.db.models import Sum @csrf_exempt def login(request): if request.method == "POST": username = request.POST.get(\'username\') passwd = request.POST.get(\'passwd\') user = User.objects.filter(username=username).first() auth.login(request, user) # 登录成功 print(username) print(passwd) return HttpResponse("登录成功!") else: return HttpResponse("请求错误") #小程序appid appid = "wxxxxxxxxx" # 小程序秘钥 appsecret = "xxxxxxxxxxxx" \'\'\' 登录函数: \'\'\' @require_http_methods([\'POST\']) @csrf_exempt def GetOpenIdView(request): data = json.loads(request.body) jscode = data[\'jscode\'] openid, session_key = OpenId(jscode).get_openid() return JsonResponse({ \'openid\': openid, \'session_key\': session_key }) from app import models @require_http_methods([\'POST\']) @csrf_exempt def login_or_create_account(request): data = json.loads(request.body) print(data) openid = data[\'openid\'] nickname = data[\'nickname\'] avatar_url = data[\'avatar_url\'] gender = data[\'gender\'] try: user = models.NewUser.objects.get(username=openid) except models.NewUser.DoesNotExist: user = None if user: user = models.NewUser.objects.get(username=openid) else: user = models.NewUser.objects.create( username=openid, password=openid, nickname=nickname, avatar_url=avatar_url, gender=gender ) jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER payload = jwt_payload_handler(user) token = jwt_encode_handler(payload) res = { \'status\': \'success\', \'nickname\': user.nickname, \'user_id\': user.id, \'token\': token } return JsonResponse(res) class OpenId: def __init__(self, jscode): self.url = \'https://api.weixin.qq.com/sns/jscode2session\' self.app_id = appid self.app_secret = appsecret self.jscode = jscode def get_openid(self): url = self.url + "?appid=" + self.app_id + "&secret=" + self.app_secret + "&js_code=" + self.jscode + "&grant_type=authorization_code" res = requests.get(url) try: openid = res.json()[\'openid\'] session_key = res.json()[\'session_key\'] except KeyError: return \'fail\' else: return openid, session_key import hashlib import json import requests from rest_framework import status from rest_framework.decorators import api_view, authentication_classes from rest_framework.response import Response from django_redis import get_redis_connection from app.WXBizDataCrypt import WXBizDataCrypt appid = "" secret = "" @api_view([\'POST\']) @authentication_classes([]) # 添加 def code2Session(request): js_code = request.data[\'jscode\'] url = \'https://api.weixin.qq.com/sns/jscode2session\' + \'?appid=\' + appid + \'&secret=\' + secret + \'&js_code=\' + js_code + \'&grant_type=authorization_code\' response = json.loads(requests.get(url).content) # 将json数据包转成字典 if \'errcode\' in response: # 有错误码 return Response(data={\'code\': response[\'errcode\'], \'msg\': response[\'errmsg\']}) # 登录成功 openid = response[\'openid\'] session_key = response[\'session_key\'] # 保存openid, 需要先判断数据库中有没有这个openid user = models.NewUser.objects.filter(username=openid).first() if user is None: user = models.NewUser.objects.create( username=openid, password=uuid.uuid4(), session_key=session_key ) else: user.session_key = session_key user.save() # 将自定义登录态保存到缓存中, 两个小时过期 jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER payload = jwt_payload_handler(user) skey = jwt_encode_handler(payload) print(skey) return JsonResponse({\'code\': 200, \'msg\': \'ok\', \'token\': skey}) def filter_emoji(desstr, restr=\'\'): # 过滤表情 try: res = re.compile(u\'[\U00010000-\U0010ffff]\') except re.error: res = re.compile(u\'[\uD800-\uDBFF][\uDC00-\uDFFF]\') return res.sub(restr, desstr) @api_view([\'POST\']) @authentication_classes([]) # 添加 def
请发表评论