前言
只要开始写一个微信小程序,必要的登录流程每次都要再理一下逻辑,记录一下思路,以便于后面自己食用。 大家有什么更好的思路可以分享~后面想创建一个学习小组,有兴趣的小伙伴可以联系我。
文章目录
前言一、小程序微信授权登录1.登录态检查(节点 B)2.获取临时凭证 `code`(节点 D)3. 开发者服务器换用户标识(节点 F-G)4. 生成自定义登录态(节点 H)5.登录态存储与使用(节点 J-L)
二、 需用户信息授权(如获取头像、昵称)三、小程序获取手机号登录1. 前置:获取临时登录凭证 code(节点 D)2. 触发手机号授权(节点 E-F)3. 获取加密手机号数据(节点 H)4. 服务器解密手机号(节点 J-L)5. 关联用户与生成登录态(节点 M)6. 登录态存储与后续使用(节点 N-P)
四、具体代码实现1.需求示例2.代码实现(这里以页面上显示登录按钮为例)
五、关键注意事项1.解密失败排查:2.用户拒绝授权处理:3.安全性要求:
一、小程序微信授权登录
1.登录态检查(节点 B)
操作方:小程序前端目的:避免重复登录,提升用户体验实现:
前端通过 wx.getStorageSync('token') 检查本地是否存储了有效的自定义登录态(如 Token)。若有 Token,直接携带 Token 请求开发者服务器验证有效性,有效则进入主页;若无 Token 或 Token 已过期,触发登录流程。
2.获取临时凭证 code(节点 D)
操作方:小程序前端核心接口:wx.login()说明:
调用后微信客户端会向微信服务器请求,返回一个 一次性、有效期 5 分钟 的 code(临时登录凭证,仅能使用 1 次);此步骤 无需用户手动授权(静默获取,不弹授权弹窗)。
文档直通车: 开放接口/登录/wx.login
3. 开发者服务器换用户标识(节点 F-G)
操作方:开发者服务器核心接口:微信官方 auth.code2Session 接口(GET 请求)请求参数(必须包含):
参数名来源说明appid小程序管理后台获取小程序唯一标识secret小程序管理后台获取小程序密钥(需保密,不可暴露在前端)js_code前端传递的 code临时登录凭证grant_type固定值填 "authorization_code"
返回结果(微信服务器返回给开发者服务器):
openid:用户在当前小程序的 唯一标识(每个用户对每个小程序的 openid 不同);session_key:微信服务器与开发者服务器之间的 加密密钥(用于解密用户敏感信息,如手机号、头像昵称,需保密存储);若 code 无效(如过期、重复使用),会返回错误码(如 40029)。
文档直通车: 小程序登录/小程序登录
4. 生成自定义登录态(节点 H)
操作方:开发者服务器核心目的:避免直接使用 openid/session_key 传输,提升安全性常见方案:
用 openid 查询数据库,判断是否为新用户(新用户则创建用户记录,老用户则更新 session_key);生成自定义登录态(如 JWT Token、随机字符串),并将 Token 与 openid、session_key 关联存储(如存入 Redis、MySQL);Token 需设置有效期(如 7 天),过期后需重新登录。
5.登录态存储与使用(节点 J-L)
前端存储:用 wx.setStorageSync('token', 开发者返回的 Token) 保存到本地,下次打开小程序时可直接读取;后续请求:前端发起业务请求(如 “获取我的订单”)时,在请求头(如 Authorization: Bearer Token)或参数中携带 Token;服务器验证:开发者服务器接收请求后,先验证 Token 有效性(如检查 Token 是否存在、是否过期),验证通过则返回数据,否则返回 “登录失效”,前端需重新触发登录流程。
二、 需用户信息授权(如获取头像、昵称)
注意:微信已废弃旧的 wx.getUserInfo() 接口,当前需用 wx.getUserProfile() 且必须用户手动点击 “允许” 才能获取信息,不可静默授权。
文档直通车: 开放接口 /用户信息 /wx.getUserProfile
三、小程序获取手机号登录
1. 前置:获取临时登录凭证 code(节点 D)
操作方:小程序前端核心接口:wx.login()说明:与基础登录流程一致,静默获取 一次性、5 分钟有效期 的 code,用于后续换取 session_key(解密手机号的关键密钥),此步骤无需用户授权。
2. 触发手机号授权(节点 E-F)
文档直通车: 表单组件 /button
操作方:小程序前端 + 用户核心组件:需用小程序原生 button 组件,且设置 open-type="getPhoneNumber"(仅该类型按钮能触发手机号授权弹窗),示例代码:
用户交互:点击按钮后,微信会弹出系统弹窗(“允许小程序获取你的手机号?”),用户需手动选择「允许」或「拒绝」,不可静默获取。
3. 获取加密手机号数据(节点 H)
操作方:小程序前端触发事件:用户同意授权后,会触发 bindgetphonenumber 事件,回调参数中包含加密的手机号信息:
// 前端 JS
handleGetPhoneNumber(e) {
if (e.detail.errMsg === "getPhoneNumber:ok") {
// 用户同意授权,获取加密数据
const encryptedData = e.detail.encryptedData; // 加密的手机号数据
const iv = e.detail.iv; // 解密所需的初始向量
// 后续将 encryptedData、iv 与 code 一起发送给服务器
} else {
// 用户拒绝授权,提示并返回
wx.showToast({ title: "需授权手机号才能登录", icon: "none" });
}
}
关键说明:encryptedData 和 iv 是加密后的字符串,前端无法解密,必须传给开发者服务器,用 session_key 解密。
4. 服务器解密手机号(节点 J-L)
操作方:开发者服务器核心步骤:
换取 session_key:用前端传的 code,调用微信 auth.code2Session 接口(参数含 appid、appsecret、code),获取 openid(用户唯一标识)和 session_key(解密密钥);解密手机号:用 session_key、iv 对 encryptedData 进行解密(需用微信官方推荐的加密库,如 Node.js 用 crypto 模块,Java 用 AES 解密工具); 解密结果:解密后会得到包含手机号的 JSON 数据,示例:
{
"phoneNumber": "13800138000", // 用户手机号(核心信息)
"purePhoneNumber": "13800138000",
"countryCode": "86" // 国家码
}
5. 关联用户与生成登录态(节点 M)
操作方:开发者服务器核心逻辑:
用户关联:用解密后的手机号(或 openid)查询数据库,判断是否为新用户:
新用户:创建用户记录,存储手机号、openid 等信息;老用户:更新用户的 session_key(若已过期),关联已有账号; 生成登录态:创建自定义登录态(如 JWT Token、随机字符串),设置有效期(如 7 天),并将 Token 与用户信息关联存储(如 Redis、MySQL); 安全注意:session_key 和手机号属于敏感信息,不可返回给前端,仅在服务器端存储和使用。
6. 登录态存储与后续使用(节点 N-P)
前端存储:接收服务器返回的 Token,用 wx.setStorageSync('token', Token) 保存到本地,下次打开小程序时直接读取;后续请求:发起业务请求(如 “查询我的订单”)时,在请求头(如 Authorization: Token xxx)携带 Token;服务器验证:服务器接收请求后,验证 Token 有效性(是否存在、是否过期),通过则返回数据,否则提示 “登录失效”,触发重新登录。
四、具体代码实现
1.需求示例
一般登录按钮会有两种方式出现:
单独的页面(项目logo、登录、取消)
一个公共弹框(从页面底部或中间弹出授权弹框)
2.代码实现(这里以页面上显示登录按钮为例)
import Taro from '@tarojs/taro'
import loginService from "../../server/login/loginService"
// // 获取code
const getCode = async () => {
const { code } = await Taro.login()
Taro.setStorageSync('code', code)
}
const getphonenumber = async(e) => {
console.log("getphonenumber -------- e", e);
console.log("pageObj.code", Taro.getStorageSync('code'));
let { encryptedData, iv } = e.detail;
console.log("encryptedData-------------------------", encryptedData, iv);
if (!iv) return;
try {
let res;
// 将code 、 encryptedData、iv 发送到服务器
res = await loginService.login({ code: Taro.getStorageSync('code'), encryptedData, iv });
console.log("res", res);
// 登录成功处理
Taro.setStorageSync('token', res.data.access_token);
Taro.setStorageSync('userInfo', res.data.user_info);
// 一些你的逻辑,比如页面跳转:
// Taro.switchTab({
// url: '/pages/index/index'
// })
} catch (error) {
console.error('登录失败:', error);
Taro.showToast({
title: error.message || '登录失败',
icon: 'none'
});
getCode()
}
};
const cancelLogin = () => {
Taro.showToast({
title: '取消登录',
icon: 'none'
})
}
.demo-login-container {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
.btn {
width: 600rpx;
height: 80rpx;
background-color: #4ea311;
color: #fff;
font-size: 32rpx;
border-radius: 40rpx;
text-align: center;
}
}
我的项目框架为Taro,Uniapp和小程序原生也适用,改为对应的api就行
五、关键注意事项
1.解密失败排查:
若解密失败,常见原因:session_key 过期(wx.login() 需重新调用获取新code换 session_key)、encryptedData/iv 传输错误、appid/appsecret 配置错误;
2.用户拒绝授权处理:
需友好提示 “授权手机号是登录必要条件”,并提供重新授权入口(不可强制拦截用户);
注意 小程序审核是要求,用户能取消登录的,所以大家在做登录时,不能强制用户登录
3.安全性要求:
appsecret 和 session_key 必须在服务器端存储,严禁暴露在前端代码中(防止被窃取后解密用户手机号);
建议对前端与服务器的通信采用 HTTPS 协议,避免数据传输被劫持。