小程序登录鉴权与用户体系:wx.login、code2session 与 Token

系统梳理微信小程序登录鉴权全流程,包括 wx.login 获取 code、服务端 code2session 换取 openid/unionid、自定义登录态 Token 设计、Session 管理与过期刷新、手机号快速验证与多端账号打通,并提供登录态安全加固方案。

小程序登录与常规 Web 登录有本质差异:微信不直接向开发者开放用户的密码体系,而是通过 wx.login 获取临时凭证 code,再由开发者服务端调用微信接口换取用户的 OpenID。理解这条「code → code2session → 自定义 Token」链路,是构建小程序用户体系的基石。本文将从登录整体流程出发,深入拆解登录态 Token 设计、Session 管理、手机号快速验证、多端账号打通与登录态安全,帮助开发者搭建一套可靠、可扩展、可审计的登录鉴权方案。

一、登录整体流程:从 wx.login 到 code2session

小程序登录采用「前端换 code、后端换身份」的双段式设计,前端永远接触不到微信核心密钥。

1. 小程序前端调用 wx.login  →  得到临时登录凭证 code
2. 前端将 code 通过 HTTPS 传给自有后端
3. 后端用 code + appid + secret 调用微信接口 code2session
4. 微信返回 openid / session_key /(可能含)unionid
5. 后端查库或建库,生成自定义登录态 Token 返回前端
6. 前端存储 Token,后续所有请求携带它完成身份认证

1.1 前端获取 code

wx.login 是最基础的 API,其回调 success 返回的 code 有效期约 5 分钟,且只能使用一次,使用后即失效:

// utils/auth.js
function wxLogin() {
  return new Promise((resolve, reject) => {
    wx.login({
      timeout: 10000,                 // 超时时间,默认 1000ms,推荐调大
      success: (res) => {
        if (res.code) {
          resolve(res.code);
        } else {
          reject(new Error('获取 code 失败:' + res.errMsg));
        }
      },
      fail: (err) => reject(err)
    });
  });
}

一句话:wx.login 不返回用户身份,只返回一张「5 分钟内有效、只能消费一次」的临时入场券 code,真正的身份换取必须发生在服务端。

1.2 服务端 code2session

服务端拿到 code 后,调用微信开放接口 code2session 换取身份信息:

// 服务端 Node.js(使用标准 fetch,无需额外 SDK)
async function code2session(code) {
  const url = 'https://api.weixin.qq.com/sns/jscode2session' +
    '?appid=' + APPID +
    '&secret=' + process.env.WX_SECRET +      // 密钥只存在服务端
    '&js_code=' + code +
    '&grant_type=authorization_code';

  const res = await fetch(url);
  const data = await res.json();
  // 成功响应:{ openid, session_key, unionid? }
  // 失败响应:{ errcode, errmsg }
  if (data.errcode) {
    throw new Error(`code2session 失败 ${data.errcode}: ${data.errmsg}`);
  }
  return data;
}

微信返回的 session_key 是会话密钥,用于解密用户敏感数据(如手机号);openid 是该小程序下的用户唯一标识;unionid 只有在用户将多个应用绑定到同一个开放平台账号下时才会返回。

字段说明典型用途
openid当前小程序内用户唯一 ID用户表主键、业务数据归属
session_key会话密钥,用于解密敏感数据解密手机号、encryptedData 数据
unionid开放平台下多应用统一 ID多端(小程序/公众号/App)打通

二、自定义登录态 Token 设计

微信的 session_key 不适合直接作为业务登录态下发前端:其一,它语义是「密钥」而非「凭证」;其二,它不由后端控制过期与撤销。实践中必须由服务端签发自有 Token。

2.1 JWT 无状态 Token

JWT 将用户身份与过期时间自包含在签名体中,适合微服务、多端共用的场景:

// 服务端:签发 JWT 登录态
const jwt = require('jsonwebtoken');

function issueToken(user) {
  return jwt.sign(
    {
      uid: user.id,            // 用户表主键
      openid: user.openid,
      role: user.role          // 权限字段按需放最小集
    },
    process.env.JWT_SECRET,
    { expiresIn: '7d' }        // 登录态有效期
  );
}

2.2 有状态 Session Token

对于单体应用或云开发场景,也可以使用「随机 Token + 服务端缓存」的有状态方案:Token 本身是一串无含义的随机值,用户信息存 Redis 或云数据库,注销时直接删除记录即可立刻失效。

// 服务端:有状态 Token 方案
const crypto = require('crypto');

async function createSession(user) {
  const token = crypto.randomBytes(32).toString('hex');
  await redis.set(`session:${token}`, JSON.stringify(user), 'EX', 7 * 24 * 3600);
  return token;
}
维度JWT(无状态)Opaque Session(有状态)
存储不需要服务端存储需要 Redis/DB 存储
失效/撤销需要黑名单机制直接删除记录即刻失效
分布式友好天然支持多实例需共享存储
适用场景微服务、多端共用单体、云开发、强管控

一句话:无论哪种 Token,前端都只能拿到随机凭证,绝不能拿到 openid 明文用于业务鉴权——身份判定必须由服务端根据 Token 反查。

三、Session 管理与过期刷新

3.1 前端存储与携带

前端将 Token 存入本地,请求时通过 Header 携带:

// 登录成功后
wx.setStorageSync('token', token);
wx.setStorageSync('expireAt', Date.now() + 7 * 24 * 3600 * 1000);

3.2 静默续期与 wx.checkSession

wx.checkSession 用于校验微信侧的会话(session_key)是否仍有效。注意它校验的是微信会话而非业务 Token,两者需协同处理:

// app.js —— 启动时静默恢复登录态
App({
  onLaunch() {
    this.ensureLogin().catch(() => {});
  },

  async ensureLogin() {
    const token = wx.getStorageSync('token');
    if (token) {
      // 先校验微信会话是否过期
      const sessionAlive = await new Promise((resolve) => {
        wx.checkSession({
          success: () => resolve(true),
          fail: () => resolve(false)
        });
      });
      if (sessionAlive) {
        // 会话未过期:用刷新令牌续期,无需重新走微信登录
        return this.refreshToken();
      }
    }
    // 会话过期或没有 Token:重新 wx.login 走完整登录
    return this.fullLogin();
  }
});

一句话:推荐「短期业务 Token + 长期刷新 Token」双层设计:业务 Token 过期用刷新 Token 静默续期,刷新 Token 失效才触发 wx.login 重新登录,避免用户频繁看到登录框。

3.3 用户资料(头像昵称)新规范

微信自 2022 年 10 月起不再建议通过 wx.getUserProfile 获取头像昵称,改为「头像昵称填写能力」,由用户主动填写:

<!-- 昵称输入框 -->
<input type="nickname" placeholder="请输入昵称" bindblur="onNickInput" />

<!-- 头像选择 -->
<button class="avatar-btn" open-type="chooseAvatar" bindchooseavatar="onChooseAvatar">
  <image src="{{avatarUrl}}" mode="aspectFill" />
</button>
// pages/profile/profile.js
Page({
  data: { avatarUrl: '/img/default-avatar.png', nickname: '' },

  onChooseAvatar(e) {
    const tempPath = e.detail.avatarUrl;   // 用户选择的临时头像路径
    // 上传头像到服务器/云存储,取回 CDN 地址持久化
    wx.cloud.uploadFile({
      cloudPath: `avatar/${Date.now()}.png`,
      filePath: tempPath
    }).then((res) => this.setData({ avatarUrl: res.fileID }));
  },

  onNickInput(e) {
    this.setData({ nickname: e.detail.value });
  }
});

头像与昵称属于用户敏感信息,应按隐私规范声明用途、仅在用户主动填写时获取,避免在启动阶段静默采集。

四、手机号快速验证

「手机号快捷登录」是小程序最常用的实名化手段,早期依赖 encryptedData 解密,如今官方推荐「code 换手机号」方案。

4.1 前端按钮组件

<!-- pages/login/login.wxml -->
<button
  class="phone-btn"
  open-type="getPhoneNumber"
  bindgetphonenumber="onGetPhoneNumber"
>
  手机号快捷登录
</button>
// pages/login/login.js
Page({
  onGetPhoneNumber(e) {
    const { code, errMsg } = e.detail;
    if (code) {
      // 将 phoneCode 交给服务端,服务端换取手机号
      this.request('/api/phone-login', { code });
    } else {
      wx.showToast({ title: '已取消授权', icon: 'none' });
      console.warn('手机号授权失败', errMsg);
    }
  }
});

4.2 服务端换手机号

服务端使用接口 getuserphonenumber,需要 access_token:

// 服务端:用 phoneCode 换取手机号
async function getPhoneNumber(code) {
  const url = 'https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=' + accessToken;
  const res = await fetch(url, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ code })
  });
  const { errcode, errmsg, phone_info } = await res.json();
  if (errcode !== 0) throw new Error(`${errcode}: ${errmsg}`);
  // phone_info: { phoneNumber, purePhoneNumber, countryCode }
  return phone_info.purePhoneNumber;
}

该接口仅对已认证小程序开放,code 有效期约 5 分钟。手机号明文只在服务端处理,前端拿到的应是脱敏展示或直接不展示,避免敏感信息落客户端。

五、多端账号打通:unionid 与绑定体系

当同一主体运营多款小程序、公众号或 App 时,需要识别「同一用户」。unionid 是官方提供的跨应用统一标识,前提是:

  1. 所有应用绑定到同一个微信开放平台账号(需主体一致)。
  2. 用户在这些应用中均完成了微信授权。

对于无法获得 unionid 的场景(如不同主体),可通过「手机号 + 验证码」或「手机号快捷验证」将各端账号绑定到自有统一账号中心:

打通方式前提统一标识适用场景
unionid同主体绑定开放平台unionid同主体多应用
手机号绑定自有账号体系手机号跨主体、跨端
扫码绑定需要二维码自有账号 IDPC/小程序互跳

六、登录态安全加固

登录鉴权是攻击面最集中的环节,必须遵守以下红线:

  • secret 绝不落地前端:code2session 只能发生在服务端,appsecret 一旦泄露等于整个用户体系沦陷。
  • code 单次使用:服务端应将消费过的 code 视为无效,防止重放。
  • Token 存储安全:不把 Token 写入 page 层可直接读取的全局变量之外的位置,避免 XSS/工具注入;重要操作二次校验。
  • 敏感数据不下发:session_key、openid 不随接口响应直接返回给业务层以外的地方。
  • 传输加密:全部走 HTTPS,配置域名白名单,禁止明文传输。
  • 风控与频控:登录接口加 IP/设备限流,异常高频 code2session 直接告警拉黑。
安全项措施风险等级
密钥管理secret 仅服务端、定期轮换高
code 防重放记录并拒绝重复 code高
Token 加密传输HTTPS + 请求签名高
会话固定防护每次登录签发新 Token中
数据脱敏手机号等敏感字段不下发中

七、云开发场景下的登录简化

如果使用云开发,登录链路会被大幅简化:云函数自动携带调用者身份,无需手动管理 code2session 与 access_token。

// cloudfunctions/login/index.js
const cloud = require('wx-server-sdk');
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV });

exports.main = async () => {
  const { OPENID, APPID, UNIONID } = cloud.getWXContext();
  const db = cloud.database();

  const { data } = await db.collection('users')
    .where({ _openid: OPENID }).limit(1).get();

  if (data.length === 0) {
    // 首次登录,创建用户
    await db.collection('users').add({ data: { _openid: OPENID, createdAt: db.serverDate() } });
  }
  return { openid: OPENID, unionid: UNIONID };
};

一句话:云开发通过 cloud.getWXContext() 把微信身份直接注入云函数,省去前端 code 透传与服务端密钥管理,代价是与腾讯云生态绑定更深。

八、常见问题与调试

  • code 一直失效:检查是否重复消费、是否超 5 分钟、是否用错 js_code 参数名。
  • 拿不到 unionid:确认应用已绑定开放平台、主体一致、用户已完成授权。
  • 手机号返回失败:确认小程序已认证、基础库版本 ≥ 2.21.2、code 未过期。
  • getUserProfile 已不推荐:2022 年后头像昵称改用 input type="nickname" 与 open-type="chooseAvatar",应避免继续依赖 getUserProfile。
  • 真机 vs 开发者工具差异:开发者工具可使用测试号与模拟数据,部分能力(如真实手机号)需在真机与正式配置下验证。

九、总结

环节核心 API / 机制关键要点
获取凭证wx.logincode 临时、单次、5 分钟有效
换取身份code2session服务端持有 secret,返回 openid/unionid/session_key
业务登录态自定义 TokenJWT 或 Opaque Session,服务端反查
过期刷新刷新 Token + wx.checkSession双层续期,静默重登
手机号验证open-type="getPhoneNumber"code 换手机号,明文不下发
多端打通unionid / 手机号绑定开放平台绑定或自有账号中心
安全加固服务端校验 / HTTPS / 频控secret 不落地,code 防重放
云开发简化cloud.getWXContext()免密钥管理,天然身份注入

登录鉴权是小程序全站安全的第一道门。正确理解 wx.login → code2session → 自定义 Token 三层链路,做到「前端不接触密钥、服务端不信任前端、敏感数据不下发」,才能为后续的订单、支付、隐私数据等核心业务建立可信的身份底座。在此基础上,可进一步参考本专题的安全与合规实践与全栈项目实战,将登录体系接入完整业务闭环。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「miniprogram」更多文章

  1. 小程序动画与 Canvas 实践:交互动效、海报生成与可视化
  2. 微信支付与交易闭环:统一下单、回调与退款
  3. 小程序分包加载与性能优化进阶:主包瘦身、预下载与按需注入