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
相关产品推荐
相关产品推荐

