ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

微信小程序登录模块详解:从登录设计到隐私合规落地

微信小程序登录模块详解:从登录设计到隐私合规落地 微信小程序开发做到第九期登录模块和个人信息这块基本是绕不过去的坎。我见过太多项目在功能开发到一半时被登录逻辑卡住要么是登录态失效导致用户频繁重新授权要么是拿不到完整的用户信息导致后续业务没法开展。这一篇把登录模块从流程设计到代码落地、从个人信息获取到隐私合规约束完整梳理一遍都是实操层面的东西直接照着做就行。1. 登录模块的整体设计思路1.1 登录模块在小程序里的定位登录模块表面上看只是“让用户进来”但实际承担的东西比想象中多。它是用户身份的起点所有后续业务——订单查询、收藏同步、购物车恢复、会员等级识别——都依赖登录后拿到的用户唯一标识。没有这一层小程序就只能做纯浏览型内容一旦涉及用户个性化数据就必须有登录态。很多新手容易犯的第一个错误是把登录理解成“调一下wx.login就完事”。实际上的登录是一整条链路小程序端发起登录请求、后端接收code、后端调微信接口换取身份信息、生成自有的登录凭证、小程序保存凭证、后续每个请求都携带凭证供后端校验。完整闭环缺一环都会出问题。1.2 为什么不能只用微信的openid我先说一个很多人踩过的坑直接把openid当用户ID存数据库、甚至把openid返回给前端当登录凭证。短期看确实能用但隐患非常大。openid是微信体系内用户在小程序维度的唯一标识它的定位是“识别用户”不是“登录凭证”。如果前端持有openid就能访问接口那任何人只要通过某种方式拿到别人的openid就能伪装成那个用户操作数据。这等于把大门钥匙直接挂在了门口。正确的做法是后端用openid找到或创建用户记录然后生成一个自有的token可以是随机字符串也可以是JWT把token返回给小程序端。后续所有请求都携带这个token后端通过token确认用户身份。1.3 登录态的生命周期设计登录态不能设计成“永久有效”也不能设计成“每次打开都要重新登录”。前者有安全风险——token一旦泄露就长期可用后者体验极差——用户每次打开小程序都要经历授权、跳转、加载。我常用的方案是双token机制一个短期token比如2小时有效一个长期refresh token比如30天有效。请求接口时用短期token过期后拿refresh token去换新的短期token整个过程中用户无感知。如果refresh token也过期了就触发重新登录。不过针对大部分中小型小程序双token有点重了。更务实的做法是设计一个7天或30天有效的token在用户每次打开小程序时检查剩余有效期如果小于某个阈值就自动静默续期。这样既保证安全又不需要引入复杂的刷新逻辑。2. wx.login到code2Session的完整链路2.1 wx.login获取code这一步做了什么小程序端调用wx.login()微信会生成一个临时登录凭证code。这个code有几个关键特性有效期只有5分钟、一次性使用用过了就失效、每个code只能换取一次身份信息。这个设计是为了安全因为code会在小程序端和后端之间传递如果code能长时间有效或者重复使用攻击者就能拿着一个code反复尝试换取用户信息。同时code本身不包含任何用户身份信息它只是一张“临时票据”真正换取身份信息的动作必须由后端完成。还有一点容易忽略code只能换取到openid和session_key拿不到用户的手机号、头像、昵称这些资料。很多人以为登录一次就能获取全部用户信息这是误解。登录只解决“你是谁”的问题其他信息需要通过专门的接口获取。2.2 后端code2Session的调用细节后端拿到前端传来的code后需要请求微信的接口地址是https://api.weixin.qq.com/sns/jscode2session?appidAPPIDsecretSECRETjs_codeCODEgrant_typeauthorization_code请求参数里appid是小程序AppIDsecret是小程序密钥grant_type固定是authorization_codejs_code就是前端传来的code。这几个参数有一个出错就会失败。这里需要特别提醒secret绝对不能放在小程序前端代码里。小程序代码是下发到用户设备上运行的任何写在小程序代码里的内容都可能被反编译获取。secret一旦泄露别人就能冒充你的小程序后端去调用微信接口。正确做法是secret只配置在后端服务器的环境变量或配置文件中。接口返回的内容包含openid和session_key。openid是用户在小程序下的唯一标识session_key是会话密钥用于解密用户手机号、微信运动数据等敏感信息。session_key同样非常重要不能泄露给前端它只在后端使用。2.3 首次登录与老用户返回的处理后端拿到openid后要先查数据库里有没有这个openid对应的用户。如果不存在说明是新用户需要创建一个用户记录如果已经存在说明是老用户直接登录。我建议用户表结构至少包含id主键、openid唯一索引、unionid如果有多端打通需求、昵称、头像、手机号、创建时间、最后登录时间、token有效期字段。其中openid要建唯一索引避免并发情况下重复插入用户记录。新用户创建时可以设置默认昵称比如“微信用户”、默认头像后面再让用户去完善。不要强制用户一进来就填完整资料很多用户会因为嫌麻烦直接退出。渐进式获取用户信息是更好的策略。3. 登录态管理与请求封装实战3.1 前端登录态存储方案前端获取到token后需要保存到本地。小程序里有两种存储方式wx.setStorageSync同步和wx.setStorage异步。我习惯用同步方式写登录态因为登录初始化通常是串行流程同步方法写起来更直观。// 保存登录态 wx.setStorageSync(token, res.token) wx.setStorageSync(userInfo, res.userInfo) // 读取登录态 const token wx.getStorageSync(token)这里有一个容易被忽略的点token不能只存在内存变量里。小程序冷启动时所有内存数据都会清空从storage里恢复登录态是非常关键的一步。每次小程序启动在App.onLaunch里读取本地token如果存在就直接进入已登录状态不存在才去走登录流程。还要注意不要用过于简单的key名比如“token”“user”在小程序全局存储空间里很容易和其他插件或第三方SDK的key冲突。我习惯用项目前缀比如order_app_token、order_app_user一眼就能看出归属排查问题也方便。3.2 登录流程代码实例看一个完整的wx.login调用流程这套代码可以直接复制到项目里function login() { return new Promise((resolve, reject) { wx.login({ success: (res) { if (res.code) { // 请求后端接口用code换取自定义token wx.request({ url: https://api.example.com/auth/login, method: POST, data: { code: res.code }, success: (response) { if (response.data.code 0) { const { token, userInfo } response.data.data wx.setStorageSync(order_app_token, token) wx.setStorageSync(order_app_user, userInfo) resolve(userInfo) } else { reject(new Error(登录失败)) } }, fail: (err) reject(err) }) } else { reject(new Error(获取code失败)) } }, fail: (err) reject(err) }) }) }这套代码的重点在于把外层封装成Promise业务层调用时可以用async/await来写逻辑会清晰很多。登录失败时reject出来上层统一处理错误提示避免每处调用都写一遍错误处理。3.3 请求拦截器里如何带token和自动续期裸的wx.request每次都要手动写header很烦而且登录态失效时还需要统一处理。我通常是在封装请求工具时把token和失效处理都内置了。const request (options) { const token wx.getStorageSync(order_app_token) return new Promise((resolve, reject) { wx.request({ url: options.url, method: options.method || GET, data: options.data || {}, header: { Content-Type: application/json, Authorization: token ? Bearer ${token} : }, success: (res) { // 约定后端返回码0成功401登录失效 if (res.data.code 401) { // 清理本地登录态跳转登录页 wx.removeStorageSync(order_app_token) wx.navigateTo({ url: /pages/login/login }) reject(new Error(登录已过期)) return } resolve(res.data) }, fail: (err) reject(err) }) }) }自动续期的逻辑可以在请求拦截器里判断token剩余有效时间如果临近过期就调用刷新接口。后端生成token时可以同时返回过期时间戳前端对比当前时间和过期时间剩余不足30分钟就触发刷新。这比拿到401再重新登录的体验好很多。要留意的是不要在“每次请求”都去刷新token那会白白消耗服务器资源。只有剩余时间低于阈值时才刷新而且刷新接口要加防重复请求机制避免并发场景下多个请求同时触发刷新导致token被覆盖。3.4 退出登录要注意清理哪些数据退出登录不是简单清掉token就完事。要清理的数据包括本地存储的token、缓存的用户信息、可能存在的购物车缓存、页面内状态数据。如果不清干净可能出现退出登录后又切换到另一个账号结果购物车还是上一个账号的数据。function logout() { // 先通知后端让token失效可选但推荐做 wx.request({ url: https://api.example.com/auth/logout, method: POST }) // 清理本地存储 wx.removeStorageSync(order_app_token) wx.removeStorageSync(order_app_user) // 清理其他业务缓存 wx.removeStorageSync(order_app_cart) // 回到登录页 wx.reLaunch({ url: /pages/login/login }) }要特别说明的是wx.clearStorageSync()虽然一步到位但不推荐。它会把小程序所有缓存都清掉包括一些业务上长期有效的缓存比如商品列表缓存、设置项缓存。退出登录最好只清理和用户身份强相关的数据业务缓存可以根据需要保留。4. 个人信息的获取与隐私合规4.1 头像昵称填写的标准做法很多开发者的知识还停留在用wx.getUserInfo或者button open-typegetUserInfo来获取用户信息。2021年后微信调整了策略这种方案获取到的头像和昵称已经变成了默认灰色头像和“微信用户”基本等于拿不到东西。现在的标准做法是使用头像昵称填写能力。用户在页面上主动点击头像选择区域触发微信的头像选择器点击昵称输入框弹出微信的昵称填充组件。这样做的好处是完全符合隐私规范用户对自己提供的信息有明确的知情权和选择权。看一段实现代码!-- 头像选择 -- button classavatar-wrapper open-typechooseAvatar bind:chooseavataronChooseAvatar image src{{avatarUrl}} modeaspectFill/image /button !-- 昵称输入 -- input typenickname placeholder请输入昵称 value{{nickname}} bindinputonNicknameInput /Page({ data: { avatarUrl: /assets/default-avatar.png, nickname: }, onChooseAvatar(e) { this.setData({ avatarUrl: e.detail.avatarUrl }) }, onNicknameInput(e) { this.setData({ nickname: e.detail.value }) } })这里有一个比较大的坑e.detail.avatarUrl返回的是一个临时文件路径在本地能直接显示但一旦用户关闭小程序或者本地缓存被清理这个路径就失效了。正式环境下需要先调用wx.uploadFile把头像文件上传到你的服务器或云存储拿到一个稳定的URL后再保存到数据库。4.2 手机号获取的正确姿势获取手机号是很多业务场景的刚需比如下单需要手机号联系用户、会员需要绑定手机号。微信提供了便捷的手机号快速验证组件用户点击后无需手动输入号码微信直接返回经过验证的手机号。实现方式是在页面里放一个button设置open-typegetPhoneNumber绑定bindgetphonenumber事件button open-typegetPhoneNumber bindgetphonenumberonGetPhoneNumber 微信一键登录 /button事件回调里会返回e.detail.code把这个code传给后端后端再调用微信接口换取手机号。从微信官方文档来看手机号换取需要用到session_key进行解密但这个动作必须全部在后端完成。手机号code也是一次性的使用后立即失效。而且手机号获取需要小程序已经通过微信认证未认证的小程序没有这个能力。个人主体小程序需要先完成微信认证才能使用这个功能。4.3 用户隐私保护指引的配置微信对个人信息保护越来越严格小程序后台需要配置“用户隐私保护指引”明确声明小程序收集了哪些个人信息、用于什么目的、如何保护。如果不配置调用手机号快速验证组件时会直接报错。在微信公众平台小程序后台能找到“设置”——“服务内容声明”——“用户隐私保护指引”入口。配置时要逐项声明收集的信息类型包括位置信息、手机号、头像、昵称等并在“用途”里写清楚。小程序代码里如果需要使用隐私相关接口还有一个前置动作在app.json里声明requiredPrivateInfos字段把需要的接口列出来。不声明的话很多隐私接口根本调用不了。而且隐私弹窗授权这块可以使用wx.requirePrivacyAuthorize主动触发授权流程让用户明确了解你收集了什么数据。4.4 信息收集的边界和克制原则在个人信息收集上“能少收就少收”是硬道理。微信官方对个人信息的收集遵循“最小必要”原则开发者仅能收集实现当前功能所必需的信息。多收的信息用不到反而增加了隐私合规风险。实际操作时我会给项目做一个信息清单业务功能清单、对应需要的用户信息、存储位置、使用期限。比如下单功能需要手机号和收货地址但不需要微信运动数据用户主动填写生日信息时如果业务上没有用到就提示用户选填。这里要说明一个实际问题即使做了用户隐私保护指引也不能保证用户一定会同意授权。所以业务流程里凡是依赖用户信息的环节都要有“用户拒绝授权”的兜底方案。比如拿不到手机号可以引导用户手动输入手机号加验证码来验证。5. 常见问题深入排查5.1 code无效和session_key解密失败开发联调时最常遇到的报错是“code无效”排查方向集中在几个点code是否在5分钟内使用、code是否已经被使用过、后端请求微信接口时appid和secret是否正确、小程序AppID和后端配置的AppID是否同一个项目。如果是session_key解密失败大概率是解密流程出了偏差。前端调用getPhoneNumber拿到的code后端换取手机号时需要先用code换取session_key再用session_key配合iv和encryptedData解密。如果前端传的iv被截断、encryptedData编码方式不对、或者session_key已经过期都会导致解密失败。调试时我习惯在解密环节把每一步的中间结果都打出来请求微信接口的返回、判断session_key是否有效、尝试解密的报错信息。看到哪一步断了问题基本就定位到哪一步。5.2 token失效后并发请求的处理多个请求同时发出token恰好过期了就会有一批请求同时返回401。如果每个请求都跳转登录页体验就会很怪异。处理这个问题的思路是设置一个“正在刷新token”的全局标志第一个请求发现token失效就触发刷新流程其他请求进入等待队列等刷新完成后拿着新token重试。let isRefreshing false let retryQueue [] function handleTokenExpired(originalRequest) { if (isRefreshing) { // 等待刷新完成放入重试队列 return new Promise((resolve) { retryQueue.push({ originalRequest, resolve }) }) } isRefreshing true return refreshToken().then((newToken) { isRefreshing false wx.setStorageSync(order_app_token, newToken) // 重放队列中的请求 retryQueue.forEach(item item.resolve(request(item.originalRequest))) retryQueue [] // 重试当前请求 return request(originalRequest) }).catch((err) { isRefreshing false retryQueue [] wx.reLaunch({ url: /pages/login/login }) throw err }) }这段逻辑看起来复杂实际就是“一次刷新、全部恢复”的模式。我处理过很多线上问题这个并发处理是最容易出bug的地方主要坑在重试队列的清空时机刷新成功后必须清空队列否则下次token过期时会出现重复重试。5.3 头像临时文件路径失效问题头像使用临时路径展示在页面上没问题但用户重新进入页面、换一台设备、过一段时间后临时路径就失效了。解决方案是头像上传到你自己的服务器后使用正常URL地址。上传时机最好放在用户点击“保存”或“提交”时统一处理不要每次选择头像就立刻上传。一来用户可能选了又换频繁上传浪费流量二来统一提交时做一次上传代码也简洁。5.4 登录态正常但业务数据不对这种情况最隐蔽实现登录后业务数据不对很多人会去查后端逻辑结果问题出在账号体系的混乱上。打开调试工具看下请求头确认前端发送的Authorization是不是当前登录账号的token。另一种情况是测试时用过多个微信号切换手机上残留了上一个账号的token当前显示的界面是新账号但请求头可能还是旧账号的数据。针对性清理一下storage或切换账号时主动清一次login缓存就解决了。5.5 真机调试常见坑开发者工具里登录流程一切正常一到真机就报错优先排查以下三个地方后端接口是否配置了域名白名单wx.request的合法域名、真机网络环境是否受限、代码中是否有依赖开发者工具专属能力的操作。域名白名单要在微信公众平台的开发设置里配置而且必须配置HTTPS。如果后端接口是http测试接口开发者工具可以勾选“不校验合法域名”但真机必须配好白名单才能跑通。这个问题和登录模块关系很大很多开发者小程序的登录请求都挂在真机域名校验上。6. 登录态安全加固的几个细节登录模块涉及用户数据安全不能只看功能跑通。我额外补充几个和登录相关但容易被忽略的安全加固措施。6.1 后端接口防止重放攻击用户登录获取token时如果攻击者截获了登录请求他能不能拿着这个请求重复发送来获取token防护方案就是防止重放后端检查client请求里带的时间戳和随机数同类请求在短时间内多次出现就拒绝服务。不过业务量不大的小程序不建议一开始就上重放防护复杂度太高。先保证token本身的安全比如使用HTTPS传输、token不过长、后端限制token泄露后的危害范围。6.2 token存storage的风险和替代方案token存wx.setStorageSync的风险在于小程序的storage文件其实存在用户设备本地理论上被root过的设备可以读到。高安全要求的场景金融类、政务类就不建议纯token方案可以采用session id加设备绑定的方案或者登录态有效时间缩短降低泄露窗口。具体说可以把openid、设备唯一标识、session版本号等信息在后台绑定请求时除了带token还传设备标识后端校验token和设备的绑定关系。实现难度没有想象中大但安全等级明显提升。6.3 测试号与正式环境的隔离很多项目快速开发时喜欢直接用测试号实际隐患是测试号拿到的openid和正式环境下不完全一致不同的小程序AppID对应不同的openid体系换了AppID用户就不是同一个用户了。测试环境尽量用独立的测试AppID生产环境用正式AppID数据库里加环境字段做隔离。否则一旦切正式环境老数据全部对不上号用户以为自己信息丢了业务上就是事故。7. 从登录模块到账号体系的扩展思考登录模块是一个人信息系统的地基真正上生产后要在这个基础上扩展的东西非常多。无论你的项目体量多大下面这四个方向迟早要面对。7.1 多端打通与unionid体系如果你的项目同时有公众号、小程序、App等多端业务建议一开始就往unionid方向设计。用户在同一个微信开放平台账号下的多个应用unionid是相同的。通过unionid就能识别出同一个用户在多个端的行为做跨端用户数据打通。获取unionid的前提是调code2Session接口时返回了unionid字段。这个字段不是所有小程序都有需要小程序绑定到微信开放平台账号下而且用户需要关注过关联的公众号或在开放平台账号下使用过小程序才会在特定条件满足时返回unionid。7.2 用户画像的建立登录之后用户的操作行为会持续沉淀。是否浏览过某些分类、是否收藏、是否下单、购买偏好是什么——这些数据积累起来就是用户画像。做用户画像时注意信息的使用边界不要为了“丰富画像”就过度收集行为数据要围绕能提升用户体验和业务转化的维度来收集。7.3 新用户引导与老用户召回登录模块是决定用户首次体验的分水岭。新用户首次进入建议先引导完成必要的授权让用户感受到价值先逛起来再逐步补充信息。老用户再次打开时自动恢复登录态后可以把个性化推荐、最近浏览、购物车这些直接放出来增强回访粘性。我观察过很多小程序登录注册环节的流失率常年在20%到40%之间多数是因为流程太繁琐。从这个角度看登录模块优化始终是投入产出比很高的部分。7.4 账号注销和用户数据删除这个容易被忽略但合规上很重要。按照个人信息保护相关要求用户应当有权注销账号并删除自己的个人信息。后台要提供账号注销功能注销后相关数据要同步删除或匿名化处理。小程序后台有一个“账号注销”开关开启后用户可以在小程序内发起注销。后端也要写对应的注销接口把用户相关信息做清理。这个需求看似不紧急但做隐私合规审查时是必查项建议在项目早期就预留注销逻辑。从整体来看登录模块虽然只是小程序的一个基础模块但它牵扯的业务面最广从前端交互体验到后端安全性再到隐私合规每一层都会影响最终用户的信任度。根据我个人经验这个模块值得投入时间去认真设计前期多花一天后期的排查和返工成本能省下好几倍。项目中如果遇到拿不准的场景回到基础流程上梳理一遍——前端获取code、后端换openid、生成token、保存登录态、携带token请求接口——绝大部分问题都能找到答案。
返回列表