PHP API如何验证移动端APP来源并防范虚假获奖参赛提交?
解决方案建议
1. 基于登录态的服务端验证令牌
既然用户已在APP登录,APP应持有服务端颁发的会话令牌(如JWT或Session ID)。你的PHP API要求请求时携带该令牌,验证通过后从令牌解析出真实的account ID,绝不直接信任请求体中的account ID。同时,服务端需维护「用户获奖记录池」,每次请求先校验该account ID是否确实获得对应prize ID——这一步必须由服务端完成,完全不依赖客户端判断。
PHP示例(JWT验证):
use Firebase\JWT\JWT; use Firebase\JWT\Key; // 从请求头获取令牌 $token = $_SERVER['HTTP_AUTHORIZATION'] ?? ''; if (empty($token)) { http_response_code(401); exit('Unauthorized'); } try { // 验证并解析JWT,提取真实account ID $decoded = JWT::decode($token, new Key('your-secret-key', 'HS256')); $realAccountId = $decoded->account_id; // 对比请求体account ID,不一致直接拒绝 if ($_POST['account_id'] !== $realAccountId) { http_response_code(403); exit('Invalid account ID'); } // 服务端校验用户是否获得对应奖品 $isValidPrize = checkUserPrize($realAccountId, $_POST['prize_id']); if (!$isValidPrize) { http_response_code(400); exit('Invalid prize claim'); } // 后续处理地址、协议同意等信息 } catch (Exception $e) { http_response_code(401); exit('Invalid token'); } // 模拟服务端获奖校验函数 function checkUserPrize($accountId, $prizeId) { $pdo = new PDO('mysql:host=localhost;dbname=your_db', 'user', 'pass'); $stmt = $pdo->prepare('SELECT COUNT(*) FROM user_prizes WHERE account_id = ? AND prize_id = ? AND is_claimed = 0'); $stmt->execute([$accountId, $prizeId]); return $stmt->fetchColumn() > 0; }
2. 一次性请求签名机制
和APP开发方约定一个共享密钥,让APP发起请求前,用HMAC算法对account ID + prize ID + 当前时间戳生成签名,将这些参数连同签名一起提交。PHP端用相同规则生成签名并对比,同时校验时间戳有效期(比如5分钟内),防止重放攻击。
PHP端校验逻辑:
$secretKey = 'your-shared-secret'; // 和APP端一致,密钥需保密 $accountId = $_POST['account_id']; $prizeId = $_POST['prize_id']; $timestamp = $_POST['timestamp']; $clientSignature = $_POST['signature']; // 校验请求是否过期 if (time() - $timestamp > 300) { // 5分钟有效期 http_response_code(400); exit('Request expired'); } // 服务端生成签名并对比 $serverSignature = hash_hmac('sha256', $accountId . $prizeId . $timestamp, $secretKey); if ($serverSignature !== $clientSignature) { http_response_code(403); exit('Invalid signature'); } // 再执行服务端获奖有效性校验(同方案1的checkUserPrize函数)
注:该方案依赖APP安全存储密钥,若密钥泄露及时更换即可,比无验证的安全性高很多。
3. 预生成获奖凭证
如果获奖判定由服务端完成,用户获奖时服务端直接生成唯一的获奖凭证(如UUID),关联该用户和prize ID。APP只需携带此凭证提交地址等信息,API仅需校验凭证的有效性(是否存在、未被使用、关联用户匹配)。
PHP示例:
$claimToken = $_POST['claim_token']; // 校验获奖凭证 $pdo = new PDO('mysql:host=localhost;dbname=your_db', 'user', 'pass'); $stmt = $pdo->prepare('SELECT account_id, prize_id FROM prize_claims WHERE token = ? AND is_used = 0'); $stmt->execute([$claimToken]); $claim = $stmt->fetch(PDO::FETCH_ASSOC); if (!$claim) { http_response_code(400); exit('Invalid claim token'); } // 可选:验证请求的account ID与凭证关联用户是否一致 if ($_POST['account_id'] !== $claim['account_id']) { http_response_code(403); exit('Mismatched account'); } // 处理地址等信息,标记凭证为已使用 $stmt = $pdo->prepare('UPDATE prize_claims SET is_used = 1, address = ?, agree_terms = ?, claim_date = ? WHERE token = ?'); $stmt->execute([$_POST['address'], $_POST['agree_terms'], $_POST['date'], $claimToken]);
这个方案安全性最高,伪造凭证几乎不可能,完全依赖服务端预生成逻辑,无需APP存储密钥,从根源避免了逆向获取prize ID后伪造请求的问题。
核心注意事项
- 绝对禁止依赖客户端的获奖判定:无论采用哪种方案,最终获奖有效性必须由服务端校验,客户端仅负责展示和提交。
- 不要信任请求体中的任何参数:account ID必须从登录令牌或预生成凭证中获取,而非直接使用请求参数。
- 防范重放攻击:签名方案加入时间戳,预生成凭证标记为一次性使用,避免同一请求被多次提交。
内容的提问来源于stack exchange,提问作者Adam
相关产品推荐
相关产品推荐

