微信小程序登录与获取手机号 (Python)

server/2024/9/20 1:25:14/ 标签: 微信小程序, python, 小程序

文章目录

  • 相关术语
  • 登录逻辑
  • 登录设计
  • 登录代码

相关术语

调用接口[wx.login()]获取登录凭证(code)。通过凭证进而换取用户登录态信息,包括用户在当前小程序的唯一标识(openid)、微信开放平台账号下的唯一标识(unionid,若当前小程序已绑定到微信开放平台账号)及本次登录的会话密钥(session_key)等。

临时登录凭证 code 只能使用一次。

如果开发者拥有多个移动应用、网站应用、和公众账号(包括小程序),可通过 UnionID 来区分用户的唯一性,因为只要是同一个微信开放平台账号下的移动应用、网站应用和公众账号(包括小程序),用户的 UnionID 是唯一的。换句话说,同一用户,对同一个微信开放平台下的不同应用,UnionID是相同的。

目前微信登录无法拿到微信昵称与头像,需要自己处理该逻辑。

登录逻辑

登录逻辑主要分两步:

首先这是对应的逻辑设计图:
在这里插入图片描述

首先由前端生成一个code,这个code 需要返回给后端,所以后端需要拿到一个必须得一个code 参数,

后端拿到这个code 需要去拿到对应小程序的三个appid,appsecret (这两个是小程序注册时提供),code(前端传参)去请求API:

GET https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code 

请求参数

属性类型必填说明
appidstring小程序 appId
secretstring小程序 appSecret
js_codestring登录时获取的 code,可通过wx.login获取
grant_typestring授权类型,此处只需填写 authorization_code

返回参数

属性类型说明
session_keystring会话密钥
unionidstring用户在开放平台的唯一标识符,若当前小程序已绑定到微信开放平台账号下会返回,详见 UnionID 机制说明。
errmsgstring错误信息
openidstring用户唯一标识
errcodeint32错误码

后端现在通过API拿到如上的参数,需要返回给前端对应的session_key,unionid(或者openid)这一步相当于前端使用 code 换取 openid、unionid、session_key 等信息。

现在如果需要通过这个信息拿到手机号还是需要进行第二步的处理。

前端拿到这个信息后,需要经过一系列操作返回给后端三个参数。

data.encryptedData, data.Iv, data.session_key

后端拿到这三个参数并可以解密手机号,该加密与解密思路如下:

AES对称加密算法,并且采用了 AES CBC(Cipher Block Chaining)模式。[后面会详细谈]

至此一个登录逻辑已经完成,但这里只是讲的是微信登录逻辑,下面进入到设计模块。

登录设计

任何一个登录模块都少不了数据库,所以这里还需要结合数据库JWT等进行讲述。

首先是后端设计的表,本次使用django 框架进行设计表,除了django自带的User表以外,需要再设计一个user_expand表,但由于可以做一个兼容,可以把三方登录信息在再单独做一个表出来,如下:

三方登陆信息表:

字段名类型说明
idint【主键】
user_idint【逻辑外键】关联用户扩展表id
platformstring用户来源
platform_unique_idstring新增字段,微信的就是openid,谷歌的就是用户的邮箱,其他的类似【三方登陆唯一标识】
ynbool

这里需要把三方登录单独拿出来,因为是一个用户可能有多个三方登录信息,所以需要要设计成一对多的形式。

登录逻辑如下(需要绑定手机号):

1.前端给后端一个code 2.后端通过API 获得openid 3.查找三方表,是否有这个openid ,如果有那么拿到对应的user_id,然后refresh = RefreshToken.for_user(user) 返回给前端一个access_token即可;如果没有查到这个openid,那么需要返回给前端一个信息就是该用户没有绑定过的信息,需要前端返回一个加密向量(以及后端会给前端一个openid)。 4. 后端拿到这个加密向量,解密出这个手机号信息后,把手机号信息以及openid插入到对应数据表中,返回给前端access_token 以表示绑定成功。

登录代码

首先是微信登录使用的一些工具函数:

class WeiXinMiniAppLogin:async def get_three_login_unique_id(self, param: dict):code = param.get("token")login_params = WeiXinMiniAppLoginParams()wx_info = await self.get_wx_miniapp_info(code, await login_params.get_params())  # 获取到wx_info 的信息logger.info(f"wx_info: {wx_info}")return wx_info@staticmethoddef get_login_type():return UserSourceEnum.WEIXINMINIAPP.value@staticmethodasync def get_wx_miniapp_info(code: str, login_params: dict):params = {"appid": login_params.get("appid", ""),"secret": login_params.get("secret", ""),"js_code": code,"grant_type": login_params.get("grant_type", ""),}jscode2session_url = login_params.get("jscode2session_url", "")try:async with aiohttp.ClientSession() as session:async with session.get(jscode2session_url, params=params, ssl=False) as response:if response.status != 200:logger.error(f"WeiXinMiniAppLogin.jscode2session HTTP error: {response.status}")return Nonea = await response.text()  # Debugdata = json.loads(a)# print(response)# return data# data = await response.json()  # B端的写法是data = json.loads(response.text),异步优先使用这个方法except Exception as e:logger.error(f"WeiXinMiniAppLogin.jscode2session error for HTTP: {e}")return Noneerror_code = data.get("errcode")if error_code:logger.error(f"WeiXinMiniAppLogin.jscode2session error_code: {data}")return Nonesession_key = data.get("session_key")openid = data.get("openid")unionid = data.get("unionid", None)  # 注意为空的情况# 如果为空,记录下,本小程序都是空的情况if unionid is None:# logger.error(f"WeiXinMiniAppLogin.jscode2session error: missing unionid in {data}")r_data = {"session_key": session_key,"openid": openid}return r_data# 如果不空的话就返回,实际情况就是只有openidr_data = {"unionid": unionid,"session_key": session_key,"openid": openid}return r_data@staticmethodasync def get_phone_number(encrypted_data, aes_iv, session_key):try:session_key = session_key.replace("\\", "")session_key_bytes = b64decode(session_key)aes_iv_bytes = b64decode(aes_iv)encrypted_data_bytes = b64decode(encrypted_data)cipher = AES.new(session_key_bytes, AES.MODE_CBC, aes_iv_bytes)decrypted_bytes = cipher.decrypt(encrypted_data_bytes)padding_len = decrypted_bytes[-1]decrypted_bytes = decrypted_bytes[:-padding_len]decrypted_str = decrypted_bytes.decode('utf-8')model = json.loads(decrypted_str)phone_number = model.get("phoneNumber", "")return phone_numberexcept Exception as ex:logger.error(f"WeiXinMiniAppLogin.get_phone_number error,encrypted_data:{encrypted_data},aes_iv:{aes_iv},session_key:{session_key},errormsg:{ex}")return None

这里对get_phone_number的解密方法做一些说明:

这个函数使用的是 AES对称加密算法,并且采用了 AES CBC(Cipher Block Chaining)模式

具体分析:

  1. AES (Advanced Encryption Standard):一种对称加密算法,常用于保护数据的安全性。对称加密意味着加密和解密都使用相同的密钥(即这里的 session_key)。

  2. AES.MODE_CBC (Cipher Block Chaining 模式):这是AES的一种工作模式,CBC模式是将前一个加密块的密文与当前块进行异或后再进行加密。解密时也要通过相同的初始化向量(IV,即这里的 aes_iv)来解密。

  3. b64decode:表示输入的 session_key、aes_iv 和 encrypted_data 是通过 Base64 编码的,在解密之前需要将其从 Base64 格式解码成原始字节流。

  4. 填充和去除填充:AES加密的数据块必须是固定长度(一般为128位,也就是16字节),如果数据长度不够,就会自动添加填充字符。在解密后,代码通过 padding_len = decrypted_bytes[-1] 获取填充的长度,并通过 decrypted_bytes[:-padding_len] 去除填充。

  5. 最终数据解密:解密后的数据是一个 JSON 字符串,最后通过 json.loads(decrypted_str) 解析为字典对象,获取其中的 phoneNumber。

下面是登录接口的设计,注意需要构造两个接口,

首先是三方登录回调接口:

前端传入参数:
{"login_type": "string", # 可省略,做一个三方登录标识"token": "string" 
}
后端返回参数:
//需要绑定手机号
{"status": 200,"message": "OK","data": {"third_login_unique_id": "wx_XXXXXXXXX","need_binding": true, # 需要绑定手机号"session_key": "some_session_key"}
}
// 之前绑定手机号的
// {
//     "status": 200,
//     "message": "OK",
//     "data": {
//         "need_binding": false, 
//         "refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.",
//         "access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXV"
//     }
// }

然后是绑定手机号的逻辑:

前端传入参数:
{"third_login_unique_id": "wx_onwXXXXXX","encryptedData": "xxxx","Iv": "xxxx","session_key": "xxxx"
}
后端返回参数:
{"status": 200,"message": "OK","data": {"refresh": "eyJhbGciOiJIUzI1N","access": "eyJhbGciO"}
}
python">@api_controller('user/', tags=['login'], permissions=[])
class LoginController:@http_post('third_login/, response=ThirdLoginBResponse)async def third_login(self, data: ThirdLoginBRequest):# 获取登录类型login_type = data.login_type  # weixinminiappthird_login_instances = third_login_initializer.get_instance(login_type)if third_login_instances is None:raise HttpError(status_code=400, detail="third_login_instances is None")# 获取微信信息wx_info = await third_login_instances.get_three_login_unique_id({"token": data.token})if wx_info is None:raise HttpError(status_code=400, detail="three_login_unique_id is None")# 获取 openid 和 unionidopenid = wx_info.get("openid")unionid = wx_info.get("unionid", None)logger.info(f"ThirdLoginBView.post openid:{openid},unionid:{unionid},login_type:{login_type}")old_third_login_unique_id = f"wx_{openid}"  # 小程序只有openid无法拿到unionid# new_third_login_unique_id = f"wx_{unionid}" if unionid else old_third_login_unique_id# 先从三方表里面去查这个用户user_social_account = await UserSocialAccount.objects.filter(platform_unique_id=old_third_login_unique_id,platform=login_type).afirst()User = get_user_model()# 如果用户存在,直接返回对应的token值if user_social_account:user_id = user_social_account.user_iduser = await User.objects.aget(id=user_id)refresh = RefreshToken.for_user(user)response_data = {"status": 200,"message": "OK","data": {"third_login_unique_id": old_third_login_unique_id,  # 只有openid"need_binding": False,"refresh": str(refresh),"access": str(refresh.access_token),}}response_data["data"]["session_key"] = wx_info.get("session_key")  # 调试解密,该参数只有在需要绑定的时候返回,这里做个测试保留else:# 如果用户不存在,返回需要绑定的信息response_data = {"status": 200,"message": "OK","data": {"third_login_unique_id": old_third_login_unique_id,"need_binding": True}}if login_type == UserSourceEnum.WEIXINMINIAPP.value:response_data["data"]["session_key"] = wx_info.get("session_key")return ThirdLoginBResponse(**response_data)@http_post('wx_binding_phone/', response=WXBindingPhoneResponse)async def wx_binding_phone(self, data: WXBindingPhoneRequest):# logger.info("ThirdLoginView.post start:" + json.dumps(data.dict(), ensure_ascii=False))# 解密获取手机号wx_mini_app_login = WeiXinMiniAppLogin()phone_number = await wx_mini_app_login.get_phone_number(data.encryptedData, data.Iv, data.session_key)if not phone_number:raise HttpError(status_code=400, detail="无法解密获取手机号")# 获取用户模型User = get_user_model()# 查找或创建用户user, created = await User.objects.aget_or_create(username=phone_number)if created:user.set_unusable_password()await user.asave()# 查找或创建用户扩展信息user_expand, created = await UserExpand.objects.aget_or_create(user=user)if created:user_expand.phone = user.usernameuser_expand.nick_name = phone_numberuser_expand.avatar = "https://thirdwx.qlogo.cn/mmopen/vi_32/POgEwh4mIHO4nibH0KlMECNjjGxQUq24ZEaGT4poC6icRiccVGKSyXwibcPq4BWmiaIGuG1icwxaQX6grC9VemZoJ8rg/132"# 缺头像,暂时不处理,昵称就是手机号,目前无法拿到昵称和头像await user_expand.asave()# 创建新的 UserSocialAccount 对象并保存user_social_account = UserSocialAccount(user_id=user.id,platform=UserSourceEnum.WEIXINMINIAPP.value,platform_unique_id=data.third_login_unique_id,)await user_social_account.asave()else:passrefresh = RefreshToken.for_user(user)# 构建响应数据response_data = {"status": 200,"message": "OK","data": {'refresh': str(refresh),'access': str(refresh.access_token),}}return WXBindingPhoneResponse(**response_data)

http://www.ppmy.cn/server/113764.html

相关文章

亚信安全荣获“2024年网络安全优秀创新成果大赛”优胜奖

近日,由中央网信办网络安全协调局指导、中国网络安全产业联盟(CCIA)主办的“2024年网络安全优秀创新成果大赛”评选结果公布。亚信安全信舱ForCloud荣获“创新产品”优胜奖,亚信安全“宁波市政务信息化网络数据安全一体化指挥系统…

【Rust光年纪】从心理学计算到机器学习:Rust语言数据科学库全方位解读!

Rust语言的数据科学和机器学习库大揭秘:核心功能、使用指南一网打尽! 前言 随着数据科学和机器学习在各个领域的广泛应用,使用高效、稳定的编程语言来实现这些功能变得尤为重要。Rust语言作为一种安全且高性能的系统编程语言,正…

前端核心基础知识总结

目录 前言 一、HTML模块 1. 标签结构 2. 语义化标签 3. 表单元素 二、CSS模块 1. 选择器 2. 盒模型 示例一:为一个div标签设置了宽度为 200 像素,高度为 100 像素的内容区。 示例二:内边距的存在可以使内容与边框之间有一定的间隔&…

基于云函数的自习室预约微信小程序+LW示例参考

全阶段全种类学习资源,内涵少儿、小学、初中、高中、大学、专升本、考研、四六级、建造师、法考、网赚技巧、毕业设计等,持续更新~ 文章目录 [TOC](文章目录) 1.项目介绍2.项目部署3.项目部分截图4.获取方式 1.项目介绍 技术栈工具:云数据库…

java设计模式(行为型模式:状态模式、观察者模式、中介者模式、迭代器模式、访问者模式、备忘录模式、解释器模式)

6,行为型模式 6.5 状态模式 6.5.1 概述 【例】通过按钮来控制一个电梯的状态,一个电梯有开门状态,关门状态,停止状态,运行状态。每一种状态改变,都有可能要根据其他状态来更新处理。例如,如果…

JS中【async】和【defer】属性详解与区别

理解浏览器如何处理JavaScript以及相关的async和defer属性对于前端开发是非常重要的。以下是相关知识点的详细讲解: 1. 浏览器的解析和渲染过程 浏览器在加载网页时,会按照以下步骤解析和渲染内容: HTML解析: 浏览器从顶部开始逐行解析HTML…

【语音告警】博灵智能语音报警灯JavaScript循环播报场景实例-语音报警灯|声光报警器|网络信号灯

功能说明 本文将以JavaScript代码为实例,讲解如何通过JavaScript代码调用博灵语音通知终端 A4实现声光语音告警。主要博灵语音通知终端如何实现无线循环播报或者周期播报的功能。 本代码实现HTTP接口的声光语音播报,并指定循环次数、播报内容。由于通知…

C++ linux下的cmake

cmake是一个帮助我们构建项目的跨平台工具。让我们不需要一次次手动配置makefile,或者手动去链接库这些操作。 配置 (基于vscode编辑器) 在项目main.cpp同级目录下,创建CMakeLists.txt文件,举例内容如下(需…

衡石分析平台使用手册-快速入门

快速入门​ 快速指南​ 创建管理员账号​ 按照文档安装成功之后&#xff0c;假设安装所在服务器 IP 是<Server IP>&#xff0c;端口是<Server Port>&#xff0c;则可以通过浏览器访问http://<Server IP>:<Server Port>/ 访问衡石分析平台&#xff0…

代码随想录算法day28 | 动态规划算法part01 | 理论基础、509. 斐波那契数、70. 爬楼梯、 746. 使用最小花费爬楼梯

理论基础 什么是动态规划 动态规划&#xff0c;英文&#xff1a;Dynamic Programming&#xff0c;简称DP&#xff0c;如果某一问题有很多重叠子问题&#xff0c;使用动态规划是最有效的。 所以动态规划中每一个状态一定是由上一个状态推导出来的&#xff0c;这一点就区分于贪…

任务执行拓扑排序(华为od机考题)

一、题目 1.原题 一个应用启动时&#xff0c;会有多个初始化任务需要执行&#xff0c; 并且任务之间有依赖关系&#xff0c; 例如&#xff1a;A任务依赖B任务&#xff0c;那么必须在B任务执行完成之后&#xff0c;才能开始执行A任务。 现在给出多条任务依赖关系的规则&#x…

银行定期产品

银行存款产品如下: 其中对私的储蓄存款: 定期存款是指存款人在银行或金融机构存入一定金额的资金,并约定一个固定的存期,在存期内不得随意支取,到期后可以获取本金和预先约定好的利息的一种存款方式。根据不同的存取方式和特点,定期存款主要可以分为以下几种类型: 整存…

Redis进阶(二)--Redis高级特性和应用

文章目录 第二章、Redis高级特性和应用一、Redis的慢查询1、慢查询配置2、慢查询操作命令3、慢查询建议 二、Pipeline三、事务1、Redis的事务原理2、Redis的watch命令3、Pipeline和事务的区别 四、Lua1、Lua入门&#xff08;1&#xff09;安装Lua&#xff08;2&#xff09;Lua基…

无人机纪录片航拍认知

写在前面 博文内容为纪录片航拍简单认知&#xff1a;纪录片 航拍镜头&#xff0c;航拍流程&#xff0c;航拍环境条件注意事项介绍航拍学习书籍推荐《无人机商业航拍教程》读书笔记整理&#xff0c;适合小白认知理解不足小伙伴帮忙指正 &#x1f603;,生活加油 99%的焦虑都来自于…

堆-数组的堆化+优先队列(PriorityQueue)的使用

一、堆 1、什么是堆&#xff1f; 以完全二叉树的形式将元素存储到对应的数组位置上所形成的新数组 2、为什么要将数组变成堆&#xff1f; 当数组中的元素连续多次进行排序时会消耗大量的时间&#xff0c;将数组变成堆后通过堆排序的方式将会消耗更少的时间 二、接口 给堆…

OpenSSL Windows编译

目录 1. 源码下载2. vs2022编译 1. 源码下载 源码地址 2. vs2022编译 (1) 将“VS2022安装目录VC\Auxiliary\Build\“设置为PATH环境变量&#xff0c;启动cmd命令行&#xff08;一定要先设置环境变量&#xff09;。 (2)在cmd下进入VS2013安装目录vs2022\VC\Auxiliary\Build&…

心觉:潜意识是一个免费的“超级工作狂”,你居然不会用

我们常听说&#xff1a;潜意识的力量是意识到3万倍以上 你信吗 估计很多人不相信&#xff0c;不相信当然用不好 不相信的原因核心有两个&#xff1a; 没有体验过 寻求绝对的科学验证 这两个原因会让你对潜意识不相信&#xff0c;或者半信半疑 今天我也不会给你绝对的科学…

要在 Windows 系统中通过 VNC 远程连接到 CentOS 或 Ubuntu 服务器,可以按照以下步骤来配置和使用 VNC 进行远程桌面访问

要在 Windows 系统中通过 VNC 远程连接到 CentOS 或 Ubuntu 服务器&#xff0c;可以按照以下步骤来配置和使用 VNC 进行远程桌面访问。 在 CentOS 或 Ubuntu 服务器上配置 VNC 服务 步骤 1&#xff1a;安装 VNC 服务器和桌面环境 对于 CentOS&#xff1a; 安装桌面环境&…

江协科技STM32学习- P9 OLED调试工具

&#x1f680;write in front&#x1f680; &#x1f50e;大家好&#xff0c;我是黄桃罐头&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流 &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd;​…

uni-app流式接受消息/文件

uni-app流式接受消息/文件 问题描述 今天利用fastgpt搭建了一个局域网进行访问Ai助理&#xff0c;在前端通过api接口进行请求&#xff0c;用于接收后端的发送的流式消息&#xff0c;那么前端可以进行流式的获取到这个消息&#xff0c;也可以进行直接进行在请求发送完成以后&a…