feat(controlled): 实现设备激活与安全认证流程

- 添加API客户端、加密存储和provision/token激活逻辑
- WebSocket改用Bearer令牌认证,移除REGISTER请求
- 设备ID改为服务端下发,支持令牌刷新和强制下线处理
- 新增deviceSecret加密存储和accessToken自动刷新
- 更新设备ID获取方式为出厂SN,添加安全存储依赖
This commit is contained in:
2026-08-01 15:13:12 +08:00
parent 376a2c1217
commit 6eb2c7321a
124 changed files with 10535 additions and 862 deletions

View File

@@ -0,0 +1,284 @@
-- =============================================================
-- WebRTCSignalServer 数据库建表脚本 (MySQL 8.0+)
--
-- 【请手动执行,服务端代码当前仍为内存实现,不会自动建表】
--
-- 执行方式:
-- mysql -u root -p < schema.sql
--
-- 字符集统一使用 utf8mb4 / utf8mb4_0900_ai_ci
-- 所有时间字段使用 DATETIME(3),由应用层写入 UTC 时间
-- =============================================================
CREATE DATABASE IF NOT EXISTS `webrtc_signal`
DEFAULT CHARACTER SET utf8mb4
DEFAULT COLLATE utf8mb4_0900_ai_ci;
USE `webrtc_signal`;
-- =============================================================
-- 1. 主控端用户账号
-- =============================================================
CREATE TABLE IF NOT EXISTS `app_user` (
`user_id` VARCHAR(32) NOT NULL COMMENT '用户ID格式 usr_<20位Base62>',
`username` VARCHAR(32) NOT NULL COMMENT '登录名',
`password_hash` VARCHAR(100) NOT NULL COMMENT 'BCrypt 哈希cost=12',
`status` VARCHAR(16) NOT NULL DEFAULT 'ACTIVE'
COMMENT '账号状态ACTIVE/SUSPENDED/BANNED',
`status_until` DATETIME(3) NULL COMMENT '临时封禁到期时间NULL 表示永久或未封禁',
`status_reason` VARCHAR(255) NULL COMMENT '封禁原因',
`token_version` BIGINT NOT NULL DEFAULT 1
COMMENT '凭据版本号,递增后所有已签发令牌立即失效',
`is_admin` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否管理员',
-- 双因子认证
`totp_secret` VARCHAR(64) NULL COMMENT 'TOTP 密钥Base32NULL 表示未启用',
`totp_enabled` TINYINT(1) NOT NULL DEFAULT 0 COMMENT 'TOTP 是否已完成绑定',
-- 防爆破
`failed_attempts` INT NOT NULL DEFAULT 0 COMMENT '连续登录失败次数',
`locked_until` DATETIME(3) NULL COMMENT '锁定截止时间',
`last_login_at` DATETIME(3) NULL,
`last_login_ip` VARCHAR(64) NULL,
`created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`updated_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)
ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (`user_id`),
UNIQUE KEY `uk_username` (`username`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB COMMENT='主控端用户账号';
-- =============================================================
-- 2. 密码历史(防止重复使用近期密码)
-- =============================================================
CREATE TABLE IF NOT EXISTS `password_history` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`user_id` VARCHAR(32) NOT NULL,
`password_hash` VARCHAR(100) NOT NULL,
`created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
PRIMARY KEY (`id`),
KEY `idx_user_created` (`user_id`, `created_at` DESC),
CONSTRAINT `fk_pwdhist_user` FOREIGN KEY (`user_id`)
REFERENCES `app_user` (`user_id`) ON DELETE CASCADE
) ENGINE=InnoDB COMMENT='密码历史,建议保留最近 5 条';
-- =============================================================
-- 3. 登录会话(刷新令牌上下文)
-- =============================================================
CREATE TABLE IF NOT EXISTS `login_session` (
`session_id` VARCHAR(32) NOT NULL COMMENT '会话ID格式 ses_<20位Base62>',
`principal_id` VARCHAR(32) NOT NULL COMMENT '主体IDuser_id',
`principal_type` VARCHAR(16) NOT NULL DEFAULT 'USER' COMMENT 'USER/DEVICE',
`refresh_token_hash` VARCHAR(64) NOT NULL COMMENT '当前刷新令牌的 SHA-256轮转后更新',
`refresh_expires_at` DATETIME(3) NOT NULL,
`ip` VARCHAR(64) NULL,
`user_agent` VARCHAR(256) NULL,
`revoked` TINYINT(1) NOT NULL DEFAULT 0,
`revoked_reason` VARCHAR(255) NULL,
`last_seen_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
PRIMARY KEY (`session_id`),
KEY `idx_principal` (`principal_id`, `revoked`),
KEY `idx_expires` (`refresh_expires_at`)
) ENGINE=InnoDB COMMENT='登录会话,支持踢线与刷新令牌轮转';
-- =============================================================
-- 4. SN 白名单(出厂/部署时批量导入)
-- =============================================================
CREATE TABLE IF NOT EXISTS `allowed_device` (
`sn` VARCHAR(64) NOT NULL COMMENT '设备序列号',
`batch` VARCHAR(64) NULL COMMENT '批次标识',
`note` VARCHAR(255) NULL,
`imported_by` VARCHAR(64) NULL,
`imported_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
PRIMARY KEY (`sn`),
KEY `idx_batch` (`batch`)
) ENGINE=InnoDB COMMENT='允许激活的 SN 白名单';
-- =============================================================
-- 5. 被控端设备
-- SN 仅作内部主键,对外一律使用高熵不可枚举的 device_uid
-- =============================================================
CREATE TABLE IF NOT EXISTS `device` (
`device_uid` VARCHAR(32) NOT NULL COMMENT '对外设备ID格式 dev_<22位Base62>',
`sn` VARCHAR(64) NOT NULL COMMENT '设备序列号(内部使用,勿对外暴露)',
`secret_hash` VARCHAR(64) NOT NULL COMMENT 'deviceSecret 的 SHA-256',
`model` VARCHAR(64) NULL,
`status` VARCHAR(16) NOT NULL DEFAULT 'ACTIVE'
COMMENT 'ACTIVE/SUSPENDED/BANNED',
`status_until` DATETIME(3) NULL,
`status_reason` VARCHAR(255) NULL,
`token_version` BIGINT NOT NULL DEFAULT 1,
-- 防骚扰配置P1 阶段使用)
`strict_mode` TINYINT(1) NOT NULL DEFAULT 1
COMMENT '严格模式:仅允许已绑定账号连接',
`quiet_start` TIME NULL COMMENT '勿扰时段开始',
`quiet_end` TIME NULL COMMENT '勿扰时段结束',
`provisioned_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`last_online_at` DATETIME(3) NULL,
PRIMARY KEY (`device_uid`),
UNIQUE KEY `uk_sn` (`sn`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB COMMENT='被控端设备身份';
-- =============================================================
-- 6. 设备激活 nonce防重放
-- 建议配合定时任务清理过期记录,或改用 Redis
-- =============================================================
CREATE TABLE IF NOT EXISTS `provision_nonce` (
`nonce` VARCHAR(64) NOT NULL,
`sn` VARCHAR(64) NULL,
`expires_at` DATETIME(3) NOT NULL,
`created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
PRIMARY KEY (`nonce`),
KEY `idx_expires` (`expires_at`)
) ENGINE=InnoDB COMMENT='激活请求 nonce用于防重放';
-- =============================================================
-- 7. 设备绑定关系P1 阶段:主控端唯一寻址凭证)
-- =============================================================
CREATE TABLE IF NOT EXISTS `device_binding` (
`binding_id` VARCHAR(36) NOT NULL COMMENT 'UUID',
`device_uid` VARCHAR(32) NOT NULL,
`user_id` VARCHAR(32) NOT NULL,
`role` VARCHAR(16) NOT NULL DEFAULT 'OPERATOR'
COMMENT 'OWNER/OPERATOR/VIEWER',
`alias` VARCHAR(64) NULL COMMENT '主控端自定义备注名',
`status` VARCHAR(16) NOT NULL DEFAULT 'ACTIVE' COMMENT 'ACTIVE/REVOKED',
`bound_by` VARCHAR(32) NULL,
`bound_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`expire_at` DATETIME(3) NULL COMMENT '绑定过期时间NULL 表示长期有效',
PRIMARY KEY (`binding_id`),
UNIQUE KEY `uk_device_user` (`device_uid`, `user_id`),
KEY `idx_user_status` (`user_id`, `status`),
KEY `idx_device_status` (`device_uid`, `status`)
) ENGINE=InnoDB COMMENT='设备与账号绑定关系';
-- =============================================================
-- 8. 配对码P1 阶段:建立绑定)
-- =============================================================
CREATE TABLE IF NOT EXISTS `pairing_code` (
`code_hash` VARCHAR(64) NOT NULL COMMENT '配对码的 SHA-256',
`device_uid` VARCHAR(32) NOT NULL,
`attempts` INT NOT NULL DEFAULT 0 COMMENT '错误尝试次数,超限即失效',
`used` TINYINT(1) NOT NULL DEFAULT 0,
`expires_at` DATETIME(3) NOT NULL,
`created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
PRIMARY KEY (`code_hash`),
KEY `idx_device` (`device_uid`),
KEY `idx_expires` (`expires_at`)
) ENGINE=InnoDB COMMENT='一次性配对码';
-- =============================================================
-- 9. 被控端黑名单P1 阶段:拉黑骚扰账号)
-- =============================================================
CREATE TABLE IF NOT EXISTS `device_blacklist` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`device_uid` VARCHAR(32) NOT NULL,
`blocked_user_id` VARCHAR(32) NOT NULL,
`reason` VARCHAR(255) NULL,
`created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
PRIMARY KEY (`id`),
UNIQUE KEY `uk_device_user` (`device_uid`, `blocked_user_id`),
KEY `idx_device` (`device_uid`)
) ENGINE=InnoDB COMMENT='被控端黑名单';
-- =============================================================
-- 10. 连接会话记录WebRTC 通话审计)
-- =============================================================
CREATE TABLE IF NOT EXISTS `connection_session` (
`session_id` VARCHAR(36) NOT NULL,
`binding_id` VARCHAR(36) NULL,
`device_uid` VARCHAR(32) NOT NULL,
`user_id` VARCHAR(32) NOT NULL,
`auth_type` VARCHAR(16) NULL COMMENT 'NONE/CODE/PASSWORD',
`started_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`ended_at` DATETIME(3) NULL,
`end_reason` VARCHAR(64) NULL,
PRIMARY KEY (`session_id`),
KEY `idx_device_started` (`device_uid`, `started_at` DESC),
KEY `idx_user_started` (`user_id`, `started_at` DESC)
) ENGINE=InnoDB COMMENT='远程控制会话记录';
-- =============================================================
-- 11. 审计日志(只追加,不修改)
-- =============================================================
CREATE TABLE IF NOT EXISTS `audit_log` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`actor_type` VARCHAR(16) NOT NULL COMMENT 'USER/DEVICE/ADMIN/SYSTEM',
`actor_id` VARCHAR(64) NULL,
`action` VARCHAR(64) NOT NULL
COMMENT 'LOGIN/LOGIN_FAILED/LOGOUT/REGISTER/PROVISION/BAN/UNBAN/KICK/BIND/UNBIND/BLACKLIST...',
`target_type` VARCHAR(16) NULL,
`target_id` VARCHAR(64) NULL,
`result` VARCHAR(16) NOT NULL DEFAULT 'SUCCESS' COMMENT 'SUCCESS/FAILURE',
`ip` VARCHAR(64) NULL,
`user_agent` VARCHAR(256) NULL,
`detail` VARCHAR(512) NULL COMMENT '注意:敏感信息需脱敏后再写入',
`created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
PRIMARY KEY (`id`),
KEY `idx_actor` (`actor_id`, `created_at` DESC),
KEY `idx_action` (`action`, `created_at` DESC),
KEY `idx_created` (`created_at` DESC)
) ENGINE=InnoDB COMMENT='安全审计日志';
-- =============================================================
-- 12. 骚扰举报P2 阶段风控)
-- =============================================================
CREATE TABLE IF NOT EXISTS `abuse_report` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`device_uid` VARCHAR(32) NOT NULL COMMENT '举报方(被控端)',
`reported_user_id` VARCHAR(32) NOT NULL COMMENT '被举报账号',
`reason` VARCHAR(255) NULL,
`handled` TINYINT(1) NOT NULL DEFAULT 0,
`created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
PRIMARY KEY (`id`),
KEY `idx_reported` (`reported_user_id`, `created_at` DESC),
KEY `idx_handled` (`handled`)
) ENGINE=InnoDB COMMENT='骚扰举报记录';
-- =============================================================
-- 清理建议(可配置为 MySQL Event 或应用层定时任务)
-- =============================================================
-- DELETE FROM provision_nonce WHERE expires_at < NOW();
-- DELETE FROM pairing_code WHERE expires_at < NOW();
-- DELETE FROM login_session WHERE refresh_expires_at < NOW() OR revoked = 1;
-- DELETE FROM audit_log WHERE created_at < DATE_SUB(NOW(), INTERVAL 180 DAY);
-- =============================================================
-- 初始管理员账号
-- =============================================================
-- 不在此处插入明文密码。请通过环境变量启动服务自动创建:
-- BOOTSTRAP_ADMIN_USERNAME=admin
-- BOOTSTRAP_ADMIN_PASSWORD=<强密码>
-- 或注册后手动提升权限:
-- UPDATE app_user SET is_admin = 1 WHERE username = 'admin';