
本文介绍在不依赖框架的前提下,通过服务端验证与客户端数据结构重构,从根本上防止用户利用浏览器控制台伪造 ajax 请求篡改游戏分数等关键业务数据。核心思路是:不信任前端传来的最终结果,而是由后端基于可信操作过程重新计算并校验。
本文介绍在不依赖框架的前提下,通过服务端验证与客户端数据结构重构,从根本上防止用户利用浏览器控制台伪造 ajax 请求篡改游戏分数等关键业务数据。核心思路是:不信任前端传来的最终结果,而是由后端基于可信操作过程重新计算并校验。
在纯前端实现的记忆游戏中,常见做法是 JavaScript 计算出最终得分(如 totalScore)后,直接通过 $.post() 提交用户名与分数至 PHP 后端保存。然而,这种设计存在严重安全隐患:任何熟悉基础 jQuery 的用户,只需在浏览器开发者工具中执行一行代码,即可绕过游戏逻辑,任意提交高分:
$.post("./backend/save_score.php", {"player_name": "Hacker", "score": 999999999});仅靠前端限制(如禁用控制台、混淆代码、添加时间戳或随机 token)无法真正解决问题——因为所有前端逻辑均可被审查、调试与重放。真正的防护必须落在服务端。
✅ 正确方案:传递“过程”,而非“结果”
将前端职责从「计算并上报分数」转变为「记录并上报用户真实操作序列」,由 PHP 后端独立复现游戏逻辑、验证操作合法性,并最终生成可信分数。例如:
1. 前端:记录并发送操作日志(而非分数)
// 示例:记录每次翻牌的卡片 ID 和时间戳(可选)
const gameActions = [];
function onCardFlip(cardId) {
gameActions.push({
action: "flip",
cardId: cardId,
timestamp: Date.now()
});
}
$("#submitScore").on("click", function() {
const playerName = $('#playerName').val().trim() || "Unknown";
// 发送完整操作序列,不含 score 字段
const payload = {
player_name: playerName,
actions: gameActions, // 如 [{action:"flip", cardId:"c3"}, ...]
game_version: "1.0" // 防止旧版客户端绕过新验证逻辑
};
$.post("./backend/save_score.php", payload, function(response) {
if (response.success) {
fetchHighScores();
$("#submitScore").prop('disabled', true);
} else {
alert("提交失败:" + response.error);
}
}).fail(() => alert("网络错误,请重试"));
});2. 后端(save_score.php):严格校验 + 重算分数
<?php
header('Content-Type: application/json; charset=utf-8');
session_start();
// 简单防重复提交(可选)
if (isset($_SESSION['score_submitted']) && time() - $_SESSION['score_submitted'] < 5) {
echo json_encode(['success' => false, 'error' => '请求过于频繁']);
exit;
}
$data = json_decode(file_get_contents('php://input'), true) ?: $_POST;
$actions = $data['actions'] ?? [];
$playerName = filter_var($data['player_name'] ?? '', FILTER_SANITIZE_STRING);
// ✅ 关键校验步骤:
// 1. 检查 actions 是否为空或格式非法
if (!is_array($actions) || count($actions) === 0) {
echo json_encode(['success' => false, 'error' => '无效的操作记录']);
exit;
}
// 2. 校验操作序列是否符合游戏规则(示例逻辑)
$validCards = ['c1', 'c2', 'c3', 'c4', 'c5', 'c6']; // 实际应从数据库/配置加载
$flippedPairs = [];
$score = 0;
foreach ($actions as $action) {
if ($action['action'] !== 'flip' || !in_array($action['cardId'], $validCards)) {
echo json_encode(['success' => false, 'error' => '检测到非法操作']);
exit;
}
// 模拟配对逻辑:每成功一对得 10 分(实际按你的规则实现)
$flippedPairs[] = $action['cardId'];
if (count($flippedPairs) === 2) {
if ($flippedPairs[0] === $flippedPairs[1]) {
$score += 10;
}
$flippedPairs = []; // 重置
}
}
// 3. 可选:添加时间合理性检查(如总耗时过短则质疑作弊)
$duration = (end($actions)['timestamp'] ?? 0) - ($actions[0]['timestamp'] ?? 0);
if ($duration < 1000) { // 少于 1 秒完成?极大概率作弊
error_log("Suspiciously fast game: {$playerName}, {$duration}ms");
echo json_encode(['success' => false, 'error' => '游戏时间异常,拒绝提交']);
exit;
}
// ✅ 安全入库:仅使用后端计算的 $score
try {
$pdo = new PDO("mysql:host=localhost;dbname=memory_game", $user, $pass);
$stmt = $pdo->prepare("INSERT INTO scores (player_name, score, created_at) VALUES (?, ?, NOW())");
$stmt->execute([$playerName, $score]);
$_SESSION['score_submitted'] = time();
echo json_encode(['success' => true, 'score' => $score]);
} catch (Exception $e) {
error_log("DB save failed: " . $e->getMessage());
echo json_encode(['success' => false, 'error' => '服务器内部错误']);
}⚠️ 重要注意事项
- 永远不要信任 $_POST 中的 score、level、coins 等数值型结果字段,它们必须由服务端基于可验证行为重新生成;
- 前端可增加轻量级辅助校验(如操作次数上限、最小耗时提示),但仅作用户体验优化,不可作为安全依据;
- 对敏感接口建议添加简单 Token 机制(如一次性 form_token 存入 session 并随请求提交),防御 CSRF(虽非本问题核心,但属良好实践);
- 日志记录异常请求(如高频提交、超低耗时、非法 cardId),便于后续分析攻击模式。
通过将「分数计算权」完全移交服务端,并以可审计的操作序列为输入,你构建的不再是一个易被操控的“结果提交接口”,而是一个具备业务逻辑自证能力的「游戏过程验证服务」——这才是无框架环境下最坚实、最可持续的安全防线。










