微信小程序 - 登陆(后端实现) | 受权(后端实现)

登陆与受权

官方文档html

一.登陆

登陆流程时序

 

 

说明:mysql

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

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

注意:算法

  1. 会话密钥 session_key 是对用户数据进行 加密签名 的密钥。为了应用自身的数据安全,开发者服务器不该该把会话密钥下发到小程序,也不该该对外提供这个密钥
  2. 临时登陆凭证 code 只能使用一次
总结:
小程序端执行wx.login后在回调函数中就能拿到上图的code,而后把这个code传给咱们后端程序,后端拿到这个这个code后,
能够请求code2Session接口拿到用的openid和session_key,openid是用户在微信中惟一标识,咱们就能够把这个
两个值(val)存起来,而后返回一个键(key)给小程序端,下次小程序请求咱们后端的时候,带上这个key,
咱们就能找到这个val,就能够,这样就把登入作好了。

 

wx.login

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

 

参数sql

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

object.success 回调函数数据库

参数django

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

code2Session

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

登陆凭证校验。经过 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?appid={}&secret={}&js_code={}&grant_type=authorization_code"
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":"缺乏参数"})
相关文章
相关标签/搜索