如何安全存储和使用API Secret的实战经验分享

统维酱~ 安全 阅读 1,134
赞 20 收藏
二维码
手机扫码查看
反馈

先看代码,再扯别的

我最近在搞一个前端项目,需要调用第三方地图 API,结果一上来就卡住了——API Secret 怎么处理?直接写进代码里?那不是谁扒开浏览器 DevTools 都能偷走?可不写进去,又没法发请求。折腾了半天,最后用了个折中但靠谱的方案,今天就来聊聊我踩过的坑和亲测有效的做法。

如何安全存储和使用API Secret的实战经验分享

先上最简单的场景:你有个前端页面要调用某个 API,比如获取天气数据:

fetch('https://jztheme.com/api/weather?city=beijing&api_secret=abc123xyz')
  .then(res => res.json())
  .then(data => console.log(data));

别笑,我知道这写法很 naive。但我敢说,至少一半刚入行的人(包括曾经的我)都这么干过。问题是,api_secret 完全暴露在前端代码里,别人只要打开 Network 面板,就能复制你的 key 去乱发请求,轻则被限流,重则账单爆炸。

真正的解法:后端代理 + 环境变量

正确的姿势是:前端不碰 secret,所有带 secret 的请求都走自己的后端做个“中间人”。

比如我在 Node.js 写了个简单接口:

// server.js
require('dotenv').config();
const express = require('express');
const axios = require('axios');
const app = express();

app.get('/api/weather', async (req, res) => {
  const { city } = req.query;
  try {
    const response = await axios.get('https://jztheme.com/api/weather', {
      params: {
        city,
        api_secret: process.env.MAP_API_SECRET // 从环境变量读
      }
    });
    res.json(response.data);
  } catch (error) {
    res.status(500).json({ error: '请求失败' });
  }
});

app.listen(3000);

然后前端只管调自己后端:

fetch('/api/weather?city=shanghai')
  .then(res => res.json())
  .then(data => console.log(data));

这样前端完全看不到 secret,它只和自己的后端通信。而真正的 secret 存在 .env 文件里:

MAP_API_SECRET=abc123xyz

别忘了把 .env 加到 .gitignore,不然 commit 上去等于公开密码。

部署时的坑:Vercel、Netlify 怎么配环境变量?

本地跑没问题,但部署就容易翻车。比如我第一次用 Vercel 部署时,死活拿不到 process.env.MAP_API_SECRET,查了好久才发现——Vercel 的环境变量得在界面上手动填。

步骤如下:

  • 进项目设置 → Environment Variables
  • 加个 Key:MAP_API_SECRET,Value 填你的密钥
  • 重新部署

Netlify 也一样,在 Settings → Environment variables 里配。

这里注意下,我踩过好几次坑:有时候你以为变量生效了,其实是缓存或者旧构建还在跑。建议每次改完变量,手动 trigger 一次新 deploy,别等自动同步。

那能不能前端直接用?非要用怎么办?

有人会问:我就一个小 demo,懒得搭后端,能不能让前端用,但至少防君子不防小人?

答案是:有,但效果有限。

比如你可以把 secret 存在 sessionStorage 里,通过一个登录接口动态注入:

// 用户登录后,后端返回临时 token(不含 api_secret)
fetch('/api/login', { method: 'POST', body: formData })
  .then(res => res.json())
  .then(({ tempToken }) => {
    sessionStorage.setItem('auth', tempToken);
    // 然后用这个 token 换取实际的 api 调用资格
  });

但这只是转移问题,没解决问题。真想防,还是得走代理。

还有一种常见错误做法:把 secret 拆成两半,拼在 JS 里:

const part1 = 'abc123';
const part2 = 'xyz';
const secret = part1 + part2; // abc123xyz

别闹了,这种 obfuscate 对现代浏览器来说就是明文。

CORS 和代理配置的小细节

做反向代理时,经常遇到 CORS 报错。比如前端域名是 https://myapp.com,后端是 https://api.myapp.com,得确保后端允许跨域。

Express 可以这样配:

const cors = require('cors');
app.use(cors({
  origin: ['https://myapp.com'],
  credentials: true
}));

但如果前端和后端同源(比如都部署在 Vercel,前端是根路径,API 走 /api/xxx),那就根本不会有跨域问题,推荐这种结构。

高级点的玩法:JWT + 短期凭证

如果你的系统用户量大,还可以玩更安全的:用 JWT 签发短期访问凭证。

流程大概是:

  1. 用户登录,后端验证身份
  2. 后端生成一个短期 token(比如 5 分钟有效期),里面不包含 secret,只说明“这个用户允许调用地图 API”
  3. 前端拿着这个 token 去请求代理接口
  4. 代理接口收到后,用自己的 secret 发真实请求,把结果返回

好处是:即使前端 token 被截获,也很快过期,而且不能直接用来调第三方 API。

实现上可以用 jsonwebtoken 库:

const jwt = require('jsonwebtoken');

// 生成 token
const token = jwt.sign(
  { userId: 123, permissions: ['map'] },
  process.env.JWT_SECRET,
  { expiresIn: '5m' }
);

然后在中间层验证:

function verifyToken(req, res, next) {
  const auth = req.headers.authorization;
  if (!auth) return res.status(401).json({ error: '未授权' });

  const token = auth.split(' ')[1];
  jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
    if (err) return res.status(403).json({ error: 'token无效或过期' });
    req.user = user;
    next();
  });
}

这套机制稍微重一点,但适合中大型应用。

踩坑提醒:这三点一定注意

1. 永远不要在 GitHub 搜 “api_secret” 或 “apikey” —— 我试过,搜一下全是别人的泄漏 key,有些还能用。你自己别成为别人搜索结果里的那个倒霉蛋。

2. 本地调试时,.env 文件权限设为 600,避免其他用户读取。Linux/Mac 下可以用:

chmod 600 .env

3. 监控 API 调用量。我之前没设监控,结果某天发现请求量暴涨 10 倍,查日志发现是某个测试 key 被泄露了。现在我都给每个 key 加前缀,比如 dev_prod_,方便追踪。

总结一下我现在的标准做法

我现在新项目统一这样搞:

  • 所有第三方 API 请求走后端代理
  • secret 存在环境变量,CI/CD 里配好
  • 本地用 dotenv,生产用平台提供的 env 管理
  • 敏感接口加 rate limiting,比如每个 IP 每分钟最多 10 次
  • 定期轮换 secret,尤其是团队有人离职时

改完之后仍然有一两个小问题,比如本地开发要多跑一个 server,但比起安全风险,这点麻烦不算啥。

这个技巧的拓展用法还有很多,比如结合 OAuth、API Gateway 做更精细的权限控制,后续会继续分享这类博客。以上是我踩坑后的总结,希望对你有帮助。有更优的实现方式欢迎评论区交流。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论