云脚本与外部服务器安全通信思路
由于玩家的本地客户端(地图 Lua)处于不可信环境中(存在内存修改、抓包重放、冒充他人 ID、拦截吞包等风险),云脚本与外部服务器通信的核心原则是:将客户端彻底降级为“透明搬运工”,以云脚本的平台权威数据作为唯一可信源,通过 AEAD 加密、时间窗口、密文去重与递增序号保障链路安全。
一、 精简版本(核心安全思路)
适用于快速对齐方案主干,仅保留最核心的防伪造、防篡改、防重放与时序判定逻辑:
云脚本(服务端)
├─ 1. 收集权威身份:监听平台事件,获取真实的玩家账号(plat_aid)、房间标识与全员队友信息
├─ 2. 构造数据包:封装需要发送的业务/身份数据 + 签发时间戳(如有强时序要求可追加自增 sequence_id)
└─ 3. AEAD 加密:使用固定密钥 + 唯一 Nonce 进行 AEAD 加密,生成防篡改密文
│ MsSendMlEvent 下发密文
▼
客户端(地图 Lua)
├─ 4. 接收本端密文:过滤房间广播,仅缓存属于当前本地玩家的密文
└─ 5. 透明原样转发:客户端不持有密钥、不解密,将密文原样转发给外部服务器
│ 发送密文请求
▼
外部服务器
├─ 6. 密文 Hash 去重:记录密文 Hash(如 5 分钟后释放),拒绝同 Hash 请求,防止短期抓包重放
├─ 7. AEAD 解密验签:使用固定密钥解密并验证完整性(防篡改),提取内部绑定的真实玩家身份(防串号)
├─ 8. 时间戳校验:检查时间戳是否超时(如超过 5 分钟则忽略),防止长期保留旧包重放
└─ 9. 时序判定与执行:结合时间戳(或配合 sequence_id)判定包体新旧顺序,提取明文数据执行业务并绑定连接身份二、 完整版本(含身份公证、生命周期、防丢包与防乱序)
适用于工程落地设计,涵盖局隔离、票据补发刷新、双源交叉比对、连接绑定以及网络丢包/乱序处理:
云脚本(服务端)
├─ 1. 权威身份收集:监听 _roomloaded 与 _playerjoin,集齐房间内全员权威 plat_aid、昵称与 role_id
├─ 2. 生成本局唯一标识:用 <MsGetRoomLoadedTs()>-<随机数> 生成同房间统一的 room_id 与开局时间 start_ts
├─ 3. 构造数据包(v1 JSON):
│ └─ 包含 aid(本人账号)、name、role_id、room_id、start_ts、issue_ts(签发时间戳)、members(全员队友列表) + 可选 sequence_id
├─ 4. AEAD 加密封装:JSON -> AEAD 加密(固定密钥 Key + 每次生成的唯一 Nonce) -> 拼接 (Nonce + 密文 + AuthTag) 并使用 MsBase64Encode 编码为 ml_ticket
└─ 5. 按需重签:收到客户端 AuthTicketRequest 补发请求时,忽略客户端上行参数,仅取事件自带权威 role_id 重新生成 Nonce 并签发
│ MsSendMlEvent 下发 AuthTicket(房间广播) ▲ send_mlscript_msg 请求补发/刷新(3s 节流)
▼ │
客户端(地图 Lua)
├─ 6. 防广播串号过滤:收到 MsSendMlEvent 后,严格校验 data.player:get_id() == LOCAL_PLAYER.id 才缓存密文
├─ 7. 生命周期与刷新:无票时拦截登录并每 3s 请求云脚本补发;300s 到期前主动清空刷新;断线立即 Reset
├─ 8. 原样转发凭证:发起安全登录请求,将 ml_ticket(云脚本 AEAD 密文,可选附带平台登录加密包)原样上送
└─ 9. 日常 RPC 通信:后续业务请求不传 aid(防替别人发包),仅在已绑定身份的连接上发送序列化/编码后的业务载荷
│ TCP 发送密文凭证与 RPC 请求 ▲ 下行 Push 推送 + 客户端按序号 Pull(lastOrder) 轮询补拉
▼ │
外部服务器
├─ 10. 密文指纹去重(防短期重放):计算并记录 ml_ticket 密文 Hash(300s 过期),拒绝重复使用同一张票据
├─ 11. AEAD 解密与完整性验证:使用固定密钥 Key 与包内 Nonce 解密并校验 AuthTag(若被篡改直接拒绝),再校验 JSON 结构与版本号
├─ 12. 时间窗口校验(防过期与时钟偏移):检查签发时间 issue_ts 是否在 [-30s, +300s] 窗口内
├─ 13. 双源交叉比对(可选):若同时接入了平台登录加密包,将 ml_ticket 解密出的本人及全员队友 aid/昵称与之逐一比对
├─ 14. 连接级身份绑定:校验通过后将该 TCP 连接与权威 aid 强绑定,后续所有业务包只认连接不认客户端自报 ID
└─ 15. 丢包与乱序处理(双序号机制):
├─ 防丢包(order 序号):服务端每条推送带单调递增 order;客户端可配合 Pull(lastOrder) 主动补拉漏包,并按 order > lastOrder 去重、同帧升序排列
└─ 防乱序(push_seq 序号):每次状态更新带自增 push_seq;若旧包乱序晚到(seq <= lastPushSeq),自动剥离包内的全量快照,只保留增量变更,防止旧状态回档覆盖新状态三、 双向安全通信版本(云脚本 ⇄ 外部服务器 双向闭环)
当业务不仅需要“云脚本 ➔ 外部服务器”单向上报,还需要“外部服务器 ➔ 云脚本”安全下发敏感指令(如外部服校验资产后通知云脚本在局内发放道具/结算)时,需采用对称 AEAD 加密与双向序号确认机制,防止客户端伪造下行指令或恶意吞包:
云脚本(服务端)
├─ 1. 权威身份与局标识:收集真实 plat_aid,生成本局唯一 room_id
├─ 2. 上行封装(M2S):业务载荷 + room_id + aid + ts + m2s_seq + ack_s2m_seq -> AEAD 加密(固定密钥 + 随机 Nonce)
├─ 3. 下行解密验签(S2M):收到客户端转交的外部服密文 -> AEAD 解密验签 -> 校验 room_id、aid 与 [-30s, +300s] 时间窗口
├─ 4. 下行防重放与保序:按玩家维护 last_s2m_seq,仅执行 s2m_seq > last_s2m_seq 的指令(配合密文 Hash 去重)
└─ 5. 上行超时重传:维护待确认的 M2S 关键指令队列,收到外部服回传的 ack_m2s_seq 后清理;超时未确认则重发,防客户端恶意吞包
│ MsSendMlEvent(下发 M2S 密文) ▲ send_mlscript_msg(转交 S2M 密文)
▼ │
客户端(地图 Lua —— 纯盲盒中转站 / 不可信通道)
├─ 6. 广播过滤:收到 MsSendMlEvent 仅提取 LOCAL_PLAYER 自己的 M2S 密文
├─ 7. 上行透传:通过 TCP 将 M2S 密文原样转发给外部服务器
└─ 8. 下行透传:收到外部服务器下发的 S2M 密文后,不解密不修改,直接调用 send_mlscript_msg 转交云脚本
│ TCP 转发 M2S 密文 ▲ TCP 下发 S2M 密文(Push + Pull 保达)
▼ │
外部服务器
├─ 9. 上行防重放与解密:记录 M2S 密文 Hash(300s 过期) -> AEAD 解密验签 -> 校验 [-30s, +300s] 时间窗口与连接绑定的 aid
├─ 10. 上行时序与防吞包校验:按 (room_id, aid) 校验 m2s_seq
│ ├─ 状态快照型:仅处理 m2s_seq > last_m2s_seq 的最新状态包
│ └─ 增量事件型:要求 m2s_seq 连续递增;按 (room_id, aid, m2s_seq) 做业务幂等,遇断号则缓存等待或要求重传
└─ 11. 下行封装(S2M):响应/指令数据 + room_id + aid + ts + s2m_seq + ack_m2s_seq -> AEAD 加密(固定密钥 + 随机 Nonce)下发四、 关键安全机制补充说明
- 为什么使用 AEAD(如 AES-GCM / ChaCha20-Poly1305)?
- 相比传统 AES-CBC,AEAD 同时具备机密性(加密)与完整性认证(Auth Tag 验签),一旦密文在客户端被篡改任意 1 比特,解密时直接验签失败拒绝,无需额外拼接 HMAC。
- 什么是 Nonce?为什么要每次唯一?
- Nonce(Number used once)是每次加密时生成的唯一随机数或递增编号(通常 12 字节,随密文明文传输)。在固定密钥下,每次使用不同 Nonce 可确保相同明文每次加密出完全不同的密文,且避免 AEAD 因 Nonce 复用导致密钥流被异或破解。
- 为什么要加
room_id(会话隔离)?- 云脚本中的全局变量
sequence_id = 0在新开一局或房间重启时会归零。带上<开局时间戳>-<随机数>组成的room_id后,外部服务器按(aid, room_id)维度记录sequence_id,避免玩家开下一局时序号从 1 开始被误判为上一局的旧包。
- 云脚本中的全局变量

