目前有一个小程序,功能上比较简单,但是需要调用其他平台提供的 AI 相关接口。为了节省服务器费用,想直接在前端请求接口,但是这样就会暴露接口的 key。有没有其他办法可以防止 key 泄露? 或者默许 key 可能的泄露,然后实时监控接口的使用量,这种方式是否可行?
目前有一个小程序,功能上比较简单,但是需要调用其他平台提供的 AI 相关接口。为了节省服务器费用,想直接在前端请求接口,但是这样就会暴露接口的 key。有没有其他办法可以防止 key 泄露? 或者默许 key 可能的泄露,然后实时监控接口的使用量,这种方式是否可行?
绝对不要在前端硬编码或明文存储敏感 Key。以下方案按推荐优先级排序:
实现方式:
// 前端请求你的代理接口(无Key)
fetch('https://your-proxy.com/ai-api', {
method: 'POST',
body: JSON.stringify({ prompt: "用户输入" })
})
// 云函数(示例:AWS Lambda)
exports.handler = async (event) => {
const response = await fetch('https://ai-platform.com/api', {
headers: {
'Authorization': `Bearer ${process.env.SECRET_KEY}` // 从环境变量读取
},
body: event.body
});
return response.json();
};
HTTP Referer
或IP白名单sign
签名参数或请求头校验增强安全性监控指标:
必须使用方案一,其他方案作为辅助。Serverless方案:
云函数(Serverless)方案
适用场景: 调用量不大的小程序,希望零运维
实现方式:
成本优势:
代码示例:
// 云函数代码
exports.main = async (event) => {
const response = await fetch('AI_API_URL', {
headers: {
'Authorization': `Bearer ${process.env.AI_API_KEY}`
},
body: JSON.stringify(event.data)
});
return response.json();
}
云函数特性:
安全特性:
使用方法:
成本控制:
// ============= 云函数代码 =============
// 文件名: index.js
const crypto = require('crypto');
// 配置
const config = {
AI_API_URL: 'https://api.openai.com/v1/chat/completions', // 替换为实际AI接口地址
APP_SECRET: process.env.APP_SECRET || 'your-app-secret', // 应用密钥
RATE_LIMIT: {
perMinute: 10, // 每分钟最多调用次数
perDay: 100 // 每天最多调用次数
}
};
// 内存缓存(生产环境建议使用Redis)
const cache = new Map();
// 获取客户端IP
function getClientIP(event) {
return event.headers['x-forwarded-for'] ||
event.headers['x-real-ip'] ||
event.requestContext?.identity?.sourceIp ||
'unknown';
}
// 验证token
function verifyToken(token, timestamp) {
try {
const [appId, ts, signature] = Buffer.from(token, 'base64').toString().split(':');
// 检查时间戳(5分钟内有效)
const now = Date.now();
if (Math.abs(now - parseInt(ts)) > 5 * 60 * 1000) {
return { valid: false, error: 'Token expired' };
}
// 验证签名
const expectedSig = crypto.createHmac('sha256', config.APP_SECRET)
.update(`${appId}:${ts}`)
.digest('hex');
if (signature !== expectedSig) {
return { valid: false, error: 'Invalid signature' };
}
return { valid: true, appId };
} catch (error) {
return { valid: false, error: 'Invalid token format' };
}
}
// 检查频率限制
function checkRateLimit(clientId) {
const now = Date.now();
const minuteKey = `${clientId}:${Math.floor(now / 60000)}`;
const dayKey = `${clientId}:${Math.floor(now / 86400000)}`;
// 检查分钟限制
const minuteCount = cache.get(minuteKey) || 0;
if (minuteCount >= config.RATE_LIMIT.perMinute) {
return { allowed: false, error: 'Rate limit exceeded (per minute)' };
}
// 检查日限制
const dayCount = cache.get(dayKey) || 0;
if (dayCount >= config.RATE_LIMIT.perDay) {
return { allowed: false, error: 'Rate limit exceeded (per day)' };
}
// 更新计数
cache.set(minuteKey, minuteCount + 1);
cache.set(dayKey, dayCount + 1);
// 设置过期时间(简单实现,生产环境建议用Redis TTL)
if (minuteCount === 0) {
setTimeout(() => cache.delete(minuteKey), 60000);
}
if (dayCount === 0) {
setTimeout(() => cache.delete(dayKey), 86400000);
}
return { allowed: true };
}
// 调用AI接口
async function callAIAPI(data) {
try {
const response = await fetch(config.AI_API_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.AI_API_KEY}`
},
body: JSON.stringify(data)
});
if (!response.ok) {
throw new Error(`AI API error: ${response.status} ${response.statusText}`);
}
return await response.json();
} catch (error) {
console.error('AI API call failed:', error);
throw error;
}
}
// 主函数
exports.main = async (event, context) => {
try {
// 解析请求
const { token, data } = typeof event.body === 'string'
? JSON.parse(event.body)
: event.body || event;
// 验证必要参数
if (!token || !data) {
return {
statusCode: 400,
body: JSON.stringify({
success: false,
error: 'Missing token or data'
})
};
}
// 验证token
const tokenResult = verifyToken(token);
if (!tokenResult.valid) {
return {
statusCode: 401,
body: JSON.stringify({
success: false,
error: tokenResult.error
})
};
}
// 获取客户端标识
const clientIP = getClientIP(event);
const clientId = `${tokenResult.appId}:${clientIP}`;
// 检查频率限制
const rateLimitResult = checkRateLimit(clientId);
if (!rateLimitResult.allowed) {
return {
statusCode: 429,
body: JSON.stringify({
success: false,
error: rateLimitResult.error
})
};
}
// 调用AI接口
const aiResponse = await callAIAPI(data);
// 返回结果
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*', // 根据需要调整CORS
'Access-Control-Allow-Methods': 'POST',
'Access-Control-Allow-Headers': 'Content-Type'
},
body: JSON.stringify({
success: true,
data: aiResponse
})
};
} catch (error) {
console.error('Function execution error:', error);
return {
statusCode: 500,
body: JSON.stringify({
success: false,
error: 'Internal server error'
})
};
}
};
// ============= 前端调用代码 =============
// 文件名: api-client.js
class AIApiClient {
constructor(appId, appSecret, cloudFunctionUrl) {
this.appId = appId;
this.appSecret = appSecret;
this.cloudFunctionUrl = cloudFunctionUrl;
}
// 生成认证token
generateToken() {
const timestamp = Date.now().toString();
// 创建签名
const crypto = require('crypto'); // Node.js环境
// 浏览器环境需要使用crypto-js库: const crypto = require('crypto-js');
const signature = crypto.createHmac('sha256', this.appSecret)
.update(`${this.appId}:${timestamp}`)
.digest('hex');
// 浏览器环境:
// const signature = crypto.HmacSHA256(`${this.appId}:${timestamp}`, this.appSecret).toString();
const tokenData = `${this.appId}:${timestamp}:${signature}`;
return btoa(tokenData); // 浏览器环境可直接使用
// Node.js环境: return Buffer.from(tokenData).toString('base64');
}
// 调用AI接口
async callAI(requestData) {
try {
const token = this.generateToken();
const response = await fetch(this.cloudFunctionUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
token: token,
data: requestData
})
});
const result = await response.json();
if (!result.success) {
throw new Error(result.error || 'API call failed');
}
return result.data;
} catch (error) {
console.error('AI API call failed:', error);
throw error;
}
}
}
// 使用示例
const client = new AIApiClient(
'your-app-id',
'your-app-secret',
'https://your-cloud-function-url'
);
// 调用示例
async function example() {
try {
const response = await client.callAI({
model: "gpt-3.5-turbo",
messages: [
{ role: "user", content: "Hello, how are you?" }
],
max_tokens: 100
});
console.log('AI Response:', response);
} catch (error) {
console.error('Error:', error.message);
}
}
// ============= 环境变量配置 =============
/*
在云函数平台设置以下环境变量:
AI_API_KEY=your-actual-ai-api-key
APP_SECRET=your-app-secret-key
部署说明:
1. 将云函数代码上传到云平台
2. 设置环境变量
3. 配置触发器(HTTP触发)
4. 前端使用AIApiClient调用
*/
AI的接口如果本身没有提供混淆地址的方法,KEY肯定会被看到吧,建议前后端合作提高KEY的刷新频率。比如后端每隔1分钟就刷新一次KEY,前端必须重新获取KEY,然后再发起AI请求,这样看到也没多大用了。当然前后端传递KEY必须用保密点的方法,不能被轻易侦测到。
如果你放在前端,基本没法隐藏key,不管是混淆还是加密,在最终也会在你请求AI接口的时候会暴露,只要想办法抓包就行了。至于你的方案二,实时监控接口用量,理论上就不现实,你这估计也是人工监控,他半夜跑你有什么办法。个人建议你还是放服务端吧
即使你的加密做的再好也需要去请求
最便宜的最低核的都可以,后端程序非常小,仅利用接口,访问接口并转发即可,成本很低的;况且拥有一个服务器在很多方面都会方便很多,例如控制权限等
13 回答12.7k 阅读
2 回答5k 阅读✓ 已解决
3 回答2.2k 阅读✓ 已解决
5 回答747 阅读
3 回答2.1k 阅读
2 回答1.5k 阅读✓ 已解决
2 回答2.1k 阅读
服务器或者api转发,前端混淆根本解决不了问题。