如何安全传递JS变量highScore至PHP并防止分数恶意篡改?
可靠的分数防篡改方案
由于前端代码完全暴露,任何前端加密/混淆逻辑都能被破解,核心思路必须是让服务器掌握验证分数的关键信息,而非依赖前端计算。以下是几个可落地的实战方案:
方案一:会话级签名验证(推荐)
- 游戏初始化时获取会话密钥
用户进入游戏页面时,由PHP生成绑定当前$_SESSION的唯一随机密钥,并返回给前端:
// game_init.php session_start(); // 生成16字节随机密钥,仅存于当前会话 $_SESSION['score_sign_key'] = bin2hex(random_bytes(16)); echo json_encode(['sign_key' => $_SESSION['score_sign_key']]);
前端通过AJAX请求该接口获取密钥,仅存在内存中(不要存localStorage或cookie,避免泄露)。
- 前端计算签名并提交
游戏结束时,用密钥对分数+会话ID做哈希计算,将分数和签名一同传给后端:
// 假设已从game_init.php拿到signKey,会话ID从页面渲染时嵌入 const highScore = 1234; const sessionId = "<?php echo session_id(); ?>"; // 用原生CryptoAPI或第三方库生成SHA-256哈希 const signature = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(highScore + sessionId + signKey)); const signatureHex = Array.from(new Uint8Array(signature)).map(b => b.toString(16).padStart(2, '0')).join(''); // 用POST提交数据 fetch('writescore.php', { method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'}, body: `score=${highScore}&signature=${signatureHex}` });
- 后端验证签名有效性
后端拿到参数后,用会话内存储的密钥重新计算签名,与前端提交的对比:
// writescore.php session_start(); if (!isset($_SESSION['score_sign_key'], $_POST['score'], $_POST['signature'])) { die("非法请求"); } $score = (int)$_POST['score']; $sessionId = session_id(); // 重新计算预期签名 $expectedSignature = hash('sha256', $score . $sessionId . $_SESSION['score_sign_key']); if ($expectedSignature !== $_POST['signature']) { die("分数篡改"); } // 验证通过后写入数据库,同时销毁密钥防止重复提交 unset($_SESSION['score_sign_key']); // 执行数据库写入逻辑
优势:密钥绑定当前会话且一次性使用,攻击者无法伪造签名(不知道会话内的密钥),安全性极高。
方案二:服务器端计算最终分数
如果游戏有明确的关卡、任务等进度节点,可让服务器全程跟踪进度,最终分数由服务器计算而非前端传递:
- 前端每完成一个关卡/任务,调用
update_progress.php提交进度(如?level=5) - 后端记录用户的进度数据、耗时等信息
- 游戏结束时,后端根据记录的进度自动计算最终分数,无需前端传值
优势:彻底杜绝前端篡改分数的可能,适合规则明确的游戏,但需要改造现有游戏逻辑。
方案三:时间窗口+签名双重验证
若不想大幅修改游戏逻辑,可在现有基础上升级验证机制:
- 游戏开始时生成带有效期的token
session_start(); $expire = time() + 600; // 设置10分钟有效期 // 用用户ID、有效期、服务器私密密钥生成token $token = hash('sha256', $_SESSION['user_id'] . $expire . 'your_server_secret_key'); $_SESSION['score_token'] = [$token, $expire]; echo json_encode(['token' => $token, 'expire' => $expire]);
- 前端提交分数时携带token和时间戳
const highScore = 1234; const currentTime = Math.floor(Date.now()/1000); fetch('writescore.php', { method: 'POST', body: `score=${highScore}&token=${token}&time=${currentTime}` });
- 后端多重验证
- 检查当前时间是否在token有效期内
- 用用户ID、有效期、服务器密钥重新计算token,与前端提交的对比
- 限制单用户短时间内的分数提交次数
避坑提醒
- 绝对不要把验证用的密钥写在前端代码里,哪怕混淆也会被破解
- 提交分数必须用POST请求,避免参数在URL中泄露
- 数据库记录用户提交分数的时间、IP等信息,事后可人工排查异常(如突然出现远超正常水平的分数)
内容的提问来源于stack exchange,提问作者ZioBit
相关产品推荐
相关产品推荐

