网络编程 
首页 > 网络编程 > 浏览文章

微信小程序后端实现授权登录

(编辑:jimmy 日期: 2024/11/8 浏览:3 次 )

登录与授权

官方文档

一.登录登录流程时序

微信小程序后端实现授权登录

说明:

调用

  1. wx.login()获取临时登录凭证code,并回传到开发者服务器。
  2. 调用code2Session接口,换取用户唯一标识 OpenID和会话密钥 session_key。

之后开发者服务器可以根据用户标识来生成自定义登录态,用于后续业务逻辑中前后端交互时识别用户身份。

注意:

会话密钥session_key是对用户数据进行加密签名的密钥。为了应用自身的数据安全,开发者服务器不应该把会话密钥下发到小程序,也不应该对外提供这个密钥。

临时登录凭证 code 只能使用一次

总结:
小程序端执行wx.login后在回调函数中就能拿到上图的code,然后把这个code传给我们后端程序,后端拿到这个这个code后,可以请求code2Session接口拿到用的openid和session_key,openid是用户在微信中唯一标识,我们就可以把这个两个值(val)存起来,然后返回一个键(key)给小程序端,下次小程序请求我们后端的时候,带上这个key,我们就能找到这个val,就可以,这样就把登入做好了。

wx.login

调用接口获取登录凭证(code)。通过凭证进而换取用户登录态信息,包括用户的唯一标识(openid)及本次登录的会话
密钥(session_key)等。用户数据的加解密通讯需要依赖会话密钥完成。[/code]

参数

属性 类型 默认值 必填 说明 最低版本 timeout number   否 超时时间,单位ms 1.9.90 success function   否 接口调用成功的回调函数   fail function   否 接口调用失败的回调函数   complete function   否 接口调用结束的回调函数(调用成功、失败都会执行)  

object.success 回调函数

参数

属性 类型 说明 code string 用户登录凭证(有效期五分钟)。开发者需要在开发者服务器后台调用 code2Session,使用 code 换取 openid 和 session_key 等信息

code2Session

本接口应在服务器端调用,详细说明参见服务端API。

登录凭证校验。通过wx.login()接口获得临时登录凭证 code 后传到开发者服务器调用此接口完成登录流程。更多使用方法详见小程序登录。

请求地址

GET https://api.weixin.qq.com/sns/jscode2sessionappid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code

请求参数

属性 类型 默认值 必填 说明 appid string   是 小程序 appId secret string   是 小程序 appSecret js_code string   是 登录时获取的 code grant_type string   是 授权类型,此处只需填写 authorization_code

返回值

Object

返回的 JSON 数据包

属性 类型 说明 openid string 用户唯一标识 session_key string 会话密钥 unionid string 用户在开放平台的唯一标识符,在满足 UnionID 下发条件的情况下会返回,详见 UnionID 机制说明。 errcode number 错误码 errmsg string 错误信息

errcode 的合法值

值 说明 -1 系统繁忙,此时请开发者稍候再试 0 请求成功 40029 code 无效 45011 频率限制,每个用户每分钟100次

二.信息授权wx.getUserInfo

获取用户信息。

参数

属性 类型 默认值 必填 说明 withCredentials boolean   否 是否带上登录态信息。当 withCredentials 为 true 时,要求此前有调用过 wx.login 且登录态尚未过期,此时返回的数据会包含 encryptedData, iv 等敏感信息;当 withCredentials 为 false 时,不要求有登录态,返回的数据不包含 encryptedData, iv 等敏感信息。 lang string en 否 显示用户信息的语言 success function   否 接口调用成功的回调函数 fail function   否 接口调用失败的回调函数 complete function   否 接口调用结束的回调函数(调用成功、失败都会执行)

object.lang 的合法值

值 说明 en 英文 zh_CN 简体中文 zh_TW 繁体中文

object.success 回调函数

参数

属性 类型 说明 userInfo UserInfo 用户信息对象,不包含 openid 等敏感信息 rawData string 不包括敏感信息的原始数据字符串,用于计算签名 signature string 使用 sha1( rawData + sessionkey ) 得到字符串,用于校验用户信息,详见 用户数据的签名验证和加解密 encryptedData string 包括敏感数据在内的完整用户信息的加密数据,详见 用户数据的签名验证和加解密 iv string 加密算法的初始向量,详见 用户数据的签名验证和加解密

注意:

1.小程序端获取授权信息要用button按钮触发

2.小程序端需要将 encryptedData, iv, login_key 传到后端用于解密

案例:

登录:

当小程序第一次执行的时候就调用wx.login

小程序端:apps.js

App({
 onLaunch: function () {
 var _this=this
 // 登录
 wx.login({
  success: res => {
  // 发送 res.code 到后台换取 openId, sessionKey, unionId
  wx.request({
   url: _this.globalData.Url+'/login/', // 后端路径
   data:{"code":res.code}, // code
   header:{"content-type":"application/json"},
   method:"POST",
   success:function(res){
   console.log(res)
   // 小程序端存储login_key
   wx.setStorageSync("login_key",res.data.data.login_key)
   }
  })
  }
 })
 },
 globalData: {
 Url:"http://127.0.0.1:8000",
 userInfo: null
 }
})

后端 django

wx
 ├── settings.py  # 小程序id,code2Session等配置
 ├── wx_login.py  # 用于调用code2Session拿到openid等
 └── WXBizDataCrypt.py # 获取用户授权信息的解密算法,官方下载

微信官方解密算法代码

项目/settings.py

# 配置数据库
DATABASES = {
 'default': {
  'ENGINE': 'django.db.backends.mysql',
  'NAME': 'wx',
  'USER':'root',
  'PASSWORD':'root',
  'HOST':'127.0.0.1',
  'PORT': 3306,
  'OPTIONS': {'charset': 'utf8mb4'}, # 微信用户名可能有标签,所以用utf8mb4
 }
}

# 配置 django-redis
CACHES = {
 'default': {
  'BACKEND': 'django_redis.cache.RedisCache',
  'LOCATION': 'redis://127.0.0.1:6379',
  "OPTIONS": {
   "CLIENT_CLASS": "django_redis.client.DefaultClient",
    "PASSWORD": "",
  },
 },
}

wx/settings.py

# 小程序开发者id
AppId="..."
# 小程序的AppSecret
AppSecret="..."

code2Session="https://api.weixin.qq.com/sns/jscode2session"
pay_mchid ='...'
pay_apikey = '...'

wx/wx_login.py

from app01.wx import settings
import requests

# 调用微信code2Session接口,换取用户唯一标识 OpenID 和 会话密钥 session_key
def login(code):
 response = requests.get(settings.code2Session.format(settings.AppId,settings.AppSecret,code))
 data = response.json()
 if data.get("openid"):
  return data
 else:
  return False

项目/views.py

from rest_framework.views import APIView
from rest_framework.response import Response
from app01.wx import wx_login
from django.core.cache import cache
from app01 import models
import time, hashlib

class Login(APIView):
 def post(self, request):
  param = request.data
  # 拿到小程序端提交的code
  if param.get('code'):
   # 调用微信code2Session接口,换取用户唯一标识 OpenID 和 会话密钥 session_key
   data = wx_login.login(param.get('code'))
   if data:
    # 将openid 和 session_key拼接
    val = data['openid'] + "&" + data["session_key"]
    key = data["openid"] + str(int(time.time()))
    # 将 openid 加密
    md5 = hashlib.md5()
    md5.update(key.encode("utf-8"))
    key = md5.hexdigest()
    # 保存到redis内存库,因为小程序端后续需要认证的操作会需要频繁校验
    cache.set(key, val)
    has_user = models.Wxuser.objects.filter(openid=data['openid']).first()
    # 用户不存在则创建用户
    if not has_user:
     models.Wxuser.objects.create(openid=data['openid'])
    return Response({
     "code": 200,
     "msg": "ok",
     "data": {"login_key": key} # 返回给小程序端
    })
   else:
    return Response({"code": 401, "msg": "code无效"})
  else:
   return Response({"code": 401, "msg": "缺少参数"})

用户信息授权

小程序端test.wxml

<!--用户信息授权-->
<button open-type="getUserInfo" bindgetuserinfo="info">授权登录</button>

test.js

Page({
info: function (res) {
 // console.log(res)
 wx.checkSession({
  success() {
  //session_key 未过期,并且在本生命周期一直有效
  wx.getUserInfo({
   success: function (res) {
   // console.log(res)
   wx.request({
    url: app.globalData.Url + "/getinfo/",
    data: { "encryptedData": res.encryptedData, "iv": res.iv, "login_key": wx.getStorageSync("login_key") },
    method: "POST",
    header: { "content-type": "application/json" },
    success: function (res) {
    console.log(res)
    }
   })
   }
  })

})

后端 django

wx/WXBizDataCrypt.py

import base64
import json
from Crypto.Cipher import AES
from app01.wx import settings

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:])]

 @classmethod
 def getInfo(cls,encryptedData,iv,session_key):
  return cls(settings.AppId,session_key).decrypt(encryptedData, iv)

项目/serializer.py

from rest_framework.serializers import ModelSerializer

from app01 import models
class User_ser(ModelSerializer):
 class Meta:
  model=models.Wxuser
  fields="__all__"

项目/views.py

from app01.wx import WXBizDataCrypt
from app01 import serializer
from app01 import models

class GetInfo(APIView):
 def post(self,request):
  param=request.data
  # 需要小程序端将 encryptedData iv login_key 的值传到后端
  # encryptedData iv seesion_key 用于解密获取用户信息
  # login_key 用于校验用户登录状态
  if param['encryptedData'] and param['iv'] and param['login_key']:
   # 从redis中拿到login_key并切分拿到 openid 和 session_key
   openid,seesion_key=cache.get(param['login_key']).split("&")
   # 利用微信官方提供算法拿到用户的开放数据
   data=WXBizDataCrypt.WXBizDataCrypt.getInfo(param['encryptedData'] ,param['iv'] ,seesion_key)
   save_data={
    "name":data['nickName'],
    "avatar":data['avatarUrl'],
    "language":data['language'],
    "province":data['province'],
    "city":data['city'],
    "country":data['country'],
   }
   # 将拿到的用户信息更新到用户表中
   models.Wxuser.objects.filter(openid=openid).update(**save_data)
   # 反序列化用户对象,并返回到小程序端
   data=models.Wxuser.objects.filter(openid=openid).first()
   data=serializer.User_ser(instance=data,many=False).data
   return Response({"code":200,"msg":"缺少参数","data":data})
  else:
   return Response({"code":200,"msg":"缺少参数"})
上一篇:Vue快速实现通用表单验证的方法
下一篇:Node使用Nodemailer发送邮件的方法实现