微信小程序 - 登錄(后端實現) | 授權(后端實現)


登錄與授權

官方文檔

一.登錄

登錄流程時序

 

 

獲取Appid和AppSecret方法

說明:

  1. 調用 wx.login() 獲取 臨時登錄憑證code ,並回傳到開發者服務器。
  2. 調用 code2Session 接口,換取 用戶唯一標識 OpenID 和 會話密鑰 session_key

之后開發者服務器可以根據用戶標識來生成自定義登錄態,用於后續業務邏輯中前后端交互時識別用戶身份。

注意:

  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)等。用戶數據的加解密通訊需要依賴會話密鑰完成。

 

參數

屬性 類型 默認值 必填 說明 最低版本
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?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()))
                # 將 key 加密
                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}  
            # 將key返回給小程序端,小程序的下次訪問我們的后端需要帶上這個key,后端通過key找到openid則證明是登錄用戶(類似session_id) })
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":"缺少參數"})

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM