路线阶段:Unity WebGL 小游戏实战第 14 章。
本章目标:明确“客户端不可信”前提下的最小安全体系,避免核心经济和排行榜被轻易污染。
学习目标
完成本章后,你应该能做到:
- 定义客户端与服务端的可信边界。
- 识别高风险路径(成绩提交、奖励发放、活动领奖)。
- 建立幂等、签名、异常检测三层防护。
- 把风控事件接入埋点与运维告警。
WebGL 安全现实
需要明确:
- 客户端逻辑可被逆向与篡改。
- 本地时间、请求参数都可能伪造。
- 仅靠前端校验无法防作弊。
因此策略是:关键结果必须可验证,关键奖励必须可追踪。
可信边界划分
客户端可做
- 交互体验校验(减少误操作)。
- 基础异常拦截(明显非法输入)。
- 日志与证据上报。
服务端必须做
- 奖励最终发放决策。
- 排行榜成绩可信度判断。
- 活动领奖次数与时间窗校验。
高风险接口清单
submit_scoreclaim_rewardevent_claimpurchase_item
这些接口必须具备:
- 幂等 token
- 请求签名/时间戳
- 风险评分
幂等机制
public sealed class IdempotencyTokenBuilder
{
public static string Build(string playerId, string action, string nonce)
{
return playerId + "|" + action + "|" + nonce;
}
}
服务端保存最近 token,重复请求直接拒绝或返回相同结果。
请求签名
public sealed class RequestSigner
{
public static string Sign(string payloadJson, string ts, string secret)
{
var raw = payloadJson + "|" + ts + "|" + secret;
return Sha256(raw);
}
private static string Sha256(string text)
{
using (var sha = System.Security.Cryptography.SHA256.Create())
{
var bytes = System.Text.Encoding.UTF8.GetBytes(text);
var hash = sha.ComputeHash(bytes);
return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
}
}
}
注意:WebGL 客户端里的
secret不能视为真正安全,仅作为低成本筛查。
成绩异常检测
public sealed class ScoreRiskEvaluator
{
public int Evaluate(ScoreSubmitPayload p)
{
var risk = 0;
if (p.Score <= 0) risk += 30;
if (p.KillCount > 200000) risk += 40;
if (p.DurationSec <= 0 || p.DurationSec > 36000) risk += 20;
if (p.MaxDps > 999999) risk += 40;
if (p.ReplayHashMismatch) risk += 50;
if (p.ClientBuildBlacklisted) risk += 80;
return risk;
}
}
[Serializable]
public sealed class ScoreSubmitPayload
{
public string PlayerId;
public int StageId;
public int Score;
public int KillCount;
public int DurationSec;
public int MaxDps;
public bool ReplayHashMismatch;
public bool ClientBuildBlacklisted;
}
策略:
risk >= 80:直接拒绝。40 <= risk < 80:进入人工复核/延迟上榜。< 40:正常入榜。
奖励风控
public sealed class RewardRiskGuard
{
private readonly Dictionary<string, int> _claimCounter = new Dictionary<string, int>(256);
public bool CanClaim(string playerId, string rewardKey, long nowMs)
{
var bucket = playerId + "|" + rewardKey + "|" + DayBucket(nowMs);
int c;
if (!_claimCounter.TryGetValue(bucket, out c))
{
_claimCounter[bucket] = 1;
return true;
}
if (c >= 3)
{
return false;
}
_claimCounter[bucket] = c + 1;
return true;
}
private static long DayBucket(long ms)
{
return ms / (24L * 3600L * 1000L);
}
}
回放佐证
对于高价值成绩:
- 要求提交回放摘要 hash。
- 服务端抽样复跑回放。
- 若关键指标偏差大,标记可疑。
黑名单与熔断
最小能力:
- 黑名单设备指纹/账号。
- 异常峰值时临时关闭高风险入口(如双倍奖励广告)。
- 自动触发安全告警。
安全埋点
新增事件:
risk_score_submitrisk_reward_claimrisk_action_blockedrisk_manual_review
与前面系统联动
- 排行榜系统:成绩上榜前走风险评估。
- 活动系统:领奖请求加入幂等与频控。
- 广告系统:奖励发放走统一防重逻辑。
- 发布运维:风险事件进入告警看板。
WebGL 注意点
- 前端混淆只能提高门槛,不是安全方案。
- 客户端检测与服务端判定必须分层。
- 安全策略升级要支持远程配置开关。
验收清单
- 重放同一领奖请求不会重复发奖。
- 异常成绩会被标记并阻断上榜。
- 高风险事件可被监控系统捕获。
- 风险策略配置可热更新。
常见坑
坑 1:把签名逻辑当核心防线
客户端密钥可泄露。签名只是筛查,不是终极防护。
坑 2:风控只做“拒绝”,不做留痕
无法复盘和迭代。每次拦截必须记录上下文。
坑 3:策略写死代码
应支持远程调参,否则响应慢。
本月作业
实现“排行榜防刷一期”:
- 成绩提交接入风险评分。
- 高风险成绩延迟上榜并打标。
- 输出一周风控命中报告(命中率/误杀率)。
下一章进入 Unity WebGL 小游戏实战 15:版本长线演进与技术债治理(可持续迭代收官)。