微信小程序与 H5 调用第三方 API:把密钥收回服务端,再处理两端差异
一句话回答
对大多数项目,正确做法不是让小程序和 H5 各自直连第三方 API,而是都只请求自建服务端,由服务端保管密钥、代发请求并统一处理超时、重试与错误码。这样可以避开前端泄露密钥、H5 跨域和小程序域名配置三类问题,代价是多一跳和一个需要维护的代理层。第三方是否允许服务端转发、微信合法域名的具体规则,本文未核验,需按官方文档和第三方条款逐项确认。
核心要点 TL;DR
- 前端代码可被查看,任何写进小程序包或 H5 bundle 的第三方密钥都应视为公开。
- H5 卡在同源策略与 CORS,小程序卡在合法域名列表;把请求收进自建服务端可同时绕过这两个约束。
- 代理层要自己定义超时、重试范围、错误码映射与日志脱敏,不能把第三方响应原样透传。
- 第三方服务条款是否允许代理转发,以及微信域名配置的具体要求,需单独核实。
结论先行:小程序和 H5 都不应该直连需要密钥的第三方 API。更稳妥的做法是两端都只请求你自己的服务端,由服务端持有密钥、代发请求,并统一处理超时、重试和错误码。这样,两端的规则差异就被压缩成一处配置:H5 处理同源或 CORS,小程序处理合法域名。代价是多一次网络跳转,以及一个需要维护的代理层。无密钥、无鉴权的公开只读接口是例外,可以直连,但前提是两端规则都满足,且第三方条款允许。
两端到底差在哪#
- H5:请求由浏览器发出,受同源策略约束。跨域时依赖第三方返回 CORS 响应头,带自定义头的请求还可能先触发 OPTIONS 预检。第三方是否支持、支持到什么粒度,需要逐项核对,本文未核验具体接口。
- 小程序:请求由客户端发起,不经过浏览器同源策略,因此不涉及 CORS 预检;它受的是另一套约束——请求域名需要满足平台的合法域名要求。具体规则、可配置数量与修改限制以微信官方文档为准,本文未核验。
- 共同点:客户端代码可被查看或反编译。任何写进小程序包、H5 bundle 或请求 URL 的密钥,都应视为已公开。
推荐结构:客户端只认自己的网关#
这是建议而非唯一方案。一个够用的代理层通常承担这些职责:用自己的会话或令牌鉴权调用方;从环境变量读取第三方密钥,绝不回传;对转发参数做白名单,避免把任意路径透传到上游;设置连接与读取超时;只对幂等请求做有限重试;按上游配额做限流与缓存;把第三方错误码映射成自己稳定的错误结构;日志里对密钥与用户标识脱敏。
最小示例(未执行)#
下面的代码只示意形态,未实际运行。占位域名 api.example.com 与密钥来源 YOUR_KEY 均为演示值,替换前需按真实接口文档核对参数与响应格式。
// 未执行的示例。假定环境:Node.js 18+(全局 fetch)、Express 风格路由
import express from "express";
const app = express();
const UPSTREAM = "https://api.example.com/v1/items"; // 占位域名
app.get("/api/items", async (req, res) => {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 5000);
try {
const url = new URL(UPSTREAM);
// 只转发白名单参数,不透传整个 query
if (typeof req.query.q === "string") url.searchParams.set("q", req.query.q);
const upstream = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.THIRD_PARTY_KEY}` }, // YOUR_KEY 放环境变量
signal: controller.signal,
});
const body = await upstream.text();
// 真实实现还需要校验响应大小与 Content-Type,再决定是否透传
res.status(upstream.status).type("application/json").send(body);
} catch (err) {
res.status(502).json({ error: "upstream_unavailable" });
} finally {
clearTimeout(timer);
}
});
改到 H5 时要动的两处#
- 代理与页面同源时无需额外配置;不同源时,要在代理上显式设置 CORS 响应头,并且不要用
*搭配凭证。 - 小程序侧把请求域名换成代理域名,并按平台要求完成域名配置;配置项与生效范围以官方文档为准。
排查表:按现象定位,而不是先改代码#
| 现象 | 先核对什么 |
|---|---|
| H5 控制台报跨域错误 | 是否触发了预检;第三方或代理是否返回了对应的 CORS 头 |
| 小程序提示域名不合法 | 请求域名是否在合法域名列表中,协议与证书是否满足要求 |
| 401 / 403 | 密钥来源是否正确、是否过期;签名类接口还要看时间戳与时区 |
| 偶发超时 | 代理的超时设置、上游响应时间分布,以及是否对非幂等请求做了重试 |
边界与待核实#
- 第三方服务条款是否允许由你的服务端代理转发,需按具体条款确认,本文未核验。
- 微信小程序合法域名的具体要求与审核流程,请以官方文档为准;本文未核验,也不据此给出数量或次数。
- 若要在小程序内直接加载第三方图片或资源,域名约束可能与请求接口不同,需单独核对。
先决定密钥放在哪里,再决定请求从哪里发出。这两步定下来,剩下的差异大多只是配置项。
常见问题(FAQ)
第三方 API 支持 CORS,H5 是不是就可以直连了?
技术上可行,但仍要评估密钥是否会出现在前端。如果接口需要密钥或长期凭证,直连等于把凭证交给每个访问者;这类接口应收进服务端。无密钥的公开只读接口才值得考虑直连。
小程序需要处理 CORS 吗?
小程序侧的请求由客户端发起,不经过浏览器的同源策略,因此不涉及 CORS 预检。它受另一套约束:请求域名需要满足平台的合法域名要求。具体规则以微信官方文档为准,本文未核验。