企业微信登录
本方案用「自定义 OAuth 2.0 身份源 + 云托管中转服务」完成企业微信自建应用登录。中转服务对 CloudBase 扮演标准 OAuth 2.0 授权服务器,对企业微信调用网页授权接口,把两套协议对齐。
企业微信授权后直接把 code 回跳给云开发,中转服务不承接浏览器回调。所有对企业微信接口的请求都从云托管固定公网出口 IP 发出,把这个 IP 填进企业微信「企业可信 IP」,无需自建 NAT 网关或 EIP。
仅支持 Web 端,且页面需在企业微信客户端内置浏览器中打开。小程序端不支持。
协议差异
| 环节 | 标准 OAuth 2.0 | 企业微信网页授权 |
|---|---|---|
| 授权请求 | client_id redirect_uri response_type=code scope | appid agentid redirect_uri scope=snsapi_base |
| 换取凭证 | POST 到 Token 端点,客户端凭证随请求体或 Basic Header | 先取企业 access_token,再用 code 换 UserId |
| 用户信息 | 标准 claims:sub name email phone_number | auth/getuserinfo 与 auth/getuserdetail,字段为 userid mobile email |
| 来源 IP | 无限制 | 需配置企业可信 IP,否则返回 60020 |
差异全部由中转服务消化,CloudBase 侧只配置一个标准 OAuth 2.0 身份源。
方案结构
| 组件 | 职责 |
|---|---|
| 业务页面 | 未登录时跳转托管登录页或发起 OAuth 授权,回调后读取 Session |
| CloudBase 身份认证 | 发起 OAuth 授权,接收企业微信回跳的 code,换 Token、拉取用户信息,创建用户并签发 Session |
| 云托管中转服务 | 翻译授权参数;用 CloudBase 传入的 CorpID 与 Secret 调企业微信接口换 UserId;签发自己的 access_token |
| 企业微信 | 展示页面授权,颁发 code,返回 UserId 与敏感字段 |
前置条件
| 项 | 要求 |
|---|---|
| 云开发环境 | 个人版及以上套餐,固定公网出口 IP 依赖套餐能力 |
| 云托管服务 | 已部署,可绑定公网域名,并启用固定公网出口 IP |
| 企业微信 | 已创建自建应用,持有 CorpID、AgentId、Secret |
| 域名 | 云开发回调地址的域名已加入企业微信可信域名并完成归属校验 |
第一步:配置企业微信自建应用
- 企业微信管理后台 → 应用管理 → 自建,创建应用,记录 AgentId 与 Secret
- 我的企业 → 企业信息,记录 CorpID
- 应用详情 → 网页授权及JS-SDK → 设置可信域名,填写云开发回调地址的域名,并按提示把
WW_verify_xxx.txt放到该域名根目录完成校验 - 应用详情 → 开发者接口 → 企业可信IP,填入云托管服务的固定公网出口 IP
⚠️ 未配置企业可信 IP 时,调用
qyapi.weixin.qq.com返回60020 not allow to access from your ip。
可信域名约束的是授权链接里的 redirect_uri。本方案 redirect_uri 指向云开发回调地址,因此校验的是云开发回调域名,不是中转服务域名。
第二步:部署云托管中转服务
对外接口
| 路径 | 方法 | 作用 | 调用方 |
|---|---|---|---|
/authorize | GET | 把标准 OAuth 授权请求翻译成企业微信授权链接,302 给浏览器 | CloudBase |
/token | POST | 用企业微信回跳的 code 换 UserId,签发 access_token | CloudBase |
/userinfo | GET | 用 Bearer access_token 换标准用户信息 | CloudBase |
不需要回调路径。企业微信把 code 直接回跳给云开发,中转服务只在服务端被调用。
环境变量
# 自建应用的 AgentId,应用详情页获取
WECOM_AGENT_ID=1000002
# 允许的云开发回调地址,多个用逗号分隔,精确匹配
CLOUDBASE_REDIRECT_URIS=https://{envId}.{region}.tcb-api.tencentcloudapi.com/auth/v1/callback
CorpID 与 Secret 不在中转服务里配置:CloudBase 会把身份源里填的 Client ID 与 Client Secret 传过来,中转服务直接用它们当 corpid 与 corpsecret。
授权端点
接收 client_id、redirect_uri、response_type、state、scope,做两件事后 302:
- 校验
redirect_uri与CLOUDBASE_REDIRECT_URIS中的某一项完全一致,不一致直接返回 400 - 拼企业微信授权链接:
appid直接用client_id(即企业 CorpID),redirect_uri与state原样透传
https://open.weixin.qq.com/connect/oauth2/authorize
?appid={client_id}
&redirect_uri={urlencode(CloudBase 传入的 redirect_uri)}
&response_type=code
&scope=snsapi_base
&state={CloudBase 传入的 state}
&agentid={WECOM_AGENT_ID}
#wechat_redirect
redirect_uri 与 state 都原样透传:企业微信回跳时直接落到云开发,state 由云开发自己校验。透传的前提是上面第 1 条的白名单校验,否则等于开放重定向。授权阶段拿不到 client_secret,client_id 是否有效留给 /token 由企业微信判定。
Token 端点
/token 收到的是企业微信回跳的 code,不是中转服务自己签发的 code。按 grant_type=authorization_code 处理:
- 从请求体或 Basic Header 取
client_id与client_secret,两种都要支持 GET https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={client_id}&corpsecret={client_secret}取企业access_token,缓存 7200 秒并提前刷新。凭证是否正确由企业微信判定,失败返回40001,中转服务转成invalid_clientGET https://qyapi.weixin.qq.com/cgi-bin/auth/getuserinfo?access_token={TOKEN}&code={CODE}取UserId- 需要手机号或邮箱时,授权链接的
scope改为snsapi_privateinfo,再用返回的user_ticket调POST https://qyapi.weixin.qq.com/cgi-bin/auth/getuserdetail - 签发中转服务自己的 access_token,返回 CloudBase
{
"access_token": "eyJhbGciOiJIUzI1NiJ9...",
"token_type": "Bearer",
"expires_in": 3600
}
code 一次性且 5 分钟有效,重复提交会失败。企业 access_token 在多实例间共享,建议存入 Redis 或数据库,避免并发刷新导致旧 Token 失效。
自己的 access_token 建议用 JWT 承载 userid、name、email 等字段,/userinfo 验签即可,不必回查会话存储。
用户信息端点
GET /userinfo 从 Authorization: Bearer {access_token} 解出用户信息,转成标准 claims:
{
"sub": "zhangsan",
"name": "张三",
"picture": "https://example.com/avatar.png",
"email": "zhangsan@example.com",
"phone_number": "13800000000"
}
sub 用企业微信 UserId。同一企业内 UserId 唯一且稳定,改名、换手机号都不变,是跨次登录的唯一身份锚点。
部署与固定出口 IP
下面以云托管为例。换成其他具备固定公网出口 IP 的服务同样可行。
- 云托管 → 服务 → 部署,任意语言,监听
PORT - 服务详情页记录公网默认域名,作为身份源三个端点的域名
- 服务设置 → 网络设置 → 启用「固定公网出口 IP」
- 把该 IP 填入企业微信「企业可信 IP」
⚠️ 服务同时启用私有网络时,公网请求会默认路由进 VPC,此时需要配 NAT 网关,并用 NAT 网关的出口 IP 作为可信 IP。
第三步:配置云开发身份源
- 云开发控制台 → 身份认证 → 企业身份源 → 添加自定义 OAuth 2.0 身份源
- 按下表填写。三个端点都指向中转服务所在的云托管服务:到该服务详情页复制公网默认域名(或已绑定的自定义域名),后面分别拼上
/authorize、/token、/userinfo,不要手写域名
| 配置项 | 值 |
|---|---|
| 身份源名称 | 企业微信 |
| 授权端点 | 云托管服务域名 + /authorize |
| Token 端点 | 云托管服务域名 + /token |
| 用户信息端点 | 云托管服务域名 + /userinfo |
| Client ID | 企业 CorpID,企业微信管理后台「我的企业 → 企业信息 → 企业 ID」 |
| Client Secret | 自建应用的 Secret,应用详情页获取 |
| Scope | openid,中转服务按需解析 |
| 回调地址 | 控制台生成的 https://{envId}.{region}.tcb-api.tencentcloudapi.com/auth/v1/callback,同时填进中转服务的 CLOUDBASE_REDIRECT_URIS |
| 认证方式 | CLIENT_SECRET_POST |
| 自动注册 | 开启时首次登录即创建用户;关闭时需先注册再绑定身份源 |
- 身份认证 → 托管登录页,勾选「企业微信」并保存
登录时序
联调检查
| 现象 | 排查点 |
|---|---|
企业微信提示 redirect_uri 参数错误 | 授权链接的 redirect_uri 域名不在可信域名内,或 redirect_uri 未做 urlencode |
企业微信提示 50001 redirect_url 未登记可信域名 | 可信域名填的是中转服务域名,应填云开发回调地址的域名 |
/token 或 /userinfo 日志出现 60020 | 企业可信 IP 未填、填错,或云托管未启用固定公网出口 IP |
CloudBase 报 invalid_client | Client ID 填成了 CorpID 以外的值,或 Secret 不是自建应用 Secret;也可能是认证方式应为 CLIENT_SECRET_BASIC 却配成 CLIENT_SECRET_POST |
| 登录成功但无手机号、邮箱 | 授权 scope 仍是 snsapi_base,未改 snsapi_privateinfo 或未调 getuserdetail |
| 首次登录提示用户不存在 | 身份源关闭了自动注册,需先注册再用 bindWithProvider 绑定 |
| 授权端点返回 404 或超时 | 端点域名不是云托管服务的公网默认域名,或服务的公网访问被关闭 |
| 打开页面没有任何跳转 | 页面不在企业微信客户端内置浏览器中,企业微信网页授权不对外部浏览器生效 |
常见问题
Q:中转服务为什么不需要回调地址?
企业微信只认一个 redirect_uri。把它指向云开发回调地址,code 就直达云开发,再由云开发转给中转服务的 /token。中转服务少一个公网回调入口,也少一份 state 存储和会话映射。
Q:可信域名为什么填云开发的域名?
企业微信校验的是 redirect_uri 的域名。本方案回跳目标是云开发回调地址,所以可信域名填云开发回调域名,与中转服务所在的云托管默认域名无关。
Q:中转服务必须用云托管吗?
不强制。选云托管的原因是它提供固定公网出口 IP,这是接入企业微信的硬要求,与企业微信是否内网互通无关。开发者用其他具备固定公网出口 IP 的服务同样可行。
Q:一个中转服务可以服务多个企业吗?
不可以。企业微信把一个可信 IP 绑定到一个企业,一个出口 IP 只能服务一个企业。要接入多个企业,就为每个企业单独部署一套中转服务,各自绑定独立的固定公网出口 IP。