微信小程序 - 登陸(後端實現) | 受權(後端實現)

登陸與受權

官方文檔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":"缺乏參數"})
相關文章
相關標籤/搜索