安全:服务端校验钓鱼等待时间,防止前端篡改 wait_time

cast() 将 {token, cast_at, wait_time} 以 JSON 存入 Redis;
reel() 在验 token 后额外校验:
  elapsed = now() - cast_at >= wait_time - 1(含1秒容差)
  未满足则返回422「鱼还没上钩,别急!」

即使用户通过 DevTools 将 wait_time 改为 0,
服务端仍按实际 wait_time 拒绝过早的收竿请求。
This commit is contained in:
2026-03-01 16:33:32 +08:00
parent 168bc002f9
commit 0ea6ea206c

View File

@@ -92,8 +92,13 @@ class FishingController extends Controller
$waitTime = rand($waitMin, $waitMax);
$token = Str::random(32);
$tokenKey = "fishing:token:{$user->id}";
// token 有效期 = 等待时间 + 10秒点击窗口 + 5秒缓冲
Redis::setex($tokenKey, $waitTime + 15, $token);
// token 有效期 = 等待时间 + 8秒点击窗口 + 5秒缓冲
// 同时把 cast 时间戳和 wait_time 一起存入,供 reel 做服务端时间校验
Redis::setex($tokenKey, $waitTime + 13, json_encode([
'token' => $token,
'cast_at' => time(),
'wait_time' => $waitTime,
]));
// 5. 生成随机浮漂坐标(百分比,避开边缘)
$bobberX = rand(15, 85); // 左右 15%~85%
@@ -130,15 +135,33 @@ class FishingController extends Controller
return response()->json(['status' => 'error', 'message' => '请先登录'], 401);
}
// 1. 验证 token(防止脚本绕过浮漂直接收竿
// 1. 验证 token + 服务端时间校验(防止前端篡改 wait_time 跳过等待
$tokenKey = "fishing:token:{$user->id}";
$storedToken = Redis::get($tokenKey);
$storedJson = Redis::get($tokenKey);
$clientToken = $request->input('token', '');
if (! $storedToken || $storedToken !== $clientToken) {
if (! $storedJson) {
return response()->json([
'status' => 'error',
'message' => '鱼儿跑了!浮漂已超时或令牌无效,请重新抛竿。',
'message' => '鱼儿跑了!浮漂已超时,请重新抛竿。',
], 422);
}
$stored = json_decode($storedJson, true);
// 校验 token 一致性
if (($stored['token'] ?? '') !== $clientToken) {
return response()->json([
'status' => 'error',
'message' => '令牌无效,请重新抛竿。',
], 422);
}
// 校验服务端时间:距抛竿必须已过 wait_time 秒(允许 ±1s 误差)
$elapsed = time() - (int) ($stored['cast_at'] ?? 0);
$required = (int) ($stored['wait_time'] ?? 0);
if ($elapsed < $required - 1) {
return response()->json([
'status' => 'error',
'message' => '鱼还没上钩,别急!',
], 422);
}