公众号网页授权 OAuth2.0:从拼链接到拿 openid 的完整流程

admin 微信QQ技巧 2026-11-02 8 0

你大概遇到过这种场景:用户在页面上点了「立即领取」,页面转了一圈白屏,日志里躺着一条 40029;更隐蔽的情况是第一次授权正常,用户退回上一页再点一次就变成 40163。这两个错误都不在业务逻辑里,而在网页授权被压缩成了一句「跳到微信、拿回 openid」。它是一条有状态、有时效、有一次性约束的链路,任何一步顺序错了,表现出来都是「拿不到 openid」。

先把踩过的坑摆出来

最常见的写法是把授权入口直接做成一个链接,用户点进去,微信回跳到你配置的 redirect_uri,页面加载时读 query 里的 code 再丢给后端。这套流程在「只点一次」时能跑通,于是很多人认为它是对的。但只要用户下拉刷新、前进后退、或者在回跳后做了一次前端路由跳转,同一个 URL 被重新执行,code 被第二次提交,就返回 40163。

第二种同样高频:测试和线上共用一个公众号,网页授权域名只配了线上那一个,本地联调把内网域名填进去,微信连 code 都不给,直接甩回 10003,而你的服务端日志里空空如也,因为请求根本没到你这里。

两种 scope 决定了用户看到什么

微信在授权入口只问你一件事:要静默还是要用户点头。这个选择由 scope 决定。

snsapi_base:静默授权

用户看不到任何确认页,页面直接完成跳转,你只能拿到 openid。适合「只需要知道这个人是谁」的场景,比如活动页判断是否参与过、H5 的未登录态识别。它不打扰用户,能用 base 的地方不要用 userinfo。

snsapi_userinfo:弹窗授权

首次访问会弹出授权确认页,用户点同意后才回跳,你除了 openid 还能拉昵称、头像、性别、地区。注意「首次」两个字:同意过之后不会重复弹窗,所以你没法靠「有没有弹窗」判断新老用户。

  • 只区分身份:snsapi_base。
  • 要展示昵称头像:snsapi_userinfo。
  • 用户拒绝过:别反复拉起弹窗,先给说明再给重试入口。
  • 两种 scope 可按页面粒度混用。

授权链接的完整拼装

入口地址固定,参数一个都不能少,注意最后的片段标记是协议的一部分。

https://open.weixin.qq.com/connect/oauth2/authorize
  ?appid=your_appid
  &redirect_uri=URLENCODE后的回调地址
  &response_type=code
  &scope=snsapi_base
  &state=自己生成的防伪串
  #wechat_redirect

response_type 只能是 code。state 由你生成、你校验,微信只负责原样带回。结尾的 #wechat_redirect 必须带上,否则部分客户端会跳出微信内置浏览器,或者出现页面不刷新。

拼装时把 redirect_uri 整体做一次 urlencode,包括协议头、路径和 query。只编码 query 部分是常见错误,微信收到的 redirect_uri 会在第一个 & 处被截断,你的页面只能拿到半个参数。

redirect_uri 的两个坑:域名配置与编码

第一个坑是域名。回调地址的域名必须和后台「网页授权域名」里配置的完全一致,微信校验的是域名,不是完整路径。不少人以为把整条 URL 填进配置项就行,实际它只比对域名部分。不匹配时返回 10003,而错误发生在授权阶段,你的服务端连请求都收不到。

第二个坑是编码。判断方法很直接:把最终链接粘到浏览器打开,看回跳后落到你页面上的 query 与预期是否逐字一致,尤其是回调地址自身带多个参数的情况。另外回调域名不要带下划线、不要用 IP,这些限制在调试期常被绕过,上线后集中爆发。

code 只能用一次,而且只有五分钟

code 是一次性凭证,成功消费一次后立即失效。下面几种写法都会撞上 40163:

  • 前端拿到 code 后发了一次请求,失败重试又用同一个 code。
  • 页面里两处初始化逻辑各提交了一次 code。
  • 后端把 code 当缓存键,多个并发请求同时消费。

正确做法是 code 只在后端消费一次,换到 openid 后立刻落到自己的会话体系,前端后续只带我方凭证。如果第一次消费超时、判断不清是否成功,就重新走一次授权拿新 code,别拿旧 code 重试。也不能把 code 塞进队列慢慢处理:异步任务排队超过五分钟,百分之百失败。

用 code 换 access_token 与 openid

拿到 code 后第一步在后端完成,参数放在 query 上。

https://api.weixin.qq.com/sns/oauth2/access_token
  ?appid=your_appid
  &secret=your_appsecret
  &code=回跳带回的code
  &grant_type=authorization_code

返回体里有 access_token、expires_in、refresh_token、openid、scope,绑定开放平台且用户授权过还会带 unionid。两个注意点:appid 与 secret 必须来自同一个公众号,混用返回 40125;scope 为 snsapi_base 时返回体里没有昵称头像字段,别指望后面从别处补上。

网页授权 access_token 和全局 access_token 是两套东西

这是最容易混的一点。网上大量 token 缓存代码缓的是全局 access_token,而网页授权拿到的是另一种凭证。两者名字相同,来源、用途、生命周期都不同,混用只会得到 40001 或 48001。

对比项网页授权 access_token全局 access_token
获取方式用 code 换,跟在用户授权之后用 appid 与 secret 直接换取
代表谁某一个具体用户整个公众号
能调什么拉用户资料、刷新授权模板消息、素材、菜单等
缓存策略按用户维度存,配 refresh_token 续期全局一份,提前刷新

全局 access_token 走下面这个地址,grant_type 用 client_credential。它不依赖用户授权,但读不了用户资料;反过来网页授权 token 也发不了模板消息。

https://api.weixin.qq.com/cgi-bin/token
  ?grant_type=client_credential
  &appid=your_appid
  &secret=your_appsecret

拉取用户信息、state 的用法与错误码

只有 snsapi_userinfo 才能拉到资料,接口是固定的:

https://api.weixin.qq.com/sns/userinfo
  ?access_token=网页授权access_token
  &openid=用户的openid
  &lang=zh_CN

返回昵称、头像、性别、城市、省份、国家。提醒两点:头像地址是微信侧的地址,别当长期资源直接落库;昵称里可能出现表情符号,字段长度和字符集要留余量。

state 不是可选项,它唯一的作用是防 CSRF:发起授权前生成随机串写进用户会话,回跳时比对。不校验的后果是,攻击者能构造一条指向你回调地址的链接,诱导已登录用户完成一次「绑定」,把对方的 openid 绑到受害者账号上。state 用一次即作废,别塞业务数据——它是明文回传的。

错误码对照

错误码原因处理
40029code 无效、被截断或已过期重新走授权拿新 code,不要重试旧 code
40163code 已被使用后端保证单次消费,前端加防重复提交
10003redirect_uri 域名与配置不符核对后台授权域名,注意只比对域名
40125appsecret 无效确认 appid 与 secret 属于同一公众号
40001凭证无效或已被刷新顶掉重新获取后重试,收敛统一刷新入口
48001接口未授权,多为用错 token 类型区分网页授权与全局 token 的调用场景

最后一条建议:把「发起授权」和「消费 code」各收成一个函数,不要散在页面各处。授权链路一旦被复制粘贴,40163 就会以你意想不到的路径出现,排查成本远高于当初的抽象成本。