Featured image of post 微信扫码登录三种方案对比:开放平台、公众号、扫码即登录

微信扫码登录三种方案对比:开放平台、公众号、扫码即登录

三种主流的微信扫码登录方案——开放平台 OAuth、公众号 OAuth、关注公众号免授权登录,把流程、协议、踩坑点全讲清楚

用户体验决定一切

国内 Web 应用的登录方式里,微信扫码已经是事实标准——比手机号验证码更顺、不用记密码、用户基本人手都有微信。

但"微信扫码登录"实际上有好几种不同的方案,对应不同的用户体验和接入条件。新人接这块时常常分不清:

  • 有的网站扫码后跳到微信"授权"页面再回来登录
  • 有的网站扫码就完事了,不需要二次确认
  • 有的扫码是公众号场景,扫完关注还能登录
  • 有的是"网站二维码 + 用手机微信扫" → 登录 PC 端

每种背后用的是完全不同的微信能力。本文把三种主流姿势梳理清楚——微信开放平台 OAuth、公众号 OAuth、关注公众号即登录


一、微信家族里的"应用类型"

要选对登录方案,先要搞清微信家的几类账号:

账号类型适用场景
微信开放平台应用网站、App 接入"使用微信登录"按钮
公众号(订阅号)内容推送
公众号(服务号)业务对接、可以做 OAuth 登录
微信小程序小程序生态
企业微信应用企业内部

我们要讲的扫码登录只涉及前两个——开放平台服务号


二、方案 A:微信开放平台 OAuth(PC 网站扫码)

最经典的"网页扫码登录"——你在 PC 端访问 12306 / 知乎 / 京东 PC 端,点"微信登录",弹出二维码,手机微信扫一扫确认即可登录。

流程

接入步骤

1. 在微信开放平台注册"网站应用"

  • 登录 open.weixin.qq.com
  • 创建网站应用,提交资质(要企业认证)
  • 拿到 AppIDAppSecret

2. 前端跳转授权链接

1
2
3
4
5
6
7
const url = `https://open.weixin.qq.com/connect/qrconnect`
        + `?appid=${APP_ID}`
        + `&redirect_uri=${encodeURIComponent(CALLBACK)}`
        + `&response_type=code`
        + `&scope=snsapi_login`
        + `&state=${randomState}`;
window.location.href = url;

state 是 CSRF 防御——后端要校验回调时的 state 与前端发出的一致。

3. 后端用 code 换 access_token

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@GetMapping("/auth/wechat/callback")
public String callback(@RequestParam String code, @RequestParam String state) {
    // 校验 state(防 CSRF)
    if (!stateService.consume(state)) {
        throw new BusinessException("invalid state");
    }

    // 换 access_token
    String url = "https://api.weixin.qq.com/sns/oauth2/access_token"
            + "?appid=" + APP_ID
            + "&secret=" + APP_SECRET
            + "&code=" + code
            + "&grant_type=authorization_code";
    Map resp = restTemplate.getForObject(url, Map.class);
    // resp 里有 access_token, openid, unionid

    // 拉用户信息
    User user = userService.bindOrCreate((String) resp.get("openid"),
                                         (String) resp.get("unionid"));

    // 颁发自己的会话 token
    return "redirect:" + frontUrl + "?token=" + jwtService.issue(user);
}

关键点:openid vs unionid

  • openid:用户在这一个应用里的唯一标识——同一用户在你的 A 应用和 B 应用 openid 不同
  • unionid:用户在整个开发者账号下的唯一标识——同一开发者下所有应用共享

生产里通常以 unionid 作为主用户标识——避免"同一个微信用户在 PC 端和小程序里看起来是两个账号"。


三、方案 B:公众号 OAuth(关注后授权登录)

很多 H5 应用通过公众号入口登录——比如某些电商小程序的 H5 版本、某些活动落地页。

流程

用户在微信内打开你的网页 → 微信检测到是 H5 → 触发 OAuth → 不需要扫码(已是微信内)→ 用户授权 → 返回业务页面已登录。

snsapi_base vs snsapi_userinfo

公众号 OAuth 有两种 scope,差异很大:

snsapi_basesnsapi_userinfo
用户感知无(静默)有授权页面
拿到信息只有 openidopenid + 昵称 + 头像 + 城市等
适用场景后台关联用户显示头像/昵称

优先用 snsapi_base——拿到 openid 后查自己业务库即可,多数场景够用。只有真要展示用户头像昵称时才用 snsapi_userinfo——它会弹一个授权页打断流程。

接入要求

  • 服务号(订阅号没有 OAuth 接口)
  • 已认证(年费 300)
  • 在公众号后台配置"网页授权域名"——只能在配置的域名下使用 OAuth
  • 必须放一个微信验证文本到域名根目录

四、方案 C:关注公众号即登录(带场景值二维码)

这是最巧妙的一种——用户扫了带场景值的临时二维码、关注公众号后,公众号收到事件,后端推送登录态给前端

业务侧体验是:“用户扫码并关注 → PC 端自动登录”——比 OAuth 少一次跳转

整体架构

实现要点

1. 生成带 sceneId 的二维码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 生成临时 sceneId(需要去重 + 过期)
String sceneId = generateUniqueSceneId();
sceneStore.put(sceneId, new SceneState(SceneStatus.PENDING), 5 * 60);

// 调微信接口生成 ticket
Map<String, Object> req = Map.of(
    "expire_seconds", 300,
    "action_name", "QR_STR_SCENE",
    "action_info", Map.of("scene", Map.of("scene_str", sceneId))
);
String ticket = callWeChatApi("/cgi-bin/qrcode/create", req);

// 返回前端:用 ticket 拼二维码 URL
String qrUrl = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=" + ticket;

2. 接收微信推送

公众号后台配置消息推送 URL,收到事件 callback:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
@PostMapping("/wechat/callback")
public String onMessage(@RequestBody String xml) {
    WechatEvent event = parseXml(xml);
    if ("subscribe".equals(event.getEvent())   // 关注
        || "SCAN".equals(event.getEvent())) {  // 已关注扫描
        String sceneId = event.getEventKey();
        String openid = event.getFromUserName();

        // 把扫码用户绑定到 sceneId
        sceneStore.update(sceneId, new SceneState(SceneStatus.SCANNED, openid));

        // 给用户回个欢迎语
        return buildReplyXml(openid, "登录成功!");
    }
    return "success";
}

3. 前端轮询或 SSE

1
2
3
4
5
6
7
8
9
const interval = setInterval(async () => {
    const resp = await fetch(`/auth/scan/check?sceneId=${sceneId}`);
    const data = await resp.json();
    if (data.status === 'SCANNED') {
        localStorage.setItem('token', data.token);
        location.href = '/dashboard';
        clearInterval(interval);
    }
}, 1500);

更优雅的姿势用 SSE 替代轮询——服务端扫码后主动推送。

这套方案的限制

  • 要服务号,订阅号不行
  • subscribe 事件只在关注时触发——已关注用户扫码触发的是 SCAN,业务要分两种处理
  • 临时二维码 5 分钟过期——前端要管理超时
  • 场景值长度限制——临时二维码 scene_str 最多 64 字符

五、三种方案对比

开放平台 OAuth公众号 OAuth公众号扫码即登录
适用场景PC 网站H5(微信内打开)PC 网站(关注公众号)
资质开放平台账号 + 企业服务号 + 认证服务号 + 认证
用户体验扫码 → 授权确认 → 登录自动跳转(snsapi_base 无感)扫码 → 关注 → 登录
是否需关注
跳转步骤一次无(PC 端不跳)
适合做营销是(强行涨粉)

六、实战建议

选型

共性踩坑

1. 多账号体系

用户可能从 H5 / PC / 小程序多个端登录——用 unionid 统一身份,否则会有重复账号。

2. AppSecret 必须保密

永远不能暴露给前端——只在服务端用。前端拿到 code 后调自己后端,由后端去换 access_token。

3. 频率限制

微信 API 有调用频率限制(每天 N 次、每分钟 M 次),超额会被拒。生产要做:

  • access_token 缓存(2 小时有效,重复调用会被踢掉旧 token)
  • API 调用打日志,超频告警

4. 二维码缓存

每次生成新二维码都要调微信 API——前端每次刷新页面都生成新的会撞限制。建议给二维码加 1-2 分钟的客户端缓存。

5. 异常处理

  • 用户在授权页面"取消"——回调还是会被调用,参数有差异
  • code 重复使用——只能用一次
  • access_token 过期——要刷新

6. 测试环境验证

微信不能跨域名调试——要么把测试环境配成独立公众号,要么用内网穿透。生产前一定在真实公众号下测过完整流程。


七、一些前沿姿势

微信小程序登录

如果你的业务要跨小程序 + H5 + PC 共享用户体系:

  • 小程序:wx.login() + code2Session 拿 openid + unionid
  • H5:snsapi_base
  • PC:开放平台 OAuth

用 unionid 一统身份——前提是这些应用都挂在同一个开放平台账号下,否则 unionid 不互通。

跨端会话同步

PC 扫码登录后,移动端怎么也保持同步?通常是:

  • PC 拿到 token 后调一个"标记 unionid 已登录"接口
  • App / H5 启动时检查 unionid 是否处于"已登录"状态——是就静默登录

这是 12306 / 京东这类多端应用的常用做法。


小结

把全文压一句:

PC 网站用开放平台 OAuth;H5 微信内用公众号 OAuth;想顺带涨粉用『关注公众号即登录』。三者底层协议都是微信生态,但接入门槛、体验、营销价值差别很大。

工程上记住几条:

  1. AppSecret 永远在服务端
  2. 用 unionid 统一身份
  3. access_token 缓存 + 频率告警
  4. state 防 CSRF
  5. 二维码加客户端缓存避免刷限额

把这套吃透,国内 Web 应用的"登录"问题基本就解决了。

使用 Hugo 构建
主题 StackJimmy 设计