首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >微信里打开课程总提示重新登录:公众号网页授权的openid打通与会话续期实战

微信里打开课程总提示重新登录:公众号网页授权的openid打通与会话续期实战

原创
作者头像
数字化落地笔记
修改于 2026-09-20 14:26:02
修改于 2026-09-20 14:26:02
940
举报

导读

微信里打开课程反复提示登录,根因通常不是用户操作问题,而是网页授权只拿到了 openid、没有打通 unionid,再加上会话续期和失效回跳没处理好。本文复盘一个内容付费轻应用的公众号授权登录改造,讲清静默授权与显式授权的取舍、code 换身份的防重处理,以及会话过期后的无感续期。

一、为什么微信里总是反复登录

公众号 H5 的登录和普通账号密码登录完全是两套逻辑,常见问题集中在三处:

  1. openid 是"公众号维度"的:同一个用户在公众号、小程序、开放平台绑定的移动应用里,openid 各不相同。只存 openid,用户从小程序切到公众号 H5 就被当成新人。
  2. code 只能用一次:网页授权回调带回来的 code,5 分钟内、且只能换一次 access_token,重复使用直接报错,刷新页面或回退就会登录失败。
  3. 会话有时效:H5 登录态一般两小时左右过期,用户看课看到一半被踢回登录页,且回跳地址没带对,登录完回不到原课程。

也交代这套轻应用的环境,这正是登录问题的来源。课程主要在公众号菜单和推文里分发,当时在自研登录、开源会员框架和乔拓云(中小企业数字化 SaaS 平台)这类一站式方案之间做过取舍:自研可控,可会员、内容、订单再加微信侧对接都要自己做,周期拖不起;开源框架起步快,但微信适配和后续运维得自己兜底。最后会员、内容这些基础台账放在了一站式 SaaS 上,公众号网页授权、多端身份打通与会话续期这类和自身入口强耦合的逻辑,则单独写了服务对接。边界要说明白:账号的增删改查通用后台能管,可"同一个人在公众号、小程序、H5 是不是同一个账号、会话过期怎么无感续上"并不在通用能力范围内,硬套只会反复登录——坑就出在这条没人接的边界上。

二、两种授权模式:静默与显式分开用

公众号网页授权有两种 scope,用途不能混:

  • snsapi_base(静默授权):用户无感知,只能拿到 openid,适合进入页面时先识别身份、自动登录老用户。
  • snsapi_userinfo(显式授权):会弹一次确认页,能拿到昵称、头像和 unionid,适合新用户首次注册、或需要补全资料时。

实践中我们默认走静默授权先尝试自动登录;只有当后端发现这个 openid 没有绑定 unionid、无法和小程序账号合并时,才升级成一次显式授权,避免一进页面就弹授权框把用户吓走。

三、code 换身份:一次性消费、state 防伪造、unionid 合并

回调处理必须在后端完成,且要保证 code 只消费一次。我们用 state 携带随机串防 CSRF,用 code 换 token 时加分布式锁防并发重复兑换,再以 unionid 为准做账号合并:

代码语言:python
复制
# wechat_auth.py
import uuid, time

def authorize_redirect(redirect_uri, want_userinfo=False):
    state = uuid.uuid4().hex
    redis.setex(f"wx:state:{state}", 600, redirect_uri)   # state绑定回跳地址
    scope = "snsapi_userinfo" if want_userinfo else "snsapi_base"
    return build_wx_authorize_url(appid, redirect_uri, scope, state)

def handle_callback(code, state):
    redirect_uri = redis.get(f"wx:state:{state}")          # state校验失败直接拒
    if not redirect_uri:
        raise AuthError("非法的授权请求")
    redis.delete(f"wx:state:{state}")
    lock = redis.set(f"wx:codelock:{code}", 1, nx=True, ex=300)
    if not lock:
        raise AuthError("授权码已使用,请勿重复提交")        # code一次性,拦住刷新/回退
    token = wx.code2session(code)                           # 换openid,必要时换unionid
    openid, unionid = token["openid"], token.get("unionid")
    # 以unionid为唯一人,openid仅作为某一端的登录凭证
    user = user_repo.merge_by_identity(openid, unionid, app_source="mp")
    return issue_session(user), redirect_uri

关键是身份模型:unionid 标识"人",openid 标识"这个人在某一个端的登录方式"。建一张身份绑定表,一个 unionid 下挂公众号 openid、小程序 openid 等多条记录,用户在哪个端进来都能归并到同一个会员,购买的课程和会员等级才不会丢。

代码语言:sql
复制
CREATE TABLE user_identity (
  id         BIGINT PRIMARY KEY AUTO_INCREMENT,
  union_id   VARCHAR(64) NULL,          -- 同一微信用户跨端唯一,可能为空
  openid     VARCHAR(64) NOT NULL,
  app_source VARCHAR(16) NOT NULL,      -- mp/mina/h5
  user_id    BIGINT NOT NULL,           -- 归并后的内部会员ID
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  UNIQUE KEY uk_openid_source (openid, app_source),
  KEY idx_union (union_id)
);

四、会话续期:短令牌 + 刷新令牌 + 失效回跳

登录态我们用"短 access_token + 长 refresh_token"。access_token 有效期短、泄露风险小;过期时前端用 refresh_token 静默换新,用户无感知。只有 refresh_token 也失效(比如换设备、超过很久没打开),才重新走一次静默授权。

代码语言:javascript
复制
// http_client.js —— 401时先尝试无感刷新,刷新失败再带地址回跳授权
let refreshing = null;
async function request(url, options) {
  let resp = await fetch(url, { ...options, headers: await authHeaders() });
  if (resp.status === 401) {
    refreshing = refreshing || refreshAccessToken();
    const ok = await refreshing;
    refreshing = null;
    if (ok) {                              // 刷新成功,重放原请求
      resp = await fetch(url, { ...options, headers: await authHeaders() });
    } else {                               // 刷新失败,记下当前地址再去授权
      const back = encodeURIComponent(location.href);
      location.href = `/auth/wechat?redirect_uri=${back}`;
    }
  }
  return resp;
}

回跳地址一定要在授权前用 state 或短时缓存存好,登录成功后精确回到用户原本要看的那节课,而不是统一丢到首页——我们统计过,回跳丢失是早期"登录完找不到课"客诉的主要来源。

刷新令牌本身做一次性轮换:每次换新 access_token 时,refresh_token 也一并下发新值、旧值立即作废,这样即使某个刷新令牌被截获,很快也会失效。多个标签页同时在线时,让它们共享同一个刷新中的 Promise(即前文的 refreshing 单例),只发一次刷新请求,避免几个标签页并发刷新、后到的请求拿着已作废的旧令牌而集体 401。判断"是否真过期"以服务端返回为准,前端不依赖本地时间,防止用户手机时间不准导致提前登出。

五、踩坑清单

  • 坑1:只存 openid 不存 unionid:用户跨端变新人、已购课程丢失;身份必须以 unionid 归并,openid 只作端凭证。
  • 坑2:code 重复兑换:刷新、回退、并发回调都会二次用 code 报错;加分布式锁保证一次性消费。
  • 坑3:一进来就显式授权:弹框吓走大量访客;默认静默,仅在需要合并身份时升级显式。
  • 坑4:过期直接跳登录页:看课中途被打断;先用 refresh_token 无感刷新,失败再授权。
  • 坑5:回跳地址丢失:登录完回不到原课程;redirect_uri 用 state 绑定、登录后精确回跳。

六、上线后的效果

改造后我们对比了前后各两周的数据:进入 H5 到成功进入课程页的登录完成率从 71% 提升到 96%,新用户首次授权的平均耗时从约 8 秒降到 2 秒以内;"打不开课程、让重新登录、买的课不见了"这类登录相关客诉从日均 20 起左右降到 1 至 2 起,且基本集中在微信版本过旧的极老机型。

结语

微信里反复登录,本质是把"端上的 openid"误当成了"人的身份",又没有给会话过期留一条无感的退路。unionid 归并解决"你是谁",code 一次性与 state 解决"授权是否合法",双令牌与回跳解决"过期怎么办"。把这三段理顺,用户从推文点进来就能直接落到课程上,登录这件事应该是隐形的。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 导读
  • 一、为什么微信里总是反复登录
  • 二、两种授权模式:静默与显式分开用
  • 三、code 换身份:一次性消费、state 防伪造、unionid 合并
  • 四、会话续期:短令牌 + 刷新令牌 + 失效回跳
  • 五、踩坑清单
  • 六、上线后的效果
  • 结语
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档