12 KiB
Round 5S — 真实 IoT 安全收口与验证边界
基线:373921447fa4727c20aed7b28c5d1519fe26fa78。仅本地修复;未部署、推送、修改共享数据库、连接真实 MQTT 或操作真实人脸服务。
1. 持久化契约
复用 iotlocks.lock_auth 与 iotlocks.lock_command。新增来源模式、稳定环境 ID、意图 UUID、递增业务版本、意图方向/时间、执行状态。命令另存明确设备 SN 与发送尝试开始时间。元数据由服务端生成,JSON 请求不能注入。
status/sync_status 保留原界面兼容语义,但不能单独证明设备已撤权。期望状态在授权意图中,发送状态在命令中,设备确认在双回执字段中。
| 执行状态 | 意义 | 恢复规则 |
|---|---|---|
| READY | 当前来源、当前环境、当前版本,尚未尝试发送 | 经身份、协议及写入门禁后可以恢复;PENDING 或连接失败导致的 FAIL 均不丢失意图 |
| DISPATCHING | 数据库已提交发送占用,传输可能尚未执行或结果未知 | 禁止盲重发;超时进入人工核对 |
| AWAITING_ACK | 传输调用返回,但设备结果未确认 | 禁止自动或人工命令重发;等待严格关联回执,超时人工核对 |
| CONFIRMED | 当前版本的门锁成功回执与网关回执齐备 | 授权确认成功或撤权确认清除;重复回执不改写 |
| REJECTED | 设备明确返回失败 | 意图进入人工核对,不能把 FAIL 当成重发许可 |
| MANUAL_REVIEW | 无法证明物理结果、旧操作仍可能影响设备 | 不宣称成功、不取消物理结果、不生成后继授权绕过问题 |
| QUARANTINED | LEGACY、Mock/真实来源不符、环境不符、非法身份或载荷 | 不重放、不自动升级为 REAL |
| CANCELED | 操作者取消了尚未尝试发送的命令 | 记录保留;未完成意图进入核对,不能伪称设备撤权 |
服务器启动时间不再作为可信来源判断依据。兼容属性 replayProtectionEnabled 即使关闭,也不能关闭来源、环境、版本和发送占用校验。
相同环境 ID 必须跨重启保持稳定;不能把进程启动时间或随机进程 ID 当作环境 ID。切换为另一环境时,旧意图保持隔离。
2. 事务与恢复
- 入住/退宿业务事务内持久化授权或撤权意图;退宿失败不能吞掉意图写入异常后仍声称成功。重复退宿仍执行撤权对账。
- 同一意图 UUID 最多创建一条命令,数据库唯一索引作为最终保护。
- 命令只落入 outbox;外发必须在事务提交之后。
- 独立事务按“授权行 → 命令行”顺序锁定并校验当前版本,把发送占用及尝试时间提交到数据库。
- 传输边界重新核验持久化占用、设备映射、载荷及权限;只执行一次发送尝试。
- 不确定异常、占用后崩溃、回执超时均进入核对。无协议幂等或查询证明时,不自动重发。
- 回执校验 Topic、seq、op、action、设备 SN、结果码及当前意图版本。命令确认和授权投影在同一数据库事务中完成。
READY 意图没有命令时可补建;已有 READY 命令由命令恢复器处理。失败/未知命令不再无限阻止恢复,也不会被补偿器绕过后重复生成。恢复扫描优先撤权,不因固定前 100 条策略阻塞后续合法意图。
旧版本回执无法更新新版本授权。未知旧撤权不能被本地“取消”后立即重新授予权限。
人工核对入口为现有授权/命令查询 API 返回的 executionState、intentAction、版本和静态原因。此次不增加能够把未知结果强行改成成功的 API。操作人员应保存设备侧证据,经批准后的下一阶段处理;不得直接批量修改数据库状态或来源标记。
3. 身份与协议
真实白名单为空、SN 为空或未知时一律拒绝。身份取明确设备 SN,再验证“设备 → 网关 → 分组”的数据库映射及授权快照。分组只负责路由,不是白名单身份。
本轮支持已有协议证据中的人脸/密码授权结构:
| 业务命令 | 帧 op | params |
|---|---|---|
| 0011 人脸授权 | facefeature | add、8 位人员编号、特征、人员编号、起始时间、结束时间、设备 SN、设备密码 |
| 0011 密码授权 | password | add、8 位人员编号、密码、起始时间、结束时间、设备 SN、设备密码 |
| 0012 撤权 | 对应授权类型的 op | delete、8 位人员编号、设备 SN、设备密码 |
Topic 为已有 ydlink/{groupCode}/thing/data/batch。seq 与命令流水一致,保持既有信封顺序;空载荷、错人员/目标/路由/类型、缺凭据或非法时间窗口在发布前拒绝。
不新增设备协议字段,不声称本地 UUID 被硬件理解,也不假定硬件支持幂等。既有 chksum=xx、固件和时区等仍需老师原始协议/报文独立确认;iot.mqtt.protocol-verified 默认 false,在获得证据之前真实传输被拒绝。未确认的指纹、IC 卡、网关配置和其他真实控制操作在此分支拒绝,不能扩大设备白名单来绕过。
4. 外发约束
- 原始 MQTT publish 在真实模式永久拒绝。运维 Controller 仅在
iot.mqtt.mock=true且 QA API 显式开启时加载,并要求专门权限iot:mqtt:manage。该权限未自动授予任何外部账号。 - 真实命令只能使用 command-aware 传输,并提供已提交的发送占用。所有授权、撤权、补偿、人工重试均走同一门禁。
- MQTT 默认不连接,手动 connect/reconnect 也检查模式与连接开关。所有遗嘱消息被拒绝,避免绕过 outbox。控制报文禁止 retained。
- 控制发送使用 QoS 0(at-most-once),禁用 Paho 自动重连、强制 clean session、拒绝文件报文持久化,防止客户端/会话队列绕过 outbox 自行重发。连接健康检查只创建无历史队列的新会话。丢失或送达不确定进入人工核对;该传输策略还需隔离 Broker 与老师端兼容验证,不能以提高 QoS 的方式绕过未知结果保护。
- OpenAPI 默认关闭;无密钥、短密钥和错误密钥拒绝。过滤器锚定真实 Controller 路径并处理 context path,配置路径不能绕过鉴权。
- 人脸 Mock 默认 true,优先于数据库 endpoint,直接返回合成结果。真实 HTTP 另需显式开关、环境一致、目标设备授权和精确端点白名单;禁止重定向。数据库 endpoint 无法越过白名单。
- 人脸图片只接受上传目录内的真实路径,拒绝外部 URL、目录穿越、符号链接越界及超大文件。Mock 提取不下载任何图片。
- 旧 DMS 不具备此分支的设备来源契约,真实网络被禁止,仅保留显式启用且端点为 loopback 的 Mock 回归通道,禁止重定向。
- 定时补偿默认关闭。开启后也不能发送旧 Mock、未知来源、发送结果未知或旧版本命令。
5. 安全迁移方案(未在共享环境执行)
脚本:RuoYi-Vue/sql/iot/005_round5s_execution_safety.sql。
这是增量字段与索引迁移,不删除/重建表,不创建账号,不修改权限。历史行默认 LEGACY / version 0 / QUARANTINED;禁止按日期或设备编号自动回填 REAL。空旧意图键保留为空,现有记录不被重新发送。
管理员后续须先备份并批准目标库、停止旧写入进程,在隔离 Kingbase 数据库验证脚本、Mapper、生成键、锁顺序及事务语义,再考虑批准应用到共享库。不能使用全量 qa-bootstrap 重新初始化已有库。回退时保留新增字段和证据,不自动删除列;回退旧应用也不得开启真实连接。
已在隔离 H2 PostgreSQL 模式测试脚本重复执行、默认隔离和唯一键。H2 不能替代 Kingbase 的方言及运行环境验收。
6. 认证值与历史资料
JWT 仅接受运行环境注入的 Base64 HS512 密钥(解码后至少 64 字节)。缺失/非法时登录签发安全失败,没有固定弱值回退。OpenAPI 使用注入密钥。Druid 控制台默认关闭,显式开启而无合格注入凭据时启动校验失败。
新 QA 种子不保留可直接登录的共享密码哈希,测试身份默认禁用。旧确定性临时密码降级被拒绝,即使旧开关被误开启也不会生成可预测密码。开发 Swagger 用户测试 API 默认禁用,示例中不保存密码或手机号。
管理员待轮换/复核清单(本轮未操作外部账号):
| 范围 | 所需处置 |
|---|---|
| 历史 application.yml JWT 值 | 更换密钥,确认旧 Token/会话失效策略 |
| 历史 OpenAPI 测试密钥 | 废止已部署固定值,仅在批准环境启用新密钥 |
| 历史 Druid 控制台凭据 | 禁用或轮换并核验实际访问控制 |
| 实验工程 DB/MQTT/启动脚本凭据 | 管理员核实仍有效的账号后轮换;本轮未复制或推送 |
| 既有 QA/共享库预置账号 | 审核、禁用或轮换;修改种子不会改变已经存在的账号 |
| 实验历史 iotlocks.dmp、人脸上传文件 | 限制传播并核对留存权限;不改写旧 Git 历史 |
| 继承依赖测试私钥、pay.png/profile.jpg 等素材 | 区分公开测试夹具与实际秘密,审核素材来源;旧提交和静态资源未自动清理 |
锁屏默认头像改用已有通用角色素材。源仓库历史、原工程和实验工程均保持不变,不 Force Push。
7. 自动化证据与未验证范围
Round5sExecutionIntegrationTest 使用真实 LockAuthServiceImpl、LockCommandServiceImpl、生产 MyBatis XML、DataSourceTransactionManager 和新建本地临时文件 H2 库。每次连接关闭后由文件重新打开,测试数据库不连接服务器。只有设备映射查询、MQTT 传输及回执是可控替身。
覆盖空/合法/非法白名单、分组冒充、人脸/密码及撤权载荷、Mock 切换隔离、旧请求时间恢复、意图提交后命令创建前中断、提交发送占用后中断、未知回执、明确设备拒绝、旧回执保护、错 Topic/设备、写入/连接关闭、协议未批准、回滚、并发发送占用、增量迁移及原始 MQTT/遗嘱旁路。
另有 YAML 人脸绑定/端点隔离、OpenAPI context path/密钥、JSON 元数据注入、JWT/Druid 认证、DMS 边界以及原业务桥接定向回归测试。桥接替身测试不作为真实恢复机制证明,旧“取消 SENT 后重新授权”和可预测密码测试已改为拒绝的安全语义。
重现命令(只运行选定测试,不运行数百项全量测试):
mvn -o -f RuoYi-Vue/pom.xml -pl ruoyi-admin -am \
'-Dtest=Round5s*,RealIotSecurityGateTest,FaceExtractClientMockTest,CommandRespHandlerMockTest,DmsHttpClientTest,DmsTokenManagerTest,TrainIotLockAuthBridgeMockTest' \
-Dsurefire.failIfNoSpecifiedTests=false test
mvn -o -f RuoYi-Vue/pom.xml -pl ruoyi-admin -am -DskipTests package
本轮自动化结果:81 项定向测试通过,失败/错误/跳过均为 0;Java 21 下九模块后端打包成功。授权、命令和锁屏三个 Vue 模板经隔离安装的 Vue 2.6.12 编译器检查无错误,脚本及状态标签工具通过语法检查;未完成前端全量 bundle 构建或浏览器验收。处理状态在授权/命令页面显示,未知结果不能通过“重发/取消”按钮操作,提示不再把请求受理写成设备已执行。
本轮未证明:Kingbase 实际升级与整套住宿事务、新配置下完整 Spring Boot 启动、跨进程/多实例故障注入、老师原始协议 checksum/固件/设备幂等、真实 Broker ACL/TLS、现场设备在线状态、实际门锁授权撤权以及管理员凭据轮换。
结论:软件安全逻辑可继续隔离验证,不能把本轮测试写成真实门锁联调成功;真实写入、网络连接、人脸 HTTP、OpenAPI 和自动补偿的默认开关保持关闭。