微信小程序 / H5 调用第三方 API 的正确姿势:把密钥留在服务端
一句话回答
小程序和 H5 都不该直接持有第三方 API 密钥。可行做法是让前端只调用自有后端,由后端保存凭证、转发请求并统一处理错误与限流。小程序需要在后台配置 request 合法域名,H5 受浏览器同源策略与第三方 CORS 响应头约束;具体白名单规则、配额与第三方是否允许浏览器端调用,需以平台官方文档和接口方说明为准。
直接回答:让小程序和 H5 只调用你自己的后端,由后端持有第三方密钥、转发请求并统一处理错误。前端直连第三方 API 看起来省事,但会把密钥、配额和错误处理都暴露在客户端,后期很难收回。若确实无法自建后端,至少要用平台提供的服务端中转能力,并接受更强的限制。
为什么不要把第三方密钥放进前端#
- 密钥可被提取。小程序代码包和 H5 的 JS 都会下发到客户端,抓包、反编译、查看网络请求都能拿到凭证。
- 无法做限流与审计。第三方按密钥计量配额,前端直连意味着任何人都能消耗你的额度,且你无法区分调用来源。
- 域名与跨域约束。小程序通常需要在后台配置 request 合法域名后才能发起网络请求;H5 受浏览器同源策略约束,能否直连取决于第三方是否返回合适的 CORS 响应头。这两点都不是前端代码能绕过的。
关于合法域名的具体规则(是否要求 HTTPS、能否使用 IP、端口与数量限制、是否要求备案),以及第三方接口是否允许浏览器端调用,本次没有可核验材料,待核实,请以平台后台提示与接口方文档为准。
三种接入结构对比#
| 结构 | 密钥位置 | 主要优点 | 主要风险 |
|---|---|---|---|
| 前端直连第三方 | 客户端 | 链路短,改动小 | 密钥泄露、配额被滥用,且常因域名或 CORS 直接失败 |
| 自有后端代理(BFF) | 服务端环境变量 | 可鉴权、限流、缓存、统一错误壳 | 需要维护服务与额外的网络跳数 |
| 云函数 / 边缘函数中转 | 函数运行时配置 | 无独立服务器,部署快 | 受运行时限制,冷启动与出网策略待核实 |
能控制服务端时,优先选第二种。第三种适合流量小、逻辑简单的场景,但函数平台的出网、超时与配额需自行确认。
落地步骤#
- 向接口方申请独立的服务端凭证,与测试凭证分开,并确认配额、计费方式与调用频率上限。
- 在后端封装唯一出口:负责注入密钥、校验入参、设置超时,并把第三方响应转换成统一结构。
- 前端只请求自有域名。小程序侧在后台配置 request 合法域名,H5 侧确保自有接口返回正确 CORS 头并处理预检请求。
- 设计统一错误壳,例如
{ code, message, requestId },不要把第三方原始报错原样透传,避免泄露内部信息。 - 对可重试的失败(超时、5xx)做有限次退避重试;对写操作先约定幂等键,避免重试造成重复提交。
- 日志中记录 requestId 与耗时,但不记录密钥与完整用户数据。
示例(未执行,仅示意)#
以下两段代码均未运行,域名与密钥为占位值,实际使用前需补上鉴权、超时与错误壳。
// 小程序侧:只调自有后端
wx.request({
url: 'https://api.example.com/v1/translate', // 占位域名
method: 'POST',
data: { text: 'hello' },
success(res) { /* 处理 res.data */ },
fail(err) { /* 网络层失败,走降级 */ }
});
// 服务端代理:密钥只存在于环境变量
app.post('/v1/translate', async (req, res) => {
// 占位:生产环境需先鉴权、校验参数、设置超时
const r = await fetch('https://third-party.example.com/translate', {
method: 'POST',
headers: { Authorization: 'Bearer ' + process.env.THIRD_PARTY_KEY },
body: JSON.stringify({ text: req.body.text })
});
res.status(r.status).json(await r.json());
});
上线前需要确认的边界#
- 第三方接口的超时上限、并发限制与计费口径,以及超额后的返回形态。
- 小程序后台对域名的具体要求,以及是否允许携带自定义请求头。
- 第三方是否支持浏览器端调用(CORS),以及其条款是否允许凭证出现在客户端。
- 降级路径:接口不可用时,前端显示什么、是否缓存上次结果。
这些条目会随平台规则与接口方政策变化,本次未能核验,建议在接入前逐项对照官方文档确认。