• 产品手册
  • 编辑器功能手册
  • 云脚本
  • 与外部服务器安全通信

云脚本与外部服务器安全通信思路

由于玩家的本地客户端(地图 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)下发

四、 关键安全机制补充说明

  1. 为什么使用 AEAD(如 AES-GCM / ChaCha20-Poly1305)?
    • 相比传统 AES-CBC,AEAD 同时具备机密性(加密)与完整性认证(Auth Tag 验签),一旦密文在客户端被篡改任意 1 比特,解密时直接验签失败拒绝,无需额外拼接 HMAC。
  2. 什么是 Nonce?为什么要每次唯一?
    • Nonce(Number used once)是每次加密时生成的唯一随机数或递增编号(通常 12 字节,随密文明文传输)。在固定密钥下,每次使用不同 Nonce 可确保相同明文每次加密出完全不同的密文,且避免 AEAD 因 Nonce 复用导致密钥流被异或破解。
  3. 为什么要加 room_id(会话隔离)?
    • 云脚本中的全局变量 sequence_id = 0 在新开一局或房间重启时会归零。带上 <开局时间戳>-<随机数> 组成的 room_id 后,外部服务器按 (aid, room_id) 维度记录 sequence_id,避免玩家开下一局时序号从 1 开始被误判为上一局的旧包。