网站已运行 162 · 18小时 · 05 · 43
目录

微信小程序登录流程详解:wx.login、code 与后端 session

微信小程序临时 code 经业务后端换取身份并返回业务 session 的概念图

微信小程序登录流程不是调用一次 wx.login() 就结束。客户端拿到的是短期、一次性的 code;业务后端再用它向微信接口换取 openidsession_key,最后还要建立自己可续期、可失效的业务 session。

这篇文章按真实的前后端边界拆解整个过程,并给出小程序端与 Node.js 后端示例。开始前应先准备好 AppID,并确保 AppSecret 只存在于服务器环境变量中;如果环境还没配置,可以先看微信小程序账号、AppID 与开发者工具配置。项目里的登录请求封装建议放在 services/,目录职责可参考微信小程序项目目录结构详解

一、微信小程序登录流程中的四种凭证

数据由谁产生应该保存在哪里用途
codewx.login()不长期保存,用完即弃让业务后端临时换取微信侧身份信息
openidcode2Session业务数据库标识当前小程序中的微信用户
session_keycode2Session仅业务后端微信用户数据签名、解密等场景使用的会话密钥
业务 accessToken业务后端客户端保存原始 token;服务端保存会话或 token 摘要后续业务接口识别登录用户

最关键的区分是:code 不是用户 ID,也不是可以反复使用的访问令牌;session_key 不是业务 session;openid 可以关联用户记录,但单独把它传回服务器不能证明请求者就是该用户。

二、完整时序:客户端、业务后端与微信服务器如何配合

  1. 小程序调用 wx.login() 获取临时登录凭证 code
  2. 小程序立即把 code 发送给自己的业务后端。
  3. 业务后端携带 AppID、AppSecret 和 code 调用微信 code2Session
  4. 微信返回 openidsession_key,符合条件时还会返回 unionid
  5. 业务后端按 openid 查找或创建用户,并生成自己的随机业务 token。
  6. 小程序保存业务 token,后续请求只携带这个 token,不携带 AppSecret 或 session_key
微信小程序客户端、业务后端、微信接口和会话存储之间的登录流程图
本站原创示意图:临时 code 经业务后端调用 code2Session,后端再创建业务 session。点击图片可查看大图。

按当前微信开放文档,wx.login() 返回的 code 有效期为五分钟,并且只能使用一次。因此不应预取一批 code,也不要在 code2Session 失败后无限重试同一个 code。

三、小程序端:调用 wx.login 并把 code 交给后端

客户端只负责取得 code 和调用自己的登录接口。下面把登录封装为 Promise,页面或 app.js 可以按业务需要调用;示例中的域名必须是已配置的 HTTPS 合法域名。

// services/auth.js
function requestLogin(code) {
  return new Promise((resolve, reject) => {
    wx.request({
      url: 'https://api.example.com/auth/wechat/login',
      method: 'POST',
      data: { code },
      success: ({ statusCode, data }) => {
        if (statusCode === 200 && data.accessToken) {
          resolve(data)
          return
        }
        reject(new Error(data.message || '登录接口返回异常'))
      },
      fail: reject
    })
  })
}

export function login() {
  return new Promise((resolve, reject) => {
    wx.login({
      timeout: 8000,
      success: (result) => {
        if (!result.code) {
          reject(new Error('wx.login 未返回 code'))
          return
        }
        requestLogin(result.code).then(resolve).catch(reject)
      },
      fail: reject
    })
  })
}

拿到后端返回的 accessToken 后,可以写入本地存储,并由统一的请求封装添加到请求头。不要把 AppSecret 写进 project.config.jsonutils、云端可下载的配置文件或任何会进入小程序包的代码。

四、后端:用 code2Session 换取 openid 与 session_key

code2Session 是服务端 HTTPS GET 接口,请求参数包含 appidsecretjs_code 和固定值 authorization_code。下面是 Node.js 18+ 与 Express 风格的核心实现;userssessionStore 代表你项目中的数据库适配器。

import crypto from 'node:crypto'

const WX_SESSION_URL = 'https://api.weixin.qq.com/sns/jscode2session'
const SESSION_TTL_SECONDS = 2 * 60 * 60 // 业务示例值,不是微信规定

function sha256(value) {
  return crypto.createHash('sha256').update(value).digest('hex')
}

app.post('/auth/wechat/login', async (req, res) => {
  const code = String(req.body?.code || '').trim()
  if (!code) {
    return res.status(400).json({ message: '缺少 code' })
  }

  const query = new URLSearchParams({
    appid: process.env.WX_APPID,
    secret: process.env.WX_APPSECRET,
    js_code: code,
    grant_type: 'authorization_code'
  })

  try {
    const wxResponse = await fetch(`${WX_SESSION_URL}?${query}`, {
      signal: AbortSignal.timeout(8000)
    })
    const wxData = await wxResponse.json()

    if (wxData.errcode || !wxData.openid || !wxData.session_key) {
      console.warn('code2Session failed', {
        errcode: wxData.errcode,
        errmsg: wxData.errmsg
      })
      return res.status(401).json({ message: '微信登录凭证无效,请重试' })
    }

    const user = await users.findOrCreateByOpenId(wxData.openid)
    const rawToken = crypto.randomBytes(32).toString('base64url')

    await sessionStore.set(
      sha256(rawToken),
      {
        userId: user.id,
        openid: wxData.openid,
        sessionKey: wxData.session_key
      },
      SESSION_TTL_SECONDS
    )

    return res.json({
      accessToken: rawToken,
      expiresIn: SESSION_TTL_SECONDS,
      user: { id: user.id }
    })
  } catch (error) {
    console.error('wechat login request failed', error.message)
    return res.status(502).json({ message: '登录服务暂时不可用' })
  }
})

这个示例没有把 session_key 或 AppSecret 写进日志,也没有把它们返回给客户端。微信接口的业务失败会通过 errcodeerrmsg 表达,因此不能只检查 HTTP 状态码。生产环境还应校验环境变量、限制登录接口频率,并为微信请求设置超时。

五、为什么后端还要建立自己的业务 session

微信返回的是微信侧身份与会话材料,不会替你的应用管理会员状态、角色、封禁、退出和多设备登录。业务后端仍要生成自己的 token,并把它映射到内部用户 ID。这样才能在不暴露微信密钥的情况下完成鉴权,也能独立控制有效期与撤销。

方案适合场景需要注意
随机不透明 token + Redis/数据库 session希望即时退出、封禁或集中控制会话服务端保存 token 摘要,设置 TTL,并处理存储故障
短期 JWT + 刷新令牌多服务读取身份,能承担更复杂的密钥与撤销设计不要把长期敏感数据塞入 JWT;仍需设计刷新和吊销机制

对多数中小型小程序,不透明随机 token 更容易审计:客户端拿原始值,服务端只存其哈希;退出登录时删除会话记录即可。示例里的两小时只是业务演示值,应按风险、使用频率和续期策略自行确定。

六、后续接口如何校验业务 token

小程序后续请求可以把 token 放在 Authorization 请求头中。后端提取原始 token,计算同样的哈希并查询 session;查不到、过期或用户已被禁用时返回 401,而不是重新信任客户端提交的 openid

async function requireLogin(req, res, next) {
  const value = req.get('authorization') || ''
  const match = value.match(/^Bearers+(.+)$/i)

  if (!match) {
    return res.status(401).json({ message: '未登录' })
  }

  const session = await sessionStore.get(sha256(match[1]))
  if (!session) {
    return res.status(401).json({ message: '登录已过期' })
  }

  req.auth = { userId: session.userId }
  next()
}

客户端收到 401 后,应清理旧业务 token,再重新执行一次完整登录;不要在请求拦截器中并发触发多个 wx.login()。实际项目可以用一个共享 Promise 合并同时发生的刷新请求,避免多个 code 交叉覆盖登录结果。

七、openid、UnionID 和业务用户 ID 怎么对应

openid 是用户在当前小程序中的标识。只有小程序已绑定微信开放平台账号且满足返回条件时,接口才会提供 unionid;它用于同一开放平台主体下的跨应用身份关联,不能假设每次登录都有。

字段建议唯一约束使用方式
openidappid + openid定位某一个小程序中的微信身份
unionid有值时按开放平台范围设计跨小程序、公众号或应用合并身份前先核对绑定关系
内部 user_id业务数据库主键订单、权限、资料和 session 都关联它

数据库不要只把 openid 当作全平台唯一值。更稳妥的结构是保留内部 user_id,再维护微信身份表,至少记录对应的 AppID 与 openid。以后接入第二个小程序或公众号时,不必重构所有业务表。

八、wx.checkSession 能检查什么,不能检查什么

wx.checkSession() 检查的是微信侧登录态是否过期,也就是开发者服务器保存的 session_key 是否仍可能有效。它不能验证你自己的业务 accessToken,也不能代替业务后端的鉴权。

  • 业务 token 仍有效,但 wx.checkSession() 失败:普通业务接口仍可按你的策略工作;需要解密微信数据时重新登录。
  • wx.checkSession() 成功,但业务 token 已过期:仍要重新向业务后端建立 session。
  • 用户主动退出:清理客户端 token,并让后端删除或吊销对应业务 session。

九、code2Session 常见错误怎么排查

  • 40029,code 无效:确认没有重复使用同一个 code,AppID 与 AppSecret 属于同一个小程序,失败后重新调用 wx.login() 获取新 code。
  • 45011,调用过于频繁:不要立即无限重试;合并并发登录,并在稍后按退避策略重试。
  • 40226,登录被拦截:这是高风险用户拦截场景,应保留 errcode 与请求链路 ID,按官方安全方案处理,不能通过前端反复调用绕过。
  • 后端偶发超时:为微信请求设置超时,区分网络失败与业务错误,并避免在日志里输出带 AppSecret 的完整请求 URL。
  • 开发工具正常、真机失败:检查业务接口 HTTPS 证书、合法域名、环境配置和真机网络;不要只依赖开发工具里的“不校验合法域名”选项。

十、七个常见的登录实现错误

  • 把 AppSecret 写进小程序代码,认为混淆后就安全;
  • session_key 返回客户端,或完整写入应用日志;
  • wx.login() 成功等同于业务用户已经登录;
  • openid 当成客户端可以直接提交的登录凭证;
  • 重复使用、缓存或排队消费已经失效的 code;
  • 只检查微信接口的 HTTP 状态,不检查 errcode
  • 多个请求同时触发登录刷新,导致新旧 token 相互覆盖。

十一、上线前安全检查清单

  • AppSecret 只存在于后端密钥管理或环境变量中;
  • 客户端拿到 code 后立即发送,不长期缓存、不重复使用;
  • 后端检查 errcodeopenidsession_key,并设置请求超时;
  • session_key 不下发客户端,不进入普通日志;
  • 业务 token 使用足够随机值,服务端保存摘要并设置有效期;
  • 登录、刷新和失败重试都有限频与并发合并;
  • 用真机验证 HTTPS、合法域名、弱网、过期 token 和退出登录;
  • 数据库以内部用户 ID 关联业务数据,微信身份单独建映射。

把微信侧身份交换和自己的业务 session 分开之后,登录问题会清晰很多:前者解决“这个微信用户是谁”,后者解决“这次业务请求是否仍被允许”。排查时也可以沿着 code、code2Session、用户映射和业务 token 四个节点逐段定位,而不是在客户端反复调用 wx.login()

官方资料:微信开放文档《小程序登录》微信开放文档《wx.login》微信开放文档《小程序登录凭证校验》(访问日期:2026 年 8 月 24 日)

数臻源码猫咪图标
目录
数臻源码猫咪图标

目录

标签云: