aiChat开发日志2:如何隐藏API Key

在玛丽娅还没有部署到服务器之前,调用AI模型的API Key是直接填写在网页的script.js里面的,传上网络后js文件会直接裸露出来,所有人都能获取到这个文件,自然Key也就能看到了,就很有可能被偷家,一天晚上token流的比马桶还一泻千里。虽然我这里弄这个没多大意义,但是假装正式,我还是捣鼓了一下,不过这个更多涉及后端,进一步讲,无论前端后端,鄙人都是班门弄斧罢了,下面的内容轻喷。

下面记录的 ai 聊天功能是基于通义千问的模型搭建,如果您使用的是其他品牌,信息的收发以及 API 调用部分可能会有差别,还请留意。

本文后台操作不涉及SSH编写,雨之庭服务器配备有宝塔面板,后台操作会大幅度降维轻松。

1 原理

既然不把API Key直接漏在网页,那就把它藏在服务器里,服务器端可以编写一个PHP,因为PHP是不会运行在访客的浏览器里,他们自然也就读取不到里面的内容。当网页需要与AI模型连接时,先向服务器发送请求,由服务器端携带 Key 去请求千问,拿到结果后再返回给前端,即:

服务器代管Key→网页请求→服务器去问千问→结果返回前端

这样子的话,雨之庭服务器对于玛丽娅的回复速度将会担责10%,毕竟服务器网速卡了一手,但是雨之庭搭配的是50M带宽,这个再负责任也不用大负(。

2 编写后端php

因为aiChat与雨之庭是完全互相独立的,aiChat可以独立使用一个域名,先添加一个站点(如果想和主站同一目录也不是不行,后面的操作总体上没有大变化)。

在宝塔面板中,点击“网站” -> “添加站点”。

域名填写我这里就是ai.yukimia.top了,宝塔面板同样可以部署在本地电脑,或者如果你只是本地测试,可以用端口映射。根目录保持默认或指定一个文件夹。

进入刚刚创建的站点根目录。新建一个文件,命名为 qwen-proxy.php(名字可以自定义,但后缀必须是 .php)。接下来编写这个后端,这段代码的作用是接收前端发来的消息,带上你的 Key 去请求千问,然后把结果发回给前端。

<?php
// 设置允许跨域(如果你的前端页面和这个PHP不在同一个域名下,必须开启)
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Origin: *"); // 生产环境建议改为具体的前端域名
header("Access-Control-Allow-Methods: POST");
header("Access-Control-Allow-Headers: Content-Type");

// 🔐 在这里填入你的千问 API Key (现在它是安全的,因为藏在服务器里)
$apiKey = '你的千问 API Key '; 

// 千问 API 地址
$endpoint = 'https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation';

// 获取前端发来的 POST 数据
$input = json_decode(file_get_contents('php://input'), true);
$userMessage = $input['message'] ?? '';
$chatHistory = $input['history'] ?? []; // 如果你有记忆功能,也需要传历史记录,配着script.js看这里

// 1. 验证输入
if (empty($userMessage)) {
    echo json_encode(['error' => 'Missing message']);
    exit;
}

// 2. 构造发送给千问的数据
// 注意:这里你需要根据你的前端逻辑,把 history 和 user message 组装好
$payload = [
    'model' => 'qwen-plus', 
    'input' => [
        'messages' => array_merge($chatHistory, [['role' => 'user', 'content' => $userMessage]])
    ],
    'parameters' => [
        'result_format' => 'message' 
    ]
];

// 3. 初始化 cURL 发送请求
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $endpoint);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer ' . $apiKey
]);

// 4. 执行请求
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

// 5. 处理返回结果
if ($response === false) {
    echo json_encode(['error' => 'Curl Error: ' . curl_error($ch)]);
} else {
    // 直接将千问的响应原样返回给前端
    echo $response;
}
?>

3 修改前端js

现在Key已经藏好了,我们需要去修改script.js,将现在裸露的Key变为请求上面的PHP,主要修改的就是原来的 sendMessage 函数。主要变化是:

删除前端代码中的 apiKey 变量(因为不再需要了)。

修改 fetch 的地址,指向刚才创建的 PHP 文件。

调整发送的数据格式(只发送必要的消息内容,而不是整个包含 Key 的请求)。

我们先来看一下未隐藏的js是什么样子的:

    // 🔴 请在这里填入你的千问 API Key
    const apiKey = '你的千问 API Key,这里会暴露 '; 

    // 千问 API 地址
    const endpoint = 'https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation';

    // 构造符合千问要求的 JSON 数据
        const payload = {
        model: "qwen-plus", 
        input: {
            messages: chatHistory // 🔥 记忆步骤:把整个历史记录发给 API
        },
        parameters: {
            result_format: "message" 
        }
    };

    fetch(endpoint, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${apiKey}`
        },
        body: JSON.stringify(payload)
    })
    .then(response => {
        if (!response.ok) {
            throw new Error(`网络错误: ${response.status}`);
        }
        return response.json();
    })
    .then(data => {
        loadingElement.style.display = 'none';

那么修改过后的版本是:

    // ⚠️ 注意:这里的 endpoint 改为了你自己的服务器地址
    // 我这里就是ai.yukimia.top
    const endpoint = 'https://你的域名/qwen-proxy.php';  

    // 🔥 构造发送给 你自己的服务器的数据
    // 只需要发送聊天记录,不需要发送 API Key
    const payload = {
        message: message, // 当前用户输入
        history: chatHistory.slice(0, -1) // 发送之前的历史记录(去掉最后一条刚加的,避免重复,根据实际情况调整)
    };

    fetch(endpoint, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
            // 注意:这里不再需要 Authorization 头,因为 Key 在服务器端
        },
        body: JSON.stringify(payload)
    })
    .then(response => {
        if (!response.ok) {
            throw new Error(`网络错误: ${response.status}`);
        }
        return response.json();
    })
    .then(data => {
        loadingElement.style.display = 'none';

通过这种方式,API Key 就成功隐藏在了服务器后端中,现在访问无可言喻的心声应该就看不到我的Key了。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇