You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何限制仅指定JavaScript函数向PHP后端提交数据?

解决后端API非法提交问题的方案

你现在的问题是后端仅靠id参数校验,完全可以被伪造,下面给你几个实用的改进方案:


方案1:CSRF令牌验证(Web端最常用防护方式)

CSRF令牌是服务器生成的随机字符串,绑定用户会话,只有从你的页面发起的请求才能拿到正确令牌,伪造请求无法获取。

修改前端JS代码

先在页面<head>里添加后端渲染的CSRF令牌meta标签:

<meta name="csrf-token" content="<?php echo $_SESSION['csrf_token']; ?>">

然后修改saveData函数,在请求头带上CSRF令牌:

async function saveData() {
    try {
        let id = getCookie('hhb-id') | 0;
        // 获取页面中的CSRF令牌
        const csrfToken = document.querySelector('meta[name="csrf-token"]').content;
        const response = await fetch(`api.php?id=${id}`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-CSRF-Token': csrfToken // 新增CSRF请求头
            },
            body: JSON.stringify(dictionary)
        });

        if (!response.ok) {
            let responseData = await response.json();
            console.error('保存数据出错(请求异常): ', responseData);
        }
        else {
            console.log('数据保存成功: ', await response.json());
        }
    } catch (error) {
        console.error('保存数据出错(捕获异常): ', error);
    }
}

修改后端PHP代码

开启会话并生成、验证CSRF令牌:

// 放在PHP文件最顶部,开启会话
session_start();

// 生成CSRF令牌(会话中不存在则创建)
if (!isset($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // 1. 验证CSRF令牌合法性
    $csrfToken = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';
    if (!hash_equals($_SESSION['csrf_token'], $csrfToken)) {
        http_response_code(403);
        echo json_encode(['error' => '非法请求']);
        exit;
    }

    // 2. 验证id与会话绑定(可选,进一步加固)
    if (isset($_GET['id']) && $_GET['id'] > 0) {
        // 假设用户登录后,会话中已存储user_id
        if ($_GET['id'] != $_SESSION['user_id']) {
            http_response_code(403);
            echo json_encode(['error' => '身份不匹配']);
            exit;
        }

        try {
            $data = json_decode(file_get_contents('php://input'));

            // 保存数据到数据库
            ...
        } catch (Exception $e) {
            http_response_code(500);
            echo json_encode(['error' => $e->getMessage()]);
        }
    } else {
        http_response_code(400);
        echo json_encode(['error' => '无效的ID']);
    }
}

方案2:会话绑定身份验证

如果用户是登录状态,直接从服务器会话取用户ID,彻底避免依赖前端传的id参数:

修改前端JS代码

可以去掉URL中的id参数:

async function saveData() {
    try {
        const csrfToken = document.querySelector('meta[name="csrf-token"]').content;
        const response = await fetch('api.php', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-CSRF-Token': csrfToken
            },
            body: JSON.stringify(dictionary)
        });

        // 错误处理逻辑不变
        if (!response.ok) {
            let responseData = await response.json();
            console.error('保存数据出错(请求异常): ', responseData);
        }
        else {
            console.log('数据保存成功: ', await response.json());
        }
    } catch (error) {
        console.error('保存数据出错(捕获异常): ', error);
    }
}

修改后端PHP代码

session_start();

// 先验证用户是否登录
if (!isset($_SESSION['user_id']) || $_SESSION['user_id'] <= 0) {
    http_response_code(401);
    echo json_encode(['error' => '未登录']);
    exit;
}

// 验证CSRF令牌
$csrfToken = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';
if (!hash_equals($_SESSION['csrf_token'], $csrfToken)) {
    http_response_code(403);
    echo json_encode(['error' => '非法请求']);
    exit;
}

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    try {
        $data = json_decode(file_get_contents('php://input'));
        $userId = $_SESSION['user_id']; // 直接从会话取用户ID

        // 保存数据到数据库,使用$userId关联
        ...
    } catch (Exception $e) {
        http_response_code(500);
        echo json_encode(['error' => $e->getMessage()]);
    }
}

方案3:请求签名(适合严格场景)

如果要防止请求被篡改或重放,可以生成请求签名,后端验证签名合法性:

修改前端JS代码

// 注意:密钥不能硬编码在前端!需通过后端接口返回与用户绑定的临时密钥,此处仅为示例
const secretKey = 'user-specific-secret-from-server';

async function saveData() {
    try {
        let id = getCookie('hhb-id') | 0;
        const timestamp = Date.now().toString();
        const dataStr = JSON.stringify(dictionary);
        
        // 生成SHA-256签名
        const signatureBuffer = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(timestamp + dataStr + secretKey));
        const signatureHex = Array.from(new Uint8Array(signatureBuffer))
            .map(b => b.toString(16).padStart(2, '0'))
            .join('');

        const response = await fetch(`api.php?id=${id}`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-Request-Timestamp': timestamp,
                'X-Request-Signature': signatureHex
            },
            body: dataStr
        });

        // 错误处理逻辑不变
        if (!response.ok) {
            let responseData = await response.json();
            console.error('保存数据出错(请求异常): ', responseData);
        }
        else {
            console.log('数据保存成功: ', await response.json());
        }
    } catch (error) {
        console.error('保存数据出错(捕获异常): ', error);
    }
}

修改后端PHP代码

session_start();

// 从会话或数据库获取对应用户的密钥
$secretKey = 'user-specific-secret-from-server';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // 验证时间戳,防止重放(限制5分钟内的请求)
    $timestamp = $_SERVER['HTTP_X_REQUEST_TIMESTAMP'] ?? '';
    if (empty($timestamp) || (time() - intval($timestamp)/1000) > 300) {
        http_response_code(403);
        echo json_encode(['error' => '请求已过期']);
        exit;
    }

    // 验证签名
    $signature = $_SERVER['HTTP_X_REQUEST_SIGNATURE'] ?? '';
    $dataStr = file_get_contents('php://input');
    $expectedSignature = hash('sha256', $timestamp . $dataStr . $secretKey);
    
    if (!hash_equals($expectedSignature, $signature)) {
        http_response_code(403);
        echo json_encode(['error' => '请求被篡改']);
        exit;
    }

    // 后续验证与保存逻辑
    if (isset($_GET['id']) && $_GET['id'] > 0) {
        try {
            $data = json_decode($dataStr);
            // 保存数据到数据库
            ...
        } catch (Exception $e) {
            http_response_code(500);
            echo json_encode(['error' => $e->getMessage()]);
        }
    } else {
        http_response_code(400);
        echo json_encode(['error' => '无效的ID']);
    }
}

注意事项

  1. 方案1是Web端基础防护,优先使用,配合会话绑定身份验证效果更佳
  2. 方案3适合非浏览器客户端调用的场景,绝对不能在前端硬编码固定密钥,必须用与用户绑定的动态密钥
  3. 验证不通过时,返回对应HTTP状态码(401未登录、403非法请求等),不要静默处理

内容的提问来源于stack exchange,提问作者Kugelfisch

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 21:15:25