服务端权威验证
概述
客户端一切数据都可被修改:内存改数值、改配置、伪造请求。游戏安全的第一原则是服务端权威(Server Authoritative):所有影响结果的计算都在服务器完成,客户端只是"表现终端"。本文讲清楚哪些逻辑必须上服务器、客户端如何沦为纯表现层、敏感操作如何二次校验、数值如何做合法性验证。
一、权威原则与边界划分
1.1 什么是服务端权威
服务端权威:
所有核心玩法逻辑在服务器运算
客户端发送"意图",服务端计算"结果"
客户端收到的状态以服务器为准
客户端被篡改不影响他人(只影响自己画面)
反例(客户端权威):
客户端计算伤害/掉落/胜负 → 服务器只收结果
被修改客户端可以:无限伤害、必赢、刷道具权威边界划分:
服务器权威:结算、掉落、货币、属性成长、胜负判定、概率
客户端表现:动画、音效、本地预测、输入平滑
客户端辅助:寻路建议、画质设置、界面展示1.2 划分检查表
设计阶段问自己:
这个结果影响他人吗?→ 影响 → 服务器算
这个结果产生资产吗?→ 产生 → 服务器算
这个结果能被验证吗?→ 能 → 服务器算
只是本地视觉反馈吗?→ 是 → 客户端做// 错误示范:客户端上报结果
{
"opcode": "FIGHT_RESULT",
"damage": 999999,
"win": true
}
// 正确做法:客户端上报操作
{
"opcode": "FIGHT_ACTION",
"action": "USE_SKILL",
"target": 12345
}
// 服务器计算伤害 = f(攻击力, 防御力, 技能倍率, 随机数)二、核心玩法服务器运算
2.1 典型场景上收
必须服务器运算的场景:
战斗结算:伤害公式、胜负、星级
掉落与抽卡:概率、保底、结果序列
货币变动:扣减、加发、流水
属性成长:升级、强化、进阶
排行榜与成就:以服务器数据为准
交易与拍卖:价格、锁定、成交// 伤害计算在服务器(伪代码)
int damage = (atk - def) * skillRate * critFactor;
if (damage <= 0) damage = 1; // 保底伤害
// 关键:公式只在服务器执行
// 客户端只有攻击方 atk、技能等展示数据2.2 客户端预测与校正
客户端预测(用于手感):
本地立即播放操作效果(不等待服务器)
服务器返回权威结果 → 客户端校正
预测与结果不一致 → 回滚到服务器状态
约定:
预测只是"提前展示"
最终一切以服务器状态为准
预测失败不能影响服务器数据// 客户端预测流程
1. 发送操作 → 立即本地表现(预测)
2. 服务器计算 → 广播权威状态
3. 客户端比对 → 一致则平滑,不一致则校正
4. 结算类结果:绝不预测(等服务器)三、敏感操作服务端二次校验
3.1 二次校验清单
敏感操作定义:
花钱的:商城购买、充值兑换、抽卡
转移资产的:赠送、交易、拍卖
影响排名的:冲榜、结算上报
账号安全的:改绑、注销、领取大额奖励
二次校验:
主校验:业务合法性(库存、价格、冷却)
副校验:环境校验(设备、IP、风控标记)
确认校验:关键操作弹确认(客户端 UI)
服务器强制:即使客户端跳过确认,服务器仍校验3.2 服务器校验实现
// 商城购买二次校验
1. 校验商品是否存在、是否在售、是否限购
2. 校验玩家余额 >= 价格
3. 校验防刷(频次/风控)
4. 原子扣款(防并发重复扣)
5. 发货 + 流水校验原则:
不信任客户端传的"价格/数量"
以服务器配置的商品价格为准
数量上限服务器限制
每笔变动记流水(可追溯)// 不信任客户端字段
// 客户端只传商品 ID
PurchaseReq { goodsId: 1001 }
// 价格、数量、折扣全部查服务器配置
Goods goods = goodsConfig.get(goodsId);
int price = goods.price; // 服务器配置四、数值合法性验证
4.1 合法值域校验
数值合法性范围:
值域校验:0 <= 数量 <= 上限(防负数、防溢出)
枚举校验:操作类型/物品类型必须合法
状态校验:当前状态允许此操作吗(冷却/等级/条件)
约束校验:次数上限、持仓上限、时间窗口// 值域校验示例
public void addItems(long playerId, int itemId, long count) {
if (count <= 0 || count > MAX_ONCE) {
throw new BusinessException(INVALID_COUNT);
}
if (!itemConfig.contains(itemId)) {
throw new BusinessException(INVALID_ITEM);
}
...
}4.2 防溢出与并发
溢出风险:
long 运算防溢出(Math.addExact)
数值封顶(不超上限)
同玩家操作串行(单线程处理该玩家)
并发风险:
重复领取(幂等:已领取标记)
重复扣款(条件更新/锁)
库存超卖(原子扣减)// 条件更新防并发(Redis/DB)
UPDATE player SET coin = coin - ?
WHERE player_id = ? AND coin >= ?;
// 影响行数为 0 → 余额不足,拒绝4.3 客户端伪造防御
常见伪造:
内存修改数值(金币/属性/技能 CD)
改配置(概率、价格)
伪造请求(跳过 UI 直接发协议)
防御:
数值以服务器为准(客户端改了也没用)
关键状态服务器保存(CD、次数、位置)
协议校验(见协议安全章节)
服务器对任何"看起来不合理"的请求做检查// 合理性校验示例
if (player.level < req.levelRequire) {
return reject("等级不足");
}
if (player.skillCooldown > now) {
return reject("技能冷却中");
}
if (player.pos.distance(req.pos) > MAX_SPEED * dt) {
return reject("移动速度异常"); // 瞬移检测
}五、服务端权威的代价与工程
5.1 性能与体验代价
权威化的代价:
服务器计算量大(全部逻辑上收)
网络交互多(操作 → 结果 → 广播)
实时性敏感场景(帧同步)延迟影响大
平衡策略:
高频低风险操作 → 客户端表现 + 服务器异步校验
低频高风险操作 → 服务器同步权威
对局内 → 服务器 Tick 计算 + 快照同步5.2 工程规范
工程规范:
业务逻辑只写在服务端(禁止核心逻辑下放)
客户端数据仅为展示副本
协议只含"意图"不含"结果"
所有关键计算走统一服务(如结算服务)
服务器加断言:结果异常立即告警// 服务器权威验证清单(上线检查)
[ ] 结算/掉落/货币/属性 全部服务器计算
[ ] 客户端协议不携带结果字段
[ ] 敏感操作二次校验 + 风控
[ ] 数值值域/枚举/状态校验
[ ] 幂等防重复(领取/购买/结算)
[ ] 异常数值请求有日志与告警六、实现要点
服务端权威核心:
核心玩法服务器运算(客户端仅表现)
客户端上报意图、服务器返回结果
敏感操作二次校验(花钱/转移/排名)
数值合法性验证(值域/枚举/状态/幂等)
异常请求记录日志并告警
常见坑:
客户端传结果 → 必须改为传意图
只校验不幂等 → 重复请求刷资产
本地预测未校正 → 客户端显示与服务器不符
异常请求不告警 → 外挂长期无人知
与其他系统衔接:
协议安全 → 加密/签名/防重放(上篇)
行为检测 → 异常频次与模式分析(下篇)
日志审计 → 异常请求留痕
反外挂 → 客户端完整性 + 服务端校验组合