微信小程序开发系列-05登录小程序

本文继续学习下微信小程序登录相关的内容。

微信平台小程序用户体系

普通用户视角:对于每个小程序,微信都会将用户的微信ID映射出一个小程序OpenID,作为这个用户在这个小程序的唯一标识。(注意:同一微信用户在不同小程序的openid不同。)

开发者视角:对于拥有多个小程序的开发者,开发者可以注册一个微信开放平台账号,然后把所有小程序绑定在这一个开放平台下,这样的话微信还会将微信ID映射出一个UnionID,作为这个用户在整个开放平台的唯一ID。(注:该能力不单限于小程序,所有公众号、网站、移动APP,只要使用微信开放能力登录的应用,都能获取到unionid,这样就能打通各个平台的用户账号体系了。)

登录流程时序

请添加图片描述

说明

  1. 调用 wx.login() 获取 临时登录凭证code ,并回传到开发者服务器。
  2. 调用 auth.code2Session 接口,换取 用户唯一标识 OpenID 、 用户在微信开放平台账号下的唯一标识UnionID(若当前小程序已绑定到微信开放平台账号) 和 会话密钥 session_key

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

注意事项

  1. 会话密钥 session_key 是对用户数据进行 加密签名 的密钥。为了应用自身的数据安全,开发者服务器不应该把会话密钥下发到小程序,也不应该对外提供这个密钥
  2. 临时登录凭证 code 只能使用一次

以上是官方给出的登录时序说明,要理解上述内容,还需要学习其中涉及的很多细节,并实际编写代码运行体会。

相关接口介绍

wx.login

获取用户登录凭证(code),有效期五分钟。

开发者需要在开发者服务器后台调用 code2Session,使用 code 换取 openid、unionid、session_key 等信息。

OpenID:用户在当前小程序的唯一标识。

UnionID:微信开放平台账号下的唯一标识。(若当前小程序已绑定到微信开放平台账号,没绑定的话没有)

sesion_key:本次登录的会话密钥。

wx.login({
  success (res) {
    if (res.code) {
      //发起网络请求
      wx.request({
        url: 'https://example.com/onLogin',
        data: {
          code: res.code
        }
      })
    } else {
      console.log('登录失败!' + res.errMsg)
    }
  }
})

wx.getSetting

获取用户当前设置。返回值中只会出现小程序已经向用户请求过的权限

可获取用户当前的授权状态。

属性类型说明最低版本
authSettingAuthSetting用户授权结果
subscriptionsSettingSubscriptionsSetting用户订阅消息设置,接口参数withSubscriptions值为true时才会返回。2.10.1
miniprogramAuthSettingAuthSetting在插件中调用时,当前宿主小程序的用户授权结果

请添加图片描述

请添加图片描述

AuthSetting

用户授权设置信息

属性作用接口
boolean scope.userInfo是否授权用户信息wx.getUserInfo
boolean scope.userLocation是否授权精确地理位置wx.getLocation, wx.chooseLocation
boolean scope.userFuzzyLocation是否授权模糊地理位置wx.getFuzzyLocation
boolean scope.address是否授权通讯地址,已取消此项授权,会默认返回true
boolean scope.invoiceTitle是否授权发票抬头,已取消此项授权,会默认返回true
boolean scope.invoice是否授权获取发票,已取消此项授权,会默认返回true
boolean scope.werun是否授权微信运动步数wx.getWeRunData
boolean scope.record是否授权录音功能wx.startRecord
boolean scope.writePhotosAlbum是否授权保存到相册wx.saveImageToPhotosAlbum, wx.saveVideoToPhotosAlbum
boolean scope.camera是否授权摄像头camera ((camera)) 组件
boolean scope.bluetooth是否授权蓝牙wx.openBluetoothAdapter、wx.createBLEPeripheralServer
boolean scope.addPhoneContact是否添加通讯录联系人wx.addPhoneContact
boolean scope.addPhoneCalendar是否授权系统日历wx.addPhoneRepeatCalendar、wx.addPhoneCalendar

wx.authorize

提前向用户发起授权请求。调用后会立刻弹窗询问用户是否同意授权小程序使用某项功能或获取用户的某些数据,但不会实际调用对应接口。如果用户之前已经同意授权,则不会出现弹窗,直接返回成功。

getSetting(e){
    wx.getSetting({
      success: (res) => {
        console.log(res)
        if (!res.authSetting['scope.record']){
          wx.authorize({
            scope: 'scope.record',
            success: ()=>{
              const options = {
                duration: 10000,
                sampleRate: 44100,
                numberOfChannels: 1,
                encodeBitRate: 192000,
                format: 'aac',
                frameSize: 50
              }
              wx.getRecorderManager().start(options)
            }
          })
        }
      }
    })
  },

授权后再调用wx.getSetting就可以看到scope.record为true了。

请添加图片描述

开放数据校验与解密

小程序可以通过各种前端接口获取微信提供的开放数据。考虑到开发者服务端也需要获取这些开放数据,微信提供了两种获取方式:

  • 方式一:开发者后台校验与解密开放数据
  • 方式二:云调用直接获取开放数据(云开发)

方式一:开发者后台校验与解密开放数据

微信会对这些开放数据签名加密处理。开发者后台拿到开放数据后可以对数据进行校验签名解密,来保证数据不被篡改。

请添加图片描述

签名校验以及数据加解密涉及用户的会话密钥 session_key。 开发者应该事先通过 code2Session 登录流程获取会话密钥 session_key 并保存在服务器。为了数据不被篡改,开发者不应该把 session_key 传到小程序客户端等服务器外的环境。

数据签名校验

为了确保开放接口返回用户数据的安全性,微信会对明文数据进行签名。开发者可以根据业务需要对数据包进行签名校验,确保数据的完整性。

  1. 通过调用接口(如 wx.getUserInfo)获取数据时,接口会同时返回 rawData、signature,其中 signature = sha1( rawData + session_key )
  2. 开发者将 signature、rawData 发送到开发者服务器进行校验。服务器利用用户对应的 session_key 使用相同的算法计算出签名 signature2 ,比对 signature 与 signature2 即可校验数据的完整性。
加密数据解密算法

接口如果涉及敏感数据(如 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参数说明:

参数类型说明
appidString敏感数据归属 appId,开发者可校验此参数与自身 appId 是否一致
timestampInt敏感数据获取的时间戳, 开发者可以用于数据时效性校验
会话密钥 session_key 有效性

开发者如果遇到因为 session_key 不正确而校验签名失败或解密失败,请关注下面几个与 session_key 有关的注意事项。

  1. wx.login 调用时,用户的 session_key 可能会被更新而致使旧 session_key 失效(刷新机制存在最短周期,如果同一个用户短时间内多次调用 wx.login,并非每次调用都导致 session_key 刷新)。开发者应该在明确需要重新登录时才调用 wx.login,及时通过 code2Session 接口更新服务器存储的 session_key。
  2. 微信不会把 session_key 的有效期告知开发者。我们会根据用户使用小程序的行为对 session_key 进行续期。用户越频繁使用小程序,session_key 有效期越长。
  3. 开发者在 session_key 失效时,可以通过重新执行登录流程获取有效的 session_key。使用接口 wx.checkSession可以校验 session_key 是否有效,从而避免小程序反复执行登录流程。
  4. 当开发者在实现自定义登录态时,可以考虑以 session_key 有效期作为自身登录态有效期,也可以实现自定义的时效性策略。

也就是说,在小程序服务端,通过验签和解密可以获取到敏感数据(如用户信息等)。

wx.checkSession

检查登录状态是否过期。

通过 wx.login 接口获得的用户登录态拥有一定的时效性。用户越久未使用小程序,用户登录态越有可能失效。反之如果用户一直在使用小程序,则用户登录态一直保持有效。具体时效逻辑由微信维护,对开发者透明。开发者只需要调用 wx.checkSession 接口检测当前用户登录态是否有效。

总结

微信小程序登录流程

微信小程序的登录流程大体上涉及三个部分:小程序客户端、小程序服务端(开发者自行搭建)、微信服务端。

请添加图片描述

小程序客户端通过wx.login获取临时登录凭证code,然后通过wx.request将code发送给小程序服务端,小程序服务端再通过code2session接口获取OpenId、session_key等。到这里为止,其实微信小程序本身的登录已经完成了,后续的流程只是小程序应用根据实际需求来对用户进行管理,这个部分就看小程序应用的开发者依据项目需求实际进行开发了。

微信小程序登录步骤

  1. 小程序客户端通过wx.login 获取临时登录凭证code
  2. 小程序客户端将code传给小程序服务端;
  3. 小程序服务端通过 auth.code2Session接口,获取三个东西:
    1. OpenID:用户唯一标识;
    2. UnionID: 微信开放平台账号下的唯一标识;(绑定微信开放平台后才有)
    3. session_key:会话密钥;

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

注意事项

  1. 会话密钥 session_key 是对用户数据进行 加密签名 的密钥。为了应用自身的数据安全,小程序服务端不应该把会话密钥下发到小程序应用端,也不应该对外提供这个密钥
  2. 临时登录凭证 code 有效期5分钟。

核心代码

小程序客户端代码

index.wxml
<!--pages/index/index.wxml-->
<view class="container">
  <view class="userinfo">
    <block wx:if="{{!hasUserInfo}}">
      <button wx:if="{{canIUseGetUserProfile}}" bindtap="getUserProfile"> 获取头像昵称 </button>
    </block>
    <block wx:else>
      <image bindtap="bindViewTap" class="userinfo-avatar" src="{{userInfo.avatarUrl}}" mode="cover"></image>
      <text class="userinfo-nickname">{{userInfo.nickName}}</text>
    </block>
    <input type="nickname" placeholder="请输入昵称"/>
  </view>
  <button bindtap="openSetting"> 打开设置页 </button>
  <button bindtap="getSetting">获取设置</button>
</view>
Index.js
// pages/index/index.js
Page({

  /**
   * 页面的初始数据
   */
  data: {
    userInfo: {},
    hasUserInfo: false,
    canIUseGetUserProfile: false,
    code:''
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad(options) {
    if (wx.getUserProfile) {
      this.setData({
        canIUseGetUserProfile: true
      })
    }
     wx.login({
      success: (resp) => {
        console.log("code:", resp)
        this.setData({
          code:resp.code
        })
      }
    })
  },
  getSetting(e){
    wx.getSetting({
      success: (res) => {
        console.log(res)
        /*
        if (!res.authSetting['scope.record']){
          wx.authorize({
            scope: 'scope.record',
            success: ()=>{
              const options = {
                duration: 10000,
                sampleRate: 44100,
                numberOfChannels: 1,
                encodeBitRate: 192000,
                format: 'aac',
                frameSize: 50
              }
              wx.getRecorderManager().start(options)
            }
          })
        }
        */
      }
    })
  },
  openSetting(e){
    wx.openSetting({
      success: (res) => {
        console.log(res)
      }
    })
  },
  getUserProfile(e) {
    wx.getUserProfile({
      desc: '用于完善会员资料', // 声明获取用户个人信息后的用途,后续会展示在弹窗中,请谨慎填写
      success: (res) => {
        console.log("success:", res)
        this.setData({
          userInfo: res.userInfo,
          hasUserInfo: true
        })
        wx.request({
          url: 'http://127.0.0.1:5000/login',
          data: {
            code: this.data.code,
            encryptedData: res.encryptedData,
            iv: res.iv
          },
          method: "POST",
          success(res) {
            console.log(res);
          },
          fail(res) {
            console.log(res)
          }
        })
      },
      fail: (res) => {
        console.log("fail:", res)
      }
    })
  },
  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady() {

  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow() {

  },

  /**
   * 生命周期函数--监听页面隐藏
   */
  onHide() {

  },

  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload() {

  },

  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh() {

  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom() {

  },

  /**
   * 用户点击右上角分享
   */
  onShareAppMessage() {

  }
})

请添加图片描述

点击“获取头像昵称”按钮后,可以从小程序服务端获取到userinfo,带watermark。

请添加图片描述

小程序服务端代码

test.py
import requests
from flask import Flask, jsonify, request
from WXBizDataCrypt import WXBizDataCrypt

app = Flask(__name__)

@app.route("/login", methods = ['POST'])
def login():
    data = request.get_json()
    print(data)
    appID = '你自己的appID' #开发者关于微信小程序的appID
    appSecret = '你自己的appSecret' #开发者关于微信小程序的appSecret
    code = data['code'] #前端POST过来的微信临时登录凭证code    
    encryptedData = data['encryptedData']
    iv = data['iv']
    req_params = {
        'appid': appID,
        'secret': appSecret,
        'js_code': code,
        'grant_type': 'authorization_code'
    }
    wx_login_api = 'https://api.weixin.qq.com/sns/jscode2session'
    response_data = requests.get(wx_login_api, params=req_params) #向API发起GET请求
    data2 = response_data.json()
    print(data2)
    if (data2['openid']):
        openid = data2['openid'] #得到用户关于当前小程序的OpenID
        session_key = data2['session_key'] #得到用户关于当前小程序的会话密钥session_key
        pc = WXBizDataCrypt(appID, session_key) #对用户信息进行解密
        userinfo = pc.decrypt(encryptedData, iv) #获得用户信息
        print(userinfo)
    else:
        data = "get openid failed"

    return jsonify(result = data)


if __name__ == '__main__':
    app.run()

WXBizDataCrypt.py
import base64
import json
from Crypto.Cipher import AES

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/273825.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

unityc用vs2017介绍

21版unity能用17vs&#xff0c;只要在unity的Edit/Preferences/ExternalTools里面改既可。

继承-继承方式

继承方式 继承的语法: class 子类 &#xff1a;继承方式 父类 继承方式一共有三种&#xff1a; 1.公共继承 2.保护继承 3.私有继承 //继承方式 #include<iostream> using namespace std; class Base1 { public:int m_A; protected:int m_B; private:int m_C; }; cla…

卷积神经网络基础

全连接层 BP&#xff08;back propagation&#xff09;算法包括信号的前向传播和误差的反向传播两个过程。即计算误差输出时按从输入到输出的方向进行&#xff0c;而调整权值和阈值则从输出到输入的方向进行。 误差值&#xff1a;将输出值和所期望的值进行对比&#xff0c;可以…

设备智能运维利器:无线振温一体式传感器

在现代工业领域中&#xff0c;设备的状态监测和维护是确保生产正常运行的关键环节。随着技术的不断进步&#xff0c;传感器在设备监测中发挥着越来越重要的作用。其中&#xff0c;无线振温一体式传感器作为设备智能运维的利器&#xff0c;具有独特的优势和潜力。本文将介绍无线…

C++ Qt开发:TableView与TreeView组件联动

Qt 是一个跨平台C图形界面开发库&#xff0c;利用Qt可以快速开发跨平台窗体应用程序&#xff0c;在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置&#xff0c;实现图形化开发极大的方便了开发效率&#xff0c;本章将重点介绍TableView与TreeView组件联动的常用方法及灵活…

Transfer Learning(迁移学习)

1. 什么是迁移学习 迁移学习(Transfer Learning)是一种机器学习方法&#xff0c;就是把为任务 A 开发的模型作为初始点&#xff0c;重新使用在为任务 B 开发模型的过程中。迁移学习是通过从已学习的相关任务中转移知识来改进学习的新任务&#xff0c;虽然大多数机器学习算法都…

表单(HTML)

<!DOCTYPE html> <html><head><meta charset"utf-8"><title>个人信息</title></head><body><h1>个人信息</h1><form><fieldset><legend>基本信息</legend><label for"…

强光led电筒控制芯片方案:OC5338

强光LED电筒控制芯片OC5338是一款内置100V/3A功率MOS的高性能芯片方案。它具有多种功能&#xff0c;包括100%输出、25%输出和爆闪。此外&#xff0c;它还具有宽输入电压范围&#xff0c;从3.6V到100V&#xff0c;并且具有高达90%的高效率。 强光led电筒控制芯片方案:OC5338 L…

C#与VisionPro联合编程

C#与VisionPro联合 1. 参照康耐视提供的样例2. 参照样例写一个1. 创建工程2. 添加引用3. 声明变量4. 初始化5. 刷新队列6. 用户数据获取7. 跨线程访问Windows控件--委托8. 显示图像9. 释放资源 3. 代码4. 资源下载 1. 参照康耐视提供的样例 C:\Program Files\Cognex\VisionPro…

【Linux操作系统】命令补全

补全命令 快捷键&#xff1a;Tab 示例&#xff1a; 在终端中输入“ifc”&#xff0c;按Tab键&#xff0c;自动补全为“ifconfig”命令——查询IP地址。

k8s的二进制部署

k8s的二进制部署&#xff1a;源码包部署 k8smaster01: 20.0.0.101 kube-apiserver kube-controller-manager kube-scheduler etcd k8smaster02: 20.0.0.102 kube-apiserver kube-controller-manager kube-scheduler node节点01: 20.0.0.103 kubelet kube-proxy etcd node节点02…

最优化考试之牛顿法

最优化考试之牛顿法 一、牛顿法1.问题条件2.求解过程3.例子 PS 一、牛顿法 1.问题条件 目标函数 f ( x ) f(x) f(x)&#xff0c;求极小值初始点 x 0 x_0 x0​精度要求e&#xff08;没有提就是近似0&#xff09; 2.求解过程 求解一阶雅克比矩阵 ∇ f ( x ) ∇f(x) ∇f(x)和二…

Jmeter接口工具大全使用—响应断言

断言的作用&#xff1a;一个HTTP请求发出去&#xff0c;怎么判断执行的任务是否成功呢&#xff1f;通过检查服务器响应数据&#xff0c;是否返回预期想要的数据&#xff0c;如果是&#xff0c;判断任务成功&#xff0c;反之任务失败。 1.添加断言 选中一个取样器&#xff0c;…

少走弯路:单片机使用点阵字体通过像素化的正确获取

要在单片机内自由显示文字&#xff0c;必须准备相应的字库。之前也发文介绍过&#xff1a; 在esp32(esp8266) 提供软字库显示中文的解决方案_esp32中文字库-CSDN博客 包括已经开源的项目&#xff1a; https://github.com/StarCompute/tftziku 这种字体获取思路是&#xff1a…

HackTheBox - Medium - Linux - Agile

Agile Agile 是一个中等难度的 Linux 机器&#xff0c;在端口 80 上有一个密码管理网站。创建帐户并添加几个密码后&#xff0c;发现网站的导出到 CSV 功能容易受到任意文件读取的攻击。其他终结点的枚举显示“/download”在访问时引发错误&#xff0c;并显示“Werkzeug”调试…

Duboo-入门到学废【上篇】

目录 1&#x1f95e;.什么是duboo 2&#x1f32d;.架构图 3.&#x1f37f;快速入门 4.&#x1f9c7;浅浅理解 1.什么是duboo&#x1f936;&#x1f936;&#x1f936; Dubbo是一个由阿里巴巴开发的基于Java的开源RPC框架。它提供了高性能、透明化的远程方法调用&#xff0…

通讯录管理系统简单实现

1.功能介绍 今天我们要实现的通讯录管理系统主要有7项功能&#xff1a;添加联系人&#xff0c;显示联系人&#xff0c;删除联系人&#xff0c;查找联系人&#xff0c;修改联系人&#xff0c;清空联系人&#xff0c;退出通讯录 2.功能实现 2.1创建联系人结构体 通讯录结构体 一…

linux 中 ext2文件系统实现

ext2文件系统结构 图片的svg下载链接&#xff08;图中关于buffer的部分&#xff0c;上下两部分是重复的&#xff0c;是从不同维度下看的buffer结构&#xff09; linux内核本身不提供ext2文件系统的格式化功能&#xff0c;可以参考busybox中对mkfs.ext2的实现&#xff08;mkfs.…

【数据结构】顺序表与单链表的增删查改

文章目录 前言顺序表增删查改顺序表的定义与初始化增删查改操作测试代码完整代码 单链表的增删查改数据结构定义动态申请节点单链表的尾插和头插单链表的尾删和头删单链表的查找单链表的插入和删除销毁链表测试代码完整代码 总结 前言 在计算机编程领域&#xff0c;数据结构是…

安全运维是做什么的,主要工作内容是什么

安全运维&#xff0c;简称SecOps&#xff0c;是一种集成安全措施和流程到信息技术运维的实践。它的目的是确保在日常运维活动中&#xff0c;如网络管理、系统维护、软件更新等&#xff0c;均考虑并融入安全策略。安全运维的核心是实现安全和运维团队的密切协作&#xff0c;以快…