Firebase Database游戏全球排行榜防篡改实现方案咨询
这个问题很典型——开放读写的规则确实方便开发初期测试,但上线后必须收紧,不然恶意篡改排行榜简直是分分钟的事。结合Firebase安全规则和身份验证,我们可以完美解决这个问题,核心思路是只允许认证用户更新自己的最高分,且新分数必须确实高于当前存储的分数。
1. 先启用Firebase Authentication
首先你得给每个玩家分配唯一的身份,不然没法区分是谁在提交分数。Firebase提供了多种认证方式(比如匿名、邮箱、Google登录等),选适合你游戏的就行。游戏里玩家登录后,Firebase会生成一个uid,我们用这个uid来关联每个玩家的最高分数据。
2. 设计合理的数据库结构
建议把最高分数据存在/highscores/{userId}路径下,每个userId节点只存该玩家的最高分,结构示例:
{ "highscores": { "abc123xyz": { "score": 1500 }, "def456uvw": { "score": 2200 } } }
这种结构清晰,也方便安全规则做针对性校验。
3. 配置核心安全规则
这是解决问题的关键,我们需要在规则里做三重校验:
- 确认写入请求来自已认证的玩家本人
- 新提交的分数确实高于当前存储的最高分(第一次提交时默认高于0)
- 写入的数据只能包含合法的
score字段,防止注入其他恶意内容
具体规则代码如下:
{ "rules": { "highscores": { "$userId": { ".write": "request.auth != null && request.auth.uid == $userId", ".validate": " newData.hasOnly(['score']) && newData.child('score').isNumber() && newData.child('score').val() > (data.exists() ? data.child('score').val() : 0) " }, ".read": true // 保持排行榜可被所有玩家查看 } } }
规则拆解:
.write:只有已认证且uid匹配路径中$userId的用户才能写入,确保玩家只能修改自己的分数,不能篡改他人数据。.validate:newData.hasOnly(['score']):限制只能写入score字段,避免写入无关的恶意数据。newData.child('score').isNumber():确保分数是数字类型,防止字符串等非法值。newData.child('score').val() > (data.exists() ? data.child('score').val() : 0):核心逻辑——新分数必须大于当前存储的分数(如果是第一次提交,当前数据不存在,就和0比较)。
4. 客户端代码配合(可选但推荐)
虽然安全规则已经做了兜底,但客户端可以先做前置校验:读取当前用户的最高分,比较新分数是否更高,只有更高时才发起写入请求。这样能减少不必要的数据库请求,提升用户体验。伪代码示例:
// 获取当前用户的uid const userId = firebase.auth().currentUser.uid; // 读取当前最高分 firebase.database().ref(`highscores/${userId}`).once('value').then(snapshot => { const currentHighScore = snapshot.exists() ? snapshot.val().score : 0; const newScore = 1800; // 玩家新获得的分数 if (newScore > currentHighScore) { // 写入新分数 firebase.database().ref(`highscores/${userId}`).set({ score: newScore }); } });
注意:客户端校验只是优化,不能替代安全规则,因为客户端代码可以被反编译篡改,安全规则才是最后一道防线。
进阶:防止极端作弊(可选)
如果你的游戏分数有理论最大值(比如关卡全通最多得10000分),可以在规则里再加一层限制:
newData.child('score').val() <= 10000
或者用Cloud Functions做更复杂的校验——比如验证分数是否符合游戏逻辑(比如玩家的游戏时长、操作记录是否能支撑这个分数),然后由Functions来写入数据库,这样客户端完全没有直接写入权限,安全性更高。
内容的提问来源于stack exchange,提问作者peter_griffin11

