如何实现JavaScript代码安全:API Key不公开且可正常调用?
前端订阅功能的API密钥泄露问题与修复方案
问题背景
下面这段JavaScript代码原本用来实现Mailchimp邮件订阅功能,但存在严重的安全漏洞:
const form = document.querySelector('.subsform'); const submitButton = form.querySelector('input[type="button"]'); const messageDiv = document.querySelector('#newsletter-message'); submitButton.addEventListener('click', (event) => { event.preventDefault(); const firstName = form.querySelector('input[placeholder="First Name"]').value; const lastName = form.querySelector('input[placeholder="Last Name"]').value; const email = form.querySelector('input[type="email"]').value; const apiKey = form.querySelector('input[name="mailchimpAPIkey"]').getAttribute('data-api'); const listId = form.querySelector('input[name="mailChimpListId"]').getAttribute('data-listId'); console.log({ firstName, lastName, email, apiKey, listId }); const url = `https://us20.api.mailchimp.com/3.0/lists/${listId}/members/`; const data = { email_address: email, status: 'subscribed', merge_fields: { FNAME: firstName, LNAME: lastName } }; const options = { method: 'POST', headers: { 'Authorization': `Basic ${btoa(`apikey:${apiKey}`)}`, 'Content-Type': 'application/json' }, body: JSON.stringify(data) }; fetch(url, options) .then(response => response.json()) .then(data => { messageDiv.innerHTML = `Thank you for subscribing, ${firstName}!`; console.log(data); }) .catch(error => { messageDiv.innerHTML = 'There was an error subscribing. Please try again later.'; console.error(error); }); });
只要懂点基础JS的用户,就能通过控制台执行以下代码,轻松获取页面里的API密钥和列表ID等敏感信息:
// 选中页面所有隐藏input const hiddenInputs = document.querySelectorAll('input[type="hidden"]'); // 遍历并打印到控制台 hiddenInputs.forEach(input => { console.log(input); });
漏洞根源
前端页面的任何数据(包括隐藏元素的属性值)都是完全公开的,用户可以通过开发者工具直接查看。把API密钥这类敏感信息放在前端,等于把你的Mailchimp账户权限拱手让人,别人拿到密钥后可以随意操作你的邮件列表,甚至造成不必要的损失。
修复方案:后端中转
核心思路是绝对不让前端接触敏感凭证,所有需要调用第三方API的操作,都通过自己的后端接口中转完成。
1. 改造前端代码
移除所有和敏感信息相关的逻辑,前端只负责收集用户的姓名、邮箱,然后把数据发给自己的后端接口:
const form = document.querySelector('.subsform'); const submitButton = form.querySelector('input[type="button"]'); const messageDiv = document.querySelector('#newsletter-message'); submitButton.addEventListener('click', (event) => { event.preventDefault(); const firstName = form.querySelector('input[placeholder="First Name"]').value; const lastName = form.querySelector('input[placeholder="Last Name"]').value; const email = form.querySelector('input[type="email"]').value; // 先做基础输入验证(可选但推荐) if (!email || !email.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)) { messageDiv.textContent = '请输入有效的邮箱地址'; return; } // 调用自己的后端订阅接口 fetch('/api/subscribe', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ firstName, lastName, email }) }) .then(response => response.json()) .then(result => { if (result.success) { messageDiv.textContent = `Thank you for subscribing, ${firstName}!`; form.reset(); // 订阅成功后重置表单 } else { messageDiv.textContent = result.message || '订阅失败,请稍后重试'; } }) .catch(error => { messageDiv.textContent = '网络错误,请稍后重试'; console.error(error); }); });
2. 实现后端中转接口
这里以Node.js + Express为例,其他后端语言(PHP、Python、Java等)思路完全一致:
首先安装依赖:
npm install express axios dotenv
然后编写后端代码:
const express = require('express'); const axios = require('axios'); require('dotenv').config(); // 加载环境变量 const app = express(); app.use(express.json()); // 解析JSON请求体 // 从环境变量读取敏感信息(绝对不要硬写在代码里!) const MAILCHIMP_API_KEY = process.env.MAILCHIMP_API_KEY; const MAILCHIMP_LIST_ID = process.env.MAILCHIMP_LIST_ID; const MAILCHIMP_SERVER = process.env.MAILCHIMP_SERVER; // 比如"us20",对应API密钥末尾的后缀 // 订阅接口 app.post('/api/subscribe', async (req, res) => { try { const { firstName, lastName, email } = req.body; // 调用Mailchimp官方API const mcResponse = await axios.post( `https://${MAILCHIMP_SERVER}.api.mailchimp.com/3.0/lists/${MAILCHIMP_LIST_ID}/members/`, { email_address: email, status: 'subscribed', merge_fields: { FNAME: firstName, LNAME: lastName } }, { auth: { username: 'apikey', // Mailchimp要求用户名固定为apikey password: MAILCHIMP_API_KEY } } ); res.json({ success: true, data: mcResponse.data }); } catch (error) { console.error('Mailchimp请求失败:', error.response?.data || error.message); // 把友好的错误信息返回给前端 const errorMsg = error.response?.data?.title || '订阅失败,请稍后重试'; res.status(500).json({ success: false, message: errorMsg }); } }); // 启动服务 const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(`服务运行在端口 ${PORT}`); });
3. 配置环境变量
创建一个.env文件,用来存储敏感信息:
MAILCHIMP_API_KEY=你的MailchimpAPI密钥 MAILCHIMP_LIST_ID=你的邮件列表ID MAILCHIMP_SERVER=us20 # 替换成你密钥对应的服务器前缀
记得把.env加入到.gitignore里,绝对不要提交到代码仓库!
额外注意事项
- 后端要做输入校验和限流,防止恶意批量提交垃圾邮箱。
- 处理Mailchimp的各种错误场景,比如用户已订阅、邮箱格式无效等,给用户更精准的提示。
- 如果是静态网站,可以用Serverless函数(比如Vercel Functions、Netlify Functions)来实现后端逻辑,不用单独搭建服务器。
内容的提问来源于stack exchange,提问作者WordCent
相关产品推荐
相关产品推荐

