微信小程序的登陸和授權,以及用戶信息解密


小程序登錄

小程序可以通過微信官方提供的登錄能力方便地獲取微信提供的用戶身份標識,快速建立小程序內的用戶體系。

登錄流程時序

 

 

說明:

  1. 調用 wx.login() 獲取 臨時登錄憑證code ,並回傳到開發者服務器。

  2. 調用 auth.code2Session 接口,換取 用戶唯一標識 OpenID會話密鑰 session_key

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

注意:

  1. 會話密鑰 session_key 是對用戶數據進行 加密簽名 的密鑰。為了應用自身的數據安全,開發者服務器不應該把會話密鑰下發到小程序,也不應該對外提供這個密鑰

  2. 臨時登錄憑證 code 只能使用一次

openid與unionid

openid:是用單個微信應用表示用戶的唯一標識。亞洲:餅哥小程序上openid :123,那該用戶再張成的小程序上他的opendid不是123,是其他任意一個值,上面的意思:同一用戶再不用不同應用上的openid不同,但是再同一應用上唯一。


場景: 假設你們公司有2個小程序。但是你們老板想把用戶做統一處理。比如新用戶登入任意一個小程序,就發送發送禮包。但是只要再一個小程序上領過了,就不能再另一個上面領取。
unionnid:一個用戶在多個小程序有唯一的標識

官方文檔:小程序的登入(wx.login(Object object))

調用接口獲取登錄憑證(code)。通過憑證進而換取用戶登錄態信息,包括用戶的唯一標識(openid)及本次登錄的會話密鑰(session_key)等。用戶數據的加解密通訊需要依賴會話密鑰完成。更多使用方法詳見 小程序登錄

參數

Object object

屬性 類型 默認值 必填 說明 最低版本
timeout number   超時時間,單位ms 1.9.90
success function   接口調用成功的回調函數  
fail function   接口調用失敗的回調函數  
complete function   接口調用結束的回調函數(調用成功、失敗都會執行)  

object.success 回調函數

參數
Object res
屬性 類型 說明
code string 用戶登錄憑證(有效期五分鍾)。開發者需要在開發者服務器后台調用 auth.code2Session,使用 code 換取 openid 和 session_key 等信息

 

 

 

第一步:小程序獲取code並通過request向服務端發送code

代碼演示

小程序編譯器:app.js

App({
 onLaunch: function () {
 let that = this
   // 登錄
   wx.login({
     success: res => {
       // 發送 res.code 到后台換取 openId, sessionKey, unionId
       console.log(res.code)
       //通過wx.request來發送code
         wx.request({
         url: that.globalData.baseurl+"login/",
         data:{"code":res.code},
         method:"POST",
         success(e){
           console.log(e)
        }
      })
    }
  })    
   console.log("小程序初始化")
  },
   onShow: function (option) {
     console.log("小程序onshow,:onShow", option)
  },
 globalData: {
   userInfo: null,
   //設置全局基礎路由  
   baseurl:"http://127.0.0.1:8000/"
}
})

pycharm:

# 全局路由配置urls.py
from django.conf.urls import url
from django.contrib import admin
from app01.view import test,user
urlpatterns = [
   url(r'^admin/', admin.site.urls),
   url(r'^test/', test.Test.as_view()),
   url(r'^login/', user.Login.as_view()),
]

# appp01/view/user.py
from rest_framework.views import APIView
from rest_framework.response import Response

class Login(APIView):
   def post(self, request):
       param = request.data
       print(param)
       return Response({"status": 0, "data": param})
   
# 打印結果
{'code': '023WEz2v08k2Cg1ylG4v0O3R2v0WEz28'}
[12/Mar/2020 18:32:46] "POST /login/ HTTP/1.1" 200 63

此時已完成小程序獲取code並通過request向服務端發送code

第二步:接下來開始調用auth.code2Session接口來獲取 用戶唯一標識 OpenID會話密鑰 session_key

 

官方文檔:auth.code2Session接口

本接口應在服務器端調用,詳細說明參見服務端API

登錄憑證校驗。通過 wx.login 接口獲得臨時登錄憑證 code 后傳到開發者服務器調用此接口完成登錄流程。更多使用方法詳見 小程序登錄

 

請求地址

GET https://api.weixin.qq.com/sns/jscode2session?appid=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次  

代碼演示:

pycharm中

app01/wx/wx_settings.py

AppID = "wx0e6b084777c39552"
AppSecret = "aa875e6293963ef5d0166753afcea248"
# https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code
# 將上面官網給的請求地址填到接口中,並將部分參數格式化,在wx.login中導入
code2Session = "https://api.weixin.qq.com/sns/jscode2session?appid={}&secret={}&js_code={}&grant_type=authorization_code"

app01/wx/wx_login.py

from app01.wx import settings
import requests


def get_login_info(code):
   code_url = settings.code2Session.format(settings.AppID, settings.AppSecret, code)
   # 通過request發送請求並獲取響應(響應為json數據包)
   response = requests.get(code_url)
   json_response = response.json()
   print("json_response",json_response)
   # 打印結果:json_response {'session_key': '3u/Y+ysYY5rX5Ws8Uf8J2A==', 'openid': 'o-Xsa43CuFhNt3ADKPCBw4VjQULY'}
   if json_response.get("session_key"):
       return json_response
   else:
       return False

app01/view/user.py

from rest_framework.views import APIView
from rest_framework.response import Response
from app01.wx import wx_login

class Login(APIView):
   def post(self, request):
       param = request.data
       if not param.get("code"):
           return Response({"status": 1, "msg": "缺少參數code"})
       else:
           code = param.get("code")
           # user_data就是返回的openid和session_key
           user_data = wx_login.get_login_info(code)
           print(user_data)
           #打印結果:{'session_key': '3u/Y+ysYY5rX5Ws8Uf8J2A==', 'openid': 'o-Xsa43CuFhNt3ADKPCBw4VjQULY'}
           return Response({"status": 0, "data": param})

第三步:獲取到openid和session_key后,自定義登陸狀態,綁定session_key和openid

settings.py

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

app01/view/user.py

from rest_framework.views import APIView
from rest_framework.response import Response
from app01.wx import wx_login

import hashlib, time
from django.core.cache import cache
from app01.models import Wxuser
class Login(APIView):
   def post(self, request):
       param = request.data
       if not param.get("code"):
           return Response({"status": 1, "msg": "缺少參數code"})
       else:
           code = param.get("code")
           # 獲取session_key 和 openid
           user_data = wx_login.get_login_info(code)
           
           if user_data:
               val = user_data['session_key'] + "&" + user_data['openid']
               md5 = hashlib.md5()
               md5.update(str(time.clock()).encode('utf-8'))
               # 加鹽獲取唯一標識key
               md5.update(user_data["session_key"].encode('utf-8'))
               key = md5.hexdigest()
               # 將key和value放在redis中
               cache.set(key, val)
               # 因為openid后面訂單需求中可以用來查表,所以放在數據庫中一份,需要查詢數據庫里是否有唯一openid,沒有則存進去
               hash_user = Wxuser.objects.filter(openid=user_data['openid']).first()
               if not hash_user:
                   Wxuser.objects.create(openid=user_data['openid'])
               return Response({
                   "status":0,
                   "msg":"ok",
                   # 將key返回回去用做唯一標識
                   "data":{"token":key}
              })
           else:
               return Response({"status": 2, "msg": "無效的code"})

小程序app.js

App({
 onLaunch: function () {
   // // 展示本地存儲能力
   // var logs = wx.getStorageSync('logs') || []
   // logs.unshift(Date.now())
   // wx.setStorageSync('logs', logs)
 let that = this
   // 登錄
   wx.login({
     success: res => {
       // 發送 res.code 到后台換取 openId, sessionKey, unionId
       console.log(res.code)
       wx.request({
         url: that.globalData.baseurl+"login/",
         data:{"code":res.code},
         method:"POST",
         success(e){
              //將token存儲在本地
           wx.setStorageSync('token', e.data.data.token)
        }
      })
    }
  })
   // // 獲取用戶信息
   // wx.getSetting({
   //   success: res => {
   //     if (res.authSetting['scope.userInfo']) {
   //       // 已經授權,可以直接調用 getUserInfo 獲取頭像昵稱,不會彈框
   //       wx.getUserInfo({
   //         success: res => {
   //           // 可以將 res 發送給后台解碼出 unionId
   //           this.globalData.userInfo = res.userInfo

   //           // 由於 getUserInfo 是網絡請求,可能會在 Page.onLoad 之后才返回
   //           // 所以此處加入 callback 以防止這種情況
   //           if (this.userInfoReadyCallback) {
   //             this.userInfoReadyCallback(res)
   //           }
   //         }
   //       })
   //     }
   //   }
   // })
   console.log("小程序初始化")
  },
   onShow: function (option) {
     console.log("小程序onshow,:onShow", option)
  },
 globalData: {
   userInfo: null,
   baseurl:"http://127.0.0.1:8000/"
}
})

總結:

1 小程序端執行wx.login()獲取code
2 將1中的code發送到后端,后端調用auth.code2Session這個接口,得到openid和session_key
3 自定義登入狀態,傳給前端一個唯一的標識key,下次前端訪問的時候帶着標識,如果能在數據庫中找到即符合要求,我們生成一個key與openid和session_key相綁定。把key返回到小程序中
4 小程序端保存,然后下次請求需要登入的接口的時候,把key帶上。(通過key就能獲取session_key和openid,再通過openid就能獲取用戶信息)

小程序授權

官方文檔:

授權

部分接口需要經過用戶授權同意才能調用。我們把這些接口按使用范圍分成多個 scope ,用戶選擇對 scope 來進行授權,當授權給一個 scope 之后,其對應的所有接口都可以直接使用。

此類接口調用時:

  • 如果用戶未接受或拒絕過此權限,會彈窗詢問用戶,用戶點擊同意后方可調用接口;

  • 如果用戶已授權,可以直接調用接口;

  • 如果用戶已拒絕授權,則不會出現彈窗,而是直接進入接口 fail 回調。請開發者兼容用戶拒絕授權的場景。

獲取用戶授權設置

開發者可以使用 wx.getSetting 獲取用戶當前的授權狀態。

下面關於wx.getSetting接口信息官方文檔

wx.getSetting(Object object)

基礎庫 1.2.0 開始支持,低版本需做兼容處理

獲取用戶的當前設置。返回值中只會出現小程序已經向用戶請求過的權限

參數

Object object

屬性 類型 默認值 必填 說明 最低版本
withSubscriptions Boolean false 是否同時獲取用戶訂閱消息的訂閱狀態,默認不獲取。注意:withSubscriptions 只返回用戶勾選過訂閱面板中的“總是保持以上選擇,不再詢問”的訂閱消息。 2.10.1
success function   接口調用成功的回調函數  
fail function   接口調用失敗的回調函數  
complete function   接口調用結束的回調函數(調用成功、失敗都會執行)  

object.success 回調函數

參數
Object res
屬性 類型 說明 最低版本
authSetting AuthSetting 用戶授權結果  
subscriptionsSetting SubscriptionsSetting 用戶訂閱消息設置,接口參數withSubscriptions值為true時才會返回。 2.10.1

提前發起授權請求

開發者可以使用 wx.authorize 在調用需授權 API 之前,提前向用戶發起授權請求。

打開設置界面

用戶可以在小程序設置界面(「右上角」 - 「關於」 - 「右上角」 - 「設置」)中控制對該小程序的授權狀態。

開發者可以調用 wx.openSetting 打開設置界面,引導用戶開啟授權。

授權有效期

一旦用戶明確同意或拒絕過授權,其授權關系會記錄在后台,直到用戶主動刪除小程序。

最佳實踐

在真正需要使用授權接口時,才向用戶發起授權申請,並在授權申請中說明清楚要使用該功能的理由。

注意事項

  1. wx.authorize({scope: "scope.userInfo"}),不會彈出授權窗口,請使用 ``

  2. 需要授權 scope.userLocationscope.userLocationBackground 時必須配置地理位置用途說明

后台定位

與其它類型授權不同的是,scope.userLocationBackground 不會彈窗提醒用戶。需要用戶在設置頁中,主動將“位置信息”選項設置為“使用小程序期間和離開小程序后”。開發者可以通過調用wx.openSetting,打開設置頁。

scope 列表

scope 對應接口 描述
scope.userInfo wx.getUserInfo 用戶信息
scope.userLocation wx.getLocation, wx.chooseLocation 地理位置
scope.userLocationBackground wx.startLocationUpdateBackground 后台定位
scope.address wx.chooseAddress 通訊地址
scope.invoiceTitle wx.chooseInvoiceTitle 發票抬頭
scope.invoice wx.chooseInvoice 獲取發票
scope.werun wx.getWeRunData 微信運動步數
scope.record wx.startRecord 錄音功能
scope.writePhotosAlbum wx.saveImageToPhotosAlbum, wx.saveVideoToPhotosAlbum 保存到相冊
scope.camera camera 組件 攝像頭

代碼演示

page.js

lu:function(){
   wx.getSetting({
     success(res) {
         // 是否授權
       if (!res.authSetting['scope.record']) {
         wx.authorize({
             // 錄音功能
           scope: 'scope.record',
           success() {
             // 用戶已經同意小程序使用錄音功能,后續調用 wx.startRecord 接口不會彈窗詢問
             wx.startRecord()
          }
        })
      }
       else{wx.startRecord()
      }
    }
  })
},

 

 

 

 

 

總結:

1 因為部分功能需要用同意后才能使用。

2 wx.getSetting來判斷該用戶有沒有對接口授權,我判斷哪個接口,就必須給wx.getSetting傳對應的scope值
- 一個scope值對應這個一個或多個接口

3 如果我們從wx.getSetting中發現scope值是false,標識沒有授權,我們可以通過wx.authorize發起授權,對那個接口授權,就給wx.authorize傳對應scope值就可以了。如果用用戶同意授權,就可以直接使用對應的接口了。

4 但是scope.userInfo沒有辦法使用wx.authorize自動彈起彈框。必須要用戶手動點擊按鈕喚起授權彈框。
代碼格式:
<button open-type="getUserInfo" bindgetuserinfo="user1">用戶信息</button>
我們可以再響應函數的參數中獲取用戶信息。e.detail,這個和直接調用wx.getUserInfo獲取的內容一樣。

'''
user1: function (e) {
  console.log("e",e.detail)
  wx.getSetting({
    success(res) {
      if (res.authSetting['scope.userInfo']) {
        wx.getUserInfo({
          success(res) {
            // 用戶已經同意小程序使用錄音功能,后續調用 wx.startRecord 接口不會彈窗詢問
            console.log("res",res)
          }
        })
      }
    }
  })
},
'''

====================================================================================================================================================

官方文檔:

wx.getUserInfo(Object object)

調用前需要 用戶授權 scope.userInfo。

獲取用戶信息。

參數

Object object

屬性 類型 默認值 必填 說明
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 回調函數

參數
Object res
屬性 類型 說明 最低版本
userInfo UserInfo 用戶信息對象,不包含 openid 等敏感信息  
rawData string 不包括敏感信息的原始數據字符串,用於計算簽名  
signature string 使用 sha1( rawData + sessionkey ) 得到字符串,用於校驗用戶信息,詳見 用戶數據的簽名驗證和加解密  
encryptedData string 包括敏感數據在內的完整用戶信息的加密數據,詳見 用戶數據的簽名驗證和加解密  
iv string 加密算法的初始向量,詳見 用戶數據的簽名驗證和加解密  
cloudID string 敏感數據對應的雲 ID,開通雲開發的小程序才會返回,可通過雲調用直接獲取開放數據,詳細見雲調用直接獲取開放數據 2.7.0

接口調整說明

在用戶未授權過的情況下調用此接口,將不再出現授權彈窗,會直接進入 fail 回調(詳見《公告》)。在用戶已授權的情況下調用此接口,可成功獲取用戶信息。

示例代碼

// 必須是在用戶已經授權的情況下調用
wx.getUserInfo({
 success: function(res) {
   var userInfo = res.userInfo
   var nickName = userInfo.nickName
   var avatarUrl = userInfo.avatarUrl
   var gender = userInfo.gender //性別 0:未知、1:男、2:女
   var province = userInfo.province
   var city = userInfo.city
   var country = userInfo.country
}
})

敏感數據有兩種獲取方式,一是使用 加密數據解密算法 。 獲取得到的開放數據為以下 json 結構:

{
 "openId": "OPENID",
 "nickName": "NICKNAME",
 "gender": GENDER,
 "city": "CITY",
 "province": "PROVINCE",
 "country": "COUNTRY",
 "avatarUrl": "AVATARURL",
 "unionId": "UNIONID",
 "watermark": {
   "appid":"APPID",
   "timestamp":TIMESTAMP
}
}

小程序用戶信息組件示例代碼

<!-- 如果只是展示用戶頭像昵稱,可以使用 <open-data /> 組件 -->
<open-data type="userAvatarUrl"></open-data>
<open-data type="userNickName"></open-data>
<!-- 需要使用 button 來授權登錄 -->
<button wx:if="{{canIUse}}" open-type="getUserInfo" bindgetuserinfo="bindGetUserInfo">授權登錄</button>
<view wx:else>請升級微信版本</view>
Page({
data: {
  canIUse: wx.canIUse('button.open-type.getUserInfo')
},
onLoad: function() {
  // 查看是否授權
  wx.getSetting({
    success (res){
      if (res.authSetting['scope.userInfo']) {
        // 已經授權,可以直接調用 getUserInfo 獲取頭像昵稱
        wx.getUserInfo({
          success: function(res) {
            console.log(res.userInfo)
          }
        })
      }
    }
  })
},
bindGetUserInfo (e) {
  console.log(e.detail.userInfo)
}
})

==========================================================================

==========================================================================

官方文檔

服務端獲取開放數據

小程序可以通過各種前端接口獲取微信提供的開放數據。考慮到開發者服務端也需要獲取這些開放數據,微信提供了兩種獲取方式:

 

方式一:開發者后台校驗與解密開放數據

微信會對這些開放數據做簽名和加密處理。開發者后台拿到開放數據后可以對數據進行校驗簽名和解密,來保證數據不被篡改。

img

簽名校驗以及數據加解密涉及用戶的會話密鑰 session_key。 開發者應該事先通過 wx.login 登錄流程獲取會話密鑰 session_key 並保存在服務器。為了數據不被篡改,開發者不應該把 session_key 傳到小程序客戶端等服務器外的環境。

數據簽名校驗

為了確保開放接口返回用戶數據的安全性,微信會對明文數據進行簽名。開發者可以根據業務需要對數據包進行簽名校驗,確保數據的完整性。

  1. 通過調用接口(如 wx.getUserInfo)獲取數據時,接口會同時返回 rawData、signature,其中 signature = sha1( rawData + session_key )

  2. 開發者將 signature、rawData 發送到開發者服務器進行校驗。服務器利用用戶對應的 session_key 使用相同的算法計算出簽名 signature2 ,比對 signature 與 signature2 即可校驗數據的完整性。

如 wx.getUserInfo的數據校驗:

接口返回的rawData:

{
 "nickName": "Band",
 "gender": 1,
 "language": "zh_CN",
 "city": "Guangzhou",
 "province": "Guangdong",
 "country": "CN",
 "avatarUrl": "http://wx.qlogo.cn/mmopen/vi_32/1vZvI39NWFQ9XM4LtQpFrQJ1xlgZxx3w7bQxKARol6503Iuswjjn6nIGBiaycAjAtpujxyzYsrztuuICqIM5ibXQ/0"
}

用戶的 session-key:

HyVFkGl5F5OQWJZZaNzBBg==

用於簽名的字符串為:

{"nickName":"Band","gender":1,"language":"zh_CN","city":"Guangzhou","province":"Guangdong","country":"CN","avatarUrl":"http://wx.qlogo.cn/mmopen/vi_32/1vZvI39NWFQ9XM4LtQpFrQJ1xlgZxx3w7bQxKARol6503Iuswjjn6nIGBiaycAjAtpujxyzYsrztuuICqIM5ibXQ/0"}HyVFkGl5F5OQWJZZaNzBBg==

使用sha1得到的結果為

75e81ceda165f4ffa64f4068af58c64b8f54b88c

加密數據解密算法

接口如果涉及敏感數據(如wx.getUserInfo當中的 openId 和 unionId),接口的明文內容將不包含這些敏感數據。開發者如需要獲取敏感數據,需要對接口返回的加密數據(encryptedData) 進行對稱解密。 解密算法如下:

  1. 對稱解密使用的算法為 AES-128-CBC,數據采用PKCS#7填充。

  2. 對稱解密的目標密文為 Base64_Decode(encryptedData)。

  3. 對稱解密秘鑰 aeskey = Base64_Decode(session_key), aeskey 是16字節。

  4. 對稱解密算法初始向量 為Base64_Decode(iv),其中iv由數據接口返回。

注意:下載示例代碼

微信官方提供了多種編程語言的示例代碼((點擊下載)。每種語言類型的接口名字均一致。調用方式可以參照示例。**

另外,為了應用能校驗數據的有效性,會在敏感數據加上數據水印( watermark )

watermark參數說明:

參數 類型 說明
appid String 敏感數據歸屬 appId,開發者可校驗此參數與自身 appId 是否一致
timestamp Int 敏感數據獲取的時間戳, 開發者可以用於數據時效性校驗

如接口 wx.getUserInfo 敏感數據當中的 watermark:

{
   "openId": "OPENID",
   "nickName": "NICKNAME",
   "gender": GENDER,
   "city": "CITY",
   "province": "PROVINCE",
   "country": "COUNTRY",
   "avatarUrl": "AVATARURL",
   "unionId": "UNIONID",
   "watermark":
  {
       "appid":"APPID",
       "timestamp":TIMESTAMP
  }
}

注:

  1. 解密后得到的json數據根據需求可能會增加新的字段,舊字段不會改變和刪減,開發者需要預留足夠的空間

會話密鑰 session_key 有效性

開發者如果遇到因為 session_key 不正確而校驗簽名失敗或解密失敗,請關注下面幾個與 session_key 有關的注意事項。

  1. wx.login 調用時,用戶的 session_key 可能會被更新而致使舊 session_key 失效(刷新機制存在最短周期,如果同一個用戶短時間內多次調用 wx.login,並非每次調用都導致 session_key 刷新)。開發者應該在明確需要重新登錄時才調用 wx.login,及時通過 auth.code2Session 接口更新服務器存儲的 session_key。

  2. 微信不會把 session_key 的有效期告知開發者。我們(微信服務器) 會根據用戶使用小程序的行為對 session_key 進行續期。用戶越頻繁使用小程序,session_key 有效期越長。

  3. 開發者在 session_key 失效時,可以通過重新執行登錄流程獲取有效的 session_key。使用接口 wx.checkSession可以校驗 session_key 是否有效,從而避免小程序反復執行登錄流程。

  4. 當開發者在實現自定義登錄態時,可以考慮以 session_key 有效期作為自身登錄態有效期,也可以實現自定義的時效性策略。

 

方式二:雲調用直接獲取開放數據

接口如果涉及敏感數據(如wx.getWeRunData),接口的明文內容將不包含這些敏感數據,而是在返回的接口中包含對應敏感數據的 cloudID 字段,數據可以通過雲函數獲取。完整流程如下:

1. 獲取 cloudID

使用 2.7.0 或以上版本的基礎庫,如果小程序已開通雲開發,在開放數據接口的返回值中可以通過 cloudID 字段獲取(與 encryptedData 同級),cloudID 有效期五分鍾。

2. 調用雲函數

調用雲函數時,對傳入的 data 參數,如果有頂層字段的值為通過 wx.cloud.CloudID 構造的 CloudID,則調用雲函數時,這些字段的值會被替換為 cloudID 對應的開放數據,一次調用最多可替換 5 個 CloudID

示例:

在小程序獲取到 cloudID 之后發起調用:

wx.cloud.callFunction({
 name: 'myFunction',
 data: {
   weRunData: wx.cloud.CloudID('xxx'), // 這個 CloudID 值到雲函數端會被替換
   obj: {
     shareInfo: wx.cloud.CloudID('yyy'), // 非頂層字段的 CloudID 不會被替換,會原樣字符串展示
  }
}
})

在雲函數收到的 event 示例:

// event
{
// weRunData 的值已被替換為開放數據
"weRunData": {
  "cloudID": "xxx",
  "data": {
    "stepInfoList": [
      {
        "step": 5000,
        "timestamp": 1554814312,
      }
    ],
    "watermark": {
      "appid": "wx1111111111",
      "timestamp": 1554815786
    }
  }
},
"obj": {
  // 非頂層字段維持原樣
  "shareInfo": "yyy",
}
}

如果 cloudID 非法或過期,則在 event 中獲取得到的將是一個有包含錯誤碼、錯誤信息和原始 cloudID 的對象。過期 cloudID 換取結果示例:

// event
{
"weRunData": {
  "cloudID": "xxx",
  "errCode": -601006,
  "errMsg": "cloudID expired."
},
// ...
}

====================================================================================================================================================

微信關於session_key密鑰的有效性,可以通過wx.checkSession校驗

代碼演示:微信小程序部分

# app.js

//app.js
App({
 onLaunch: function () {
   // this指的是當前App對象,調用下面的my_login方法
   this.my_login()  
  },
   onShow: function (option) {
     console.log("小程序onshow,:onShow", option)
  },

   my_login:function(){
     let that = this
     // 登錄
     wx.login({
       success: res => {
         // 發送 res.code 到后台換取 openId, sessionKey, unionId
         console.log(res.code)
         wx.request({
           url: that.globalData.baseurl + "login/",
           data: { "code": res.code },
           method: "POST",
           success(e) {
             //將token存儲在本地
             wx.setStorageSync('token', e.data.data.token)
          }
        })
      }
    })
  },
   // 表示全局可用
 globalData: {
   userInfo: null,
   baseurl:"http://127.0.0.1:8000/"
}
})



# page.js

// pages/test3/test3.js
//全局任何地方都可以通過getApp()獲取App.js中的app對象
const app = getApp()
Page({

 /**
  * 頁面的初始數據
  */
 data: {

},
 user1: function (e) {
   console.log("e",e.detail)
   wx.getSetting({
     success(res) {
       if (res.authSetting['scope.userInfo']) {
         wx.checkSession({
           success() {
             // session_key 未過期,並且在本生命周期一直有效
             wx.getUserInfo({
               success(res) {
                 console.log("res", res)
                   //朝后台getinfo路由發送數據
                 wx.request({
                   url: 'app. globalData.baseurl+"getinfo/"',
                   data:{
                     iv:res.iv,
                     encryptedData: res.encryptedData,
                     token: wx.getStorageSync("token"),              
                  },
                   method:"POST",
                   success:(e)=>{
                     console.log("后台返回的數據",e)
                  }
                })
              }
            })
          },
           fail() {
             // session_key 已經失效,需要重新執行登錄流程
           app.my_login() //重新登錄
             wx.getUserInfo({
               success(res) {
                 console.log("res", res)
                 wx.request({
                   url: 'app. globalData.baseurl+"getinfo/"',
                   data:{
                     iv:res.iv,
                     encryptedData: res.encryptedData,
                     token: wx.getStorageSync("token"),              
                  },
                   method:"POST",
                   success:(e)=>{
                     console.log("后台返回的數據",e)
                  }
                })
              }
            })
          }
        })        
      }
    }
  })
},
})

后端使用官方提供的sdk,進行解密。

# 如官方的sdk沒有Crypto包用下面的方法解決
pip install pycryptodome

解密代碼演示:django后台部分

#路由
# urls.py
from django.conf.urls import url
from django.contrib import admin
from app01.view import test,user
urlpatterns = [
   url(r'^admin/', admin.site.urls),
   url(r'^test/', test.Test.as_view()),
   url(r'^login/', user.Login.as_view()),
   url(r'^getinfo/', user.Info.as_view()),
]



# 解密模塊
#app01/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:])]

   
   # 就是上面decrypt方法的簡化(可根據官方解密文檔中的python示例)
   @classmethod
   def get_info(cls, sessionKey, encryptedData, iv):
       # appId = settings.AppID
       # sessionKey = sessionKey
       # encryptedData = encryptedData
       # iv = iv

       # pc = cls(appId, sessionKey)
       # pc = cls(settings.AppID, sessionKey)

       # print pc.decrypt(encryptedData, iv)
       return cls(settings.AppID, sessionKey).decrypt(encryptedData, iv)



   
   

# app01/view/user.py

# 獲取信息及解碼
from rest_framework.views import APIView
from rest_framework.response import Response
from app01.wx import wx_login

import hashlib, time
from django.core.cache import cache
from app01.models import Wxuser

# 導入解密模塊
from app01.wx import WXBizDataCrypt
# 導入序列化
from app01.my_ser import wx_user_ser

# 登陸驗證
class Login(APIView):
   def POST(self, request):
       param = request.data
       if not param.get("code"):
           return Response({"status": 1, "msg": "缺少參數code"})
       else:
           code = param.get("code")
           # 獲取session_key 和 openid
           user_data = wx_login.get_login_info(code)
           if user_data:
               val = user_data['session_key'] + "&" + user_data['openid']
               md5 = hashlib.md5()
               md5.update(str(time.clock()).encode('utf-8'))
               # 加鹽獲取唯一標識key
               md5.update(user_data["session_key"].encode('utf-8'))
               key = md5.hexdigest()
               # 將key和value放在redis中
               cache.set(key, val)
               # 因為openid后面訂單需求中可以用來查表,所以放在數據庫中一份,需要查詢數據庫里是否有唯一openid,沒有則存進去
               hash_user = Wxuser.objects.filter(openid=user_data['openid']).first()
               if not hash_user:
                   Wxuser.objects.create(openid=user_data['openid'])
               return Response({"status": 0, "msg": "ok",  # 將key返回回去用做唯一標識
                                "data": {"token": key}})
           else:
               return Response({"status": 2, "msg": "無效的code"})

# 獲取用戶信息(解密)
class Info(APIView):
   def post(self, request):
       param = request.data
       # 先驗證參數是否傳過來了
       if param.get('iv') and param.get('token') and param.get('encryptedData'):
           # 驗證token
           session_key_openid = cache.get(param.get("token"))
           if session_key_openid:
               # 切分獲得session_key,openid
               session_key, openid = session_key_openid.split("&")
               # 調用解密包解密
               user_info = WXBizDataCrypt.WXBizDataCrypt.get_info(session_key, param.get('encryptedData'),
                                                                  param.get('iv'))
               # 解密后就能拿到下面的信息
               save_data = {
                   "name": user_info['nickName'],
                   "avatar": user_info['avatarUrl'],
                   "language": user_info['language'],
                   "province": user_info['province'],
                   "city": user_info['city'],
                   "country": user_info['country'], }
               # 將其存入數據庫中(注意:有些信息是比如是頭像或圖片,數據庫的數據格式應當修改為utf8mb4格式)
               Wxuser.objects.filter(openid=openid).update(**save_data)
               # 如何檢驗頭像是否存入,將其再取出返回給前台,若正常返回則沒有問題
               user = Wxuser.objects.filter(openid=openid).first()
               user = wx_user_ser(instance=user, many=False).data
               return Response({"status": 0, "msg": "ok", "data": user})
           else:
               return Response({"code": 2, "msg": "無效的token"})
       else:
           return Response({"code": 1, "msg": "缺少參數"})

 

 

 

 

 

 

 

 

 

 

 

 


免責聲明!

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



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