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

PHP中获取ID用于模态框更新数据库的安全实现方案问询

解决前端ID篡改问题的几种安全方案

嘿,我完全理解你的顾虑——前端的任何数据都能通过开发者工具篡改,所以真正的安全防线必须建立在后端验证上。下面结合你的现有代码,给你几个实用的解决方案:


方案1:后端权限验证(最核心)

这是最根本的安全措施:就算用户篡改了person_id,只要他没有修改该记录的权限,操作就会被拒绝。假设你的系统有登录功能,每个persons记录关联到创建它的用户(比如有user_id字段),那在更新逻辑里必须验证当前用户是否有权限修改这条记录。

修改你的namelist.php更新逻辑:

<?php
session_start();
// 先检查用户是否登录
if (!isset($_SESSION['user_id'])) {
    header("Location: login.php");
    exit;
}

if (isset($_POST["update"])) {
    $current_user_id = $_SESSION['user_id'];
    $person_id = $_POST["person_id"];
    $first_name = $_POST["first_name"];
    $last_name = $_POST["last_name"];

    $object = new Dbc();
    // 第一步:验证该记录是否属于当前用户
    $check_stmt = $object->connect()->prepare("SELECT user_id FROM persons WHERE person_id = ?");
    $check_stmt->execute([$person_id]);
    $person = $check_stmt->fetch();

    if (!$person || $person['user_id'] !== $current_user_id) {
        echo "你无权修改这条记录!";
        exit;
    }

    // 第二步:执行更新操作
    $update_stmt = $object->connect()->prepare("UPDATE persons SET first_name = ?, last_name = ? WHERE person_id = ?");
    $update_stmt->execute([$first_name, $last_name, $person_id]);

    header("Location: namelist.php");
    exit;
}
?>

方案2:会话绑定的一次性令牌

给每个编辑请求生成唯一的令牌,存储在Session中,前端提交时同时传递令牌和ID,后端验证令牌是否匹配且有效。这样就算用户篡改了person_id,对应的令牌也不匹配,请求会被拒绝。

步骤1:修改class.names.php生成令牌

public function getAllNames() { 
    session_start();
    $obj = new Db(); 
    $stmt = $obj->connect()->query("SELECT * FROM persons"); 

    // 初始化存储编辑令牌的Session数组
    if (!isset($_SESSION['edit_tokens'])) {
        $_SESSION['edit_tokens'] = [];
    }

    while ($person = $stmt->fetch()) { 
        // 生成随机安全令牌
        $token = bin2hex(random_bytes(16));
        $_SESSION['edit_tokens'][$person['person_id']] = $token;

        echo "<tr>"; 
        echo "<td>".$person['first_name']."</td>"; 
        echo "<td>".$person['last_name']."</td>"; 
        // 用data属性传递ID和令牌,避免重复ID问题
        echo "<td><a data-person-id=\"".$person['person_id']."\" data-token=\"".$token."\" type=\"button\" data-toggle=\"modal\" data-target=\"#edit-name-modal\" class=\"btn btn-danger edit_data\" href=\"#\">Update</a></td>"; 
        echo "</tr>"; 
    } 
}

步骤2:修改names.js传递令牌并填充模态框

$(document).on('click', '.edit_data', function(){ 
    var person_id = $(this).data("person-id"); 
    var token = $(this).data("token");

    $.ajax({ 
        url:"/data/updatename.php", 
        method:"POST", 
        data:{person_id: person_id, token: token}, 
        dataType:"json", 
        success:function(data){ 
            $('#first_name').val(data.first_name); 
            $('#last_name').val(data.last_name); 
            $('#person_id').val(data.person_id); 
            $('#edit_token').val(token); // 添加令牌到隐藏输入
            $('#edit-name-modal').modal('show'); 
        } 
    }); 
});

步骤3:在namelist.php的模态框添加令牌隐藏输入

<input type="hidden" id="person_id" name="person_id">
<input type="hidden" id="edit_token" name="edit_token"> <!-- 新增令牌输入 -->

步骤4:修改updatename.php验证令牌

<?php 
session_start();
include_once 'db.php'; 

if (isset($_POST["person_id"]) && isset($_POST["token"])) { 
    $person_id = $_POST["person_id"];
    $token = $_POST["token"];

    // 验证令牌是否有效
    if (!isset($_SESSION['edit_tokens'][$person_id]) || $_SESSION['edit_tokens'][$person_id] !== $token) {
        echo json_encode(["error" => "无效的请求,请重试"]);
        exit;
    }

    $object = new Dbc(); 
    $stmt = $object->connect()->prepare("SELECT * FROM persons WHERE person_id=?"); 
    $stmt->execute([$person_id]); 
    $profile_info = $stmt->fetch(); 
    echo json_encode($profile_info); 
} 
?>

步骤5:在namelist.php更新逻辑中验证令牌

if (isset($_POST["update"])) {
    session_start();
    $person_id = $_POST["person_id"];
    $token = $_POST["edit_token"];

    // 验证令牌
    if (!isset($_SESSION['edit_tokens'][$person_id]) || $_SESSION['edit_tokens'][$person_id] !== $token) {
        echo "无效的请求!";
        exit;
    }

    // 令牌使用后销毁,防止重复提交
    unset($_SESSION['edit_tokens'][$person_id]);

    // 执行更新(建议同时加上方案1的权限验证)
    $first_name = $_POST["first_name"];
    $last_name = $_POST["last_name"];
    $object = new Dbc();
    $update_stmt = $object->connect()->prepare("UPDATE persons SET first_name = ?, last_name = ? WHERE person_id = ?");
    $update_stmt->execute([$first_name, $last_name, $person_id]);

    header("Location: namelist.php");
    exit;
}

方案3:加密ID传递

把真实的person_id加密后传给前端,后端接收后解密得到真实ID。这样用户看不到原始ID,就算篡改加密字符串,解密后也得不到有效的ID(除非知道加密密钥)。

步骤1:添加加密解密函数(比如在db.php中)

function encryptId($id) {
    // 密钥要保密,建议存在环境变量中,不要硬编码
    $key = 'your-strong-secret-key-here';
    $iv = random_bytes(openssl_cipher_iv_length('aes-256-cbc'));
    $encrypted = openssl_encrypt((string)$id, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv);
    return base64_encode($encrypted . '::' . $iv);
}

function decryptId($encryptedId) {
    $key = 'your-strong-secret-key-here';
    $parts = explode('::', base64_decode($encryptedId), 2);
    if (count($parts) !== 2) {
        return false;
    }
    list($encryptedData, $iv) = $parts;
    $decrypted = openssl_decrypt($encryptedData, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv);
    return is_numeric($decrypted) ? (int)$decrypted : false;
}

步骤2:修改class.names.php传递加密ID

public function getAllNames() { 
    $obj = new Db(); 
    $stmt = $obj->connect()->query("SELECT * FROM persons"); 

    while ($person = $stmt->fetch()) { 
        $encryptedId = encryptId($person['person_id']);
        echo "<tr>"; 
        echo "<td>".$person['first_name']."</td>"; 
        echo "<td>".$person['last_name']."</td>"; 
        echo "<td><a data-encrypted-id=\"".$encryptedId."\" type=\"button\" data-toggle=\"modal\" data-target=\"#edit-name-modal\" class=\"btn btn-danger edit_data\" href=\"#\">Update</a></td>"; 
        echo "</tr>"; 
    } 
}

步骤3:修改names.js传递加密ID

$(document).on('click', '.edit_data', function(){ 
    var encryptedId = $(this).data("encrypted-id"); 

    $.ajax({ 
        url:"/data/updatename.php", 
        method:"POST", 
        data:{encrypted_id: encryptedId}, 
        dataType:"json", 
        success:function(data){ 
            $('#first_name').val(data.first_name); 
            $('#last_name').val(data.last_name); 
            $('#encrypted_id').val(data.encrypted_id); // 存储加密ID
            $('#edit-name-modal').modal('show'); 
        } 
    }); 
});

步骤4:修改updatename.php解密ID

<?php 
include_once 'db.php'; 

if (isset($_POST["encrypted_id"])) { 
    $encryptedId = $_POST["encrypted_id"];
    $person_id = decryptId($encryptedId);

    if (!$person_id) {
        echo json_encode(["error" => "无效的ID"]);
        exit;
    }

    $object = new Dbc(); 
    $stmt = $object->connect()->prepare("SELECT * FROM persons WHERE person_id=?"); 
    $stmt->execute([$person_id]); 
    $profile_info = $stmt->fetch(); 
    // 返回加密ID给前端
    $profile_info['encrypted_id'] = $encryptedId;
    echo json_encode($profile_info); 
} 
?>

步骤5:修改namelist.php模态框和更新逻辑

替换原来的person_id隐藏输入:

<input type="hidden" id="encrypted_id" name="encrypted_id">

更新逻辑:

if (isset($_POST["update"])) {
    $encryptedId = $_POST["encrypted_id"];
    $person_id = decryptId($encryptedId);

    if (!$person_id) {
        echo "无效的请求!";
        exit;
    }

    // 建议同时加上方案1的权限验证
    $first_name = $_POST["first_name"];
    $last_name = $_POST["last_name"];
    $object = new Dbc();
    $update_stmt = $object->connect()->prepare("UPDATE persons SET first_name = ?, last_name = ? WHERE person_id = ?");
    $update_stmt->execute([$first_name, $last_name, $person_id]);

    header("Location: namelist.php");
    exit;
}

总结

  • 永远不要信任前端数据:所有关键验证必须在后端完成,前端只是展示和收集数据。
  • 权限验证是核心:就算ID被篡改,没有权限的用户也无法修改记录。
  • 令牌/加密ID是辅助:进一步降低篡改成功的概率,提升安全性。

内容的提问来源于stack exchange,提问作者St. Coding

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 08:00:06