公众号集成接入指南 · 网页授权篇
在 CloudBase 控制台创建「微信公众号」集成后,系统会自动生成一个 HTTP 云函数(函数名形如 <集成标识>-<随机串>,本文统称 offiaccount-common)。本指南只介绍网页授权(OAuth 2.0)——也是公众号集成最常用、最高频的能力,用于在公众号场景下让网页拿到用户的 openid 与(可选的)昵称/头像等信息。
其他能力(access_token 托管、JS-SDK、模板/订阅消息、客服消息、菜单、二维码、用户管理、素材等)由同一个
offiaccount-common云函数提供,本指南不展开。
整体调用链路:
- 网页跳转 → 微信授权页 → 微信回跳带回
code - 网页携带
code→ CloudBase 云 API 网关(accessToken 鉴权)→offiaccount-commonHTTP 云函数 → 微信sns/oauth2/access_tokenAPI offiaccount-common返回{ openid, access_token, ... }给网页或业务后端
架构总览
┌──────────────────┐
│ 公众号网页 │
│ (微信浏览器) │
└────────┬─────────┘
│ 1. 跳转 open.weixin.qq.com/connect/oauth2/authorize
│ ?appid=...&redirect_uri=...&scope=snsapi_base&state=...
▼
┌───────── ─────────┐
│ 微信授权页 │ 用户授权 / snsapi_base 静默
└────────┬─────────┘
│ 2. 回跳到 redirect_uri 携带 ?code=xxx&state=xxx
▼
┌──────────────────┐
│ 公众号网页 │
└────────┬─────────┘
│ 3. POST { code } 到 /oauth/token,带 Bearer accessToken
▼
┌──────────────────────────┐
│ CloudBase 云 API 网关 │ accessToken 鉴权
└────────┬─────────────────┘
│ HTTP invoke
▼
┌────────────────────────────┐
│ offiaccount-common │
│ POST /oauth/token │
│ → 调微信 sns/oauth2/... │
│ ← 返回 openid + tokens │
└────────────────────────────┘
职责分工:
| 角色 | 职责 |
|---|---|
| 公众号网页 | 引导用户跳转微信授权页;回跳后把 code 发给后端 |
| 云 API 网关 | 校验 Bearer {accessToken},将请求转发到 HTTP 云函数 |
offiaccount-common 云函数 | 持有 AppID + AppSecret,用 code 调微信换 openid 与 OAuth tokens;按需调用 sns/userinfo 拉用户资料 |
| 微信公众平台 | 颁发 code、openid,下发用户资料 |
| 集成中心 | 统一托管 AppID/AppSecret,作为环境变量注入云函数 |
前置条件
| 项 | 要求 |
|---|---|
| 公众号 | 服务号或已开通 snsapi_base 权限的订阅号;已通过微信认证 |
| 公众号 AppID + AppSecret | 持有可用值,AppSecret 可在公众平台 → 基本配置中重置 |
| 网页授权域名 | 公众平台 → 设置与开发 → 公众号设置 → 功能设置 → 网页授权域名,已添加发起授权的页面域名并完成 MP_verify_xxx.txt 校验 |
| CloudBase 环境 | 已开通;至少有一种认证登录方式可用(匿名登录不能调云函数,参见 4.3 节) |
第一步 · 准备公众号凭证
登录 微信公众平台 → 设置与开发 → 基本配置:
- AppID:形如
wxc1546068399a59fc - AppSecret:点击「重置」生成,仅显示一次,妥善保存
AppSecret 不要泄露给前端,CloudBase 集成中心会把它托管并注入到云函数环境变量。
第二步 · 在 CloudBase 创建集成
2.1 进入集成中心
路径:云开发平台 → 模板与集成 → 集成中心 → 微信公众号 → 创建集成。
2.2 填写集成信息
控制台向导需填写:
- 集成名称(自定义,例如
offiaccount-demo) - 公众号 AppID 与 AppSecret
集成中心将统一托管上述凭证。
2.3 自动生成云函数资源
创建成功后,系统自动完成两项操作:
- 按集成名称创建一个 HTTP 云函数(函数名形如
<集成标识>-<随机串>,本文统称offiaccount-common)。 - 将 AppID、AppSecret 作为环境变量注入到该云函数。
第三步 · 了解 offiaccount-common 云函数
第二步创建集成时,平台已经把 AppID、AppSecret 作为环境变量注入到云函数。运行时通过 process.env 读取,业务代码中不会出现明文凭证。
如需修改配置,进入「集成中心 → 对应集成 → 编辑」,保 存后平台将自动重新部署云函数。
3.1 网页授权相关路由
⚠️ 与「微信支付」集成的 pay-common 不同,
offiaccount-common不使用_action字段分发,而是直接使用 REST 路径。调用云函数时请使用完整路径,例如POST /oauth/token。
云函数对外暴露的网页授权 5 个 REST 路由:
| 路由 | 用途 | 必填请求体 |
|---|---|---|
POST /oauth/config | 返回当前集成配置的公众号 appId,便于前端拼授权 URL(避免在前端硬编码) | — |
POST /oauth/token | 用 code 换取 openid + OAuth access_token + refresh_token | { code } |
POST /oauth/refresh | 刷新 OAuth access_token(30 天有效的 refresh_token 换新的 2 小时 token) | { refresh_token } |
POST /oauth/userinfo | 用 OAuth access_token 拉取授权用户昵称/头像(仅 snsapi_userinfo scope 有效) | { access_token, openid } |
POST /oauth/verify | 校验 OAuth access_token 是否仍有效 | { access_token, openid } |
所有路由统一返回 { code: 0, msg: 'success', data: <业务数据> },错误返回 { code: -1, msg, data: null }。
这里的 OAuth
access_token与公众号的全局access_token(用于调菜单/消息接口)是两个不同的东西,前者只能用于拉取本用户的资料、不能调其他接口。
3.2 链路与鉴权
调用 offiaccount-common 的链路与 pay-common 完全一致:
前端登录 CloudBase 获取 accessToken → 携带 Authorization: Bearer {accessToken} 请求云 API 网关 → 网关分发至 offiaccount-common。
accessToken 必须来自「认证登录」——匿名登录拿到的 token 无权调用云函数,网关会直接返回 401。具体登录方式(邮箱、手机号、自定义登录等)请参考 CloudBase Web SDK · 认证登录。
第四步 · 接入网页授权
4.1 前置准备
配置网页授权域名
路径:微信公众平台 → 设置与开发 → 公众号设置 → 功能设置 → 网页授权域名。
下载 MP_verify_xxx.txt,放到 https://<你的域名>/MP_verify_xxx.txt 可访问的位置,再点击校验通过。
该域名必须是发起授权页的域名,不是回跳处理页的域名(虽然两者通常相同)。
理解两种 scope
| scope | 用户体验 | 能拿到什么 |
|---|---|---|
snsapi_base | 静默授权,无弹窗 | 仅 openid(多数业务用这个) |
snsapi_userinfo | 弹出授权确认页 | openid + 昵称 + 头像 + 性别等 |
4.2 完整网页授权流程
四步流程,前两步在前端完成,第三步走 offiaccount-common,第四步按需调用。
第 1 步:网页 → 跳转微信授权页
第 2 步:微信 → 回跳前端 redirect_uri,附带 ?code=xxx
第 3 步:前端 → POST /oauth/token { code } → 拿到 openid + access_token
第 4 步:前端 → POST /oauth/userinfo(仅 snsapi_userinfo 需要)
4.3 完整调用示例
适合微信内置浏览器中打开的公众号 H5 页面。
关键约束:
- 必须在微信内置浏览器中打开页面,外部浏览器(Chrome / Safari)打开微信授权页会跳到提示页
- 页面 URL 域名必须已配在公众号「网页授权域名」白名单
state参数必填,用于防 CSRF(前端生成随机串,回跳后校验)accessToken必须来自认证登录,详见 3.2
<!-- oauth.html 发起授权页 -->
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>正在授权…</title></head>
<body>
<script src="https://cdn.jsdelivr.net/npm/@cloudbase/js-sdk/dist/cloudbase.full.js"></script>
<script>
const ENV_ID = 'your-env-id'
const FN_NAME = 'offiaccount-common' // 集成创建后生成的实际函数名
const app = cloudbase.init({ env: ENV_ID })
const auth = app.auth({ persistence: 'local' })
// ⚠️ accessToken 必须来自「认证登录」,匿名登录无权调云函数
// 完整登录方式见 https://docs.cloudbase.net/api-reference/webv2/authentication#%E8%AE%A4%E8%AF%81%E7%99%BB%E5%BD%95
async function getAccessToken() {
// 示例占位:业务方需实现真实登录后返回 accessToken
throw new Error('请按 CloudBase Web SDK 文档实现认证登录后返回 accessToken')
}
async function callFn(token, path, body) {
const res = await fetch(
`https://${ENV_ID}.api.tcloudbasegateway.com/v1/functions/${FN_NAME}${path}?webfn=true`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify(body || {}),
}
)
return res.json()
}
function unwrap(body) {
// offiaccount-common 直接返回 { code, msg, data },没有 wechatpay-node-v3 那层嵌套
return body && body.code === 0 ? body.data : null
}
;(async () => {
const token = await getAccessToken()
const url = new URL(location.href)
const code = url.searchParams.get('code')
const state = url.searchParams.get('state')
// —— 第 1 步:没有 code,发起授权 ——
if (!code) {
// 拿 AppID(也可直接在前端硬编码)
const { appId } = unwrap(await callFn(token, '/oauth/config')) || {}
if (!appId) { alert('未取到 appId'); return }
const randomState = Math.random().toString(36).slice(2, 18)
sessionStorage.setItem('oauth_state', randomState)
// 当前页作为 redirect_uri,回跳后再次进入本脚本
const redirectUri = encodeURIComponent(location.href.split('?')[0])
location.replace(
`https://open.weixin.qq.com/connect/oauth2/authorize` +
`?appid=${appId}` +
`&redirect_uri=${redirectUri}` +
`&response_type=code` +
`&scope=snsapi_base` + // 改成 snsapi_userinfo 可拉昵称/头像
`&state=${randomState}` +
`#wechat_redirect`
)
return
}
// —— 第 2 步:回跳带回 code,校验 state ——
const expectedState = sessionStorage.getItem('oauth_state')
if (state !== expectedState) {
alert('state 校验失败,疑似 CSRF')
return
}
sessionStorage.removeItem('oauth_state')
// —— 第 3 步:code 换 openid ——
const tokenRes = unwrap(await callFn(token, '/oauth/token', { code }))
if (!tokenRes || !tokenRes.openid) {
alert('换取 openid 失败')
return
}
const { openid, access_token, refresh_token, scope } = tokenRes
console.log('已拿到 openid:', openid, 'scope:', scope)
// —— 第 4 步(可选):snsapi_userinfo 才能拉资料 ——
if (scope === 'snsapi_userinfo') {
const info = unwrap(await callFn(token, '/oauth/userinfo', {
access_token,
openid,
}))
console.log('用户资料:', info) // { nickname, headimgurl, sex, ... }
}
// 业务侧:把 openid 写入会话 / 跳转到业务页
sessionStorage.setItem('openid', openid)
location.replace('/home')
})()
</script>
</body>
</html>
4.4 调用 /oauth/refresh 与 /oauth/verify
OAuth access_token 默认 2 小时过期。若要在长会话场景下复用 openid 关联的资料拉取能力,可:
// 续期:用 refresh_token 换新的 access_token(refresh_token 30 天有效)
const refreshed = unwrap(await callFn(token, '/oauth/refresh', {
refresh_token,
}))
// refreshed = { openid, access_token, refresh_token, expires_in, scope }
// 校验:判断 token 是否仍有效
const { valid } = unwrap(await callFn(token, '/oauth/verify', {
access_token,
openid,
}))
大多数业务不需要持久化保存 OAuth access_token——拿到 openid 即可入库关联用户,其他公众号能力(消息/客服等)走全局 access_token(由 offiaccount-common 的 /token/* 路由托管)。
常见问题 FAQ
Q1:调用 /oauth/token 返回「微信接口错误 [40029]: invalid code」
code 有三种情况会拿到这个错误:
- 已被用过一次(
code只能消 费一次,前端误重复调用,或刷新页面带 code 又跳一遍) - 已过期(5 分钟内有效)
- 与发起授权时的 AppID 不一致(例如集成里填的是 A,授权 URL 用的是 B)
排查:清缓存重试,确认前端不会重入。
Q2:调用 /oauth/token 返回「invalid appsecret」或 40125
集成中心填写的 AppSecret 错误,或已被在公众平台重置。前往集成中心 → 编辑 → 重新粘贴 AppSecret。
Q3:授权页提示「该链接无法访问,请检查链接是否正确」/「redirect_uri 域名与后台配置不一致」
redirect_uri 的域名不在公众平台「网页授权域名」白名单。检查:
- 域名是否完整匹配(含子域;公众平台只允许填主域,子域会自动覆盖)
- 校验文件
MP_verify_xxx.txt是否真的可以访问 - 配置后建议等 1–2 分钟生效
Q4:snsapi_base 拿不到昵称头像
这是设计如此。snsapi_base 只返回 openid,要拉资料必须用 snsapi_userinfo,会弹用户确认框。
Q5:网页在企业微信、QQ 浏览器中能跑吗
不能。open.weixin.qq.com/connect/oauth2/authorize 只在微信内置浏览器中生效;外部浏览器会跳到引导页。
Q6:能否直接在前端调 https://api.weixin.qq.com/sns/oauth2/access_token
不能也不该。该接口强制需要 AppSecret,前端调用会暴露 AppSecret,且微信对 IP 来源有校验。这正是 offiaccount-common 存在的价值——把 AppSecret 留在服务端。
Q7:能否复用 pay-common 的同一个 CloudBase 环境
可以。offiaccount-common 和 pay-common 是两个独立的集成,分别生成各自的 HTTP 云函数,互不影响。前端按需求分别调用即可。