feat:升级功能、密码卡框架、主密钥接口

This commit is contained in:
waner
2026-03-26 17:30:48 +08:00
parent c1e91e76b0
commit 11ed0b5edf
208 changed files with 16742 additions and 1156 deletions
+86 -5
View File
@@ -51,7 +51,7 @@ chmod +x /home/tms/scripts/tms.sh
说明: 说明:
- 脚本默认 `APP_HOME=/home/tms`,并通过 `--spring.config.additional-location=optional:file:<app_home>/config/` 从 `/home/tms/config/` 读取外部配置。 - 脚本默认 `APP_HOME=/home/tms`,并通过 `--spring.config.additional-location=optional:file:<app_home>/config/` 从 `/home/tms/config/` 读取外部配置。
- 默认要求 `/home/tms/config/application.yml` 存在。 - 默认要求 `/home/tms/config/application.yml` 存在。
- 默认 profile 为 `prod`;可通过 `SPRING_PROFILES_ACTIVE=dev /home/tms/scripts/tms.sh start` 覆盖。 - 脚本不再强制指定 profile;默认按 Spring 配置文件自身规则解析,可通过 `SPRING_PROFILES_ACTIVE=dev /home/tms/scripts/tms.sh start` 显式覆盖。
- 可通过 `JAR_PATH=/home/tms/tms-framework.jar` 覆盖 jar 路径。 - 可通过 `JAR_PATH=/home/tms/tms-framework.jar` 覆盖 jar 路径。
- 如果部署路径不是 `/home/tms`,使用 `APP_HOME=/your/path /your/path/scripts/tms.sh start`。 - 如果部署路径不是 `/home/tms`,使用 `APP_HOME=/your/path /your/path/scripts/tms.sh start`。
- 标准 CISD 初始化辅助脚本位于 `scripts/standard-init/`,部署时需复制到 `/home/tms/bin/`。 - 标准 CISD 初始化辅助脚本位于 `scripts/standard-init/`,部署时需复制到 `/home/tms/bin/`。
@@ -75,9 +75,19 @@ Open:
- Reset create task API: `POST /api/v1/init/reset/tasks` - Reset create task API: `POST /api/v1/init/reset/tasks`
- Reset execute API: `POST /api/v1/init/reset/tasks/{taskId}/execute` - Reset execute API: `POST /api/v1/init/reset/tasks/{taskId}/execute`
- Auth login API: `POST /api/v1/auth/login` - Auth login API: `POST /api/v1/auth/login`
- Compat password login API: `POST /api/v1/auth/password-login`
- Compat UKey login API: `POST /api/v1/auth/ukey-login`
- Compat UKey random API: `GET /api/v1/auth/ukey-login/randoms`
- UKey binding issue-sign API: `POST /api/v1/auth/roles/{roleCode}/ukeys/issue-sign`
- UKey binding API: `POST /api/v1/auth/roles/{roleCode}/ukeys/bind`
- Device info API: `GET /api/v1/device/info` - Device info API: `GET /api/v1/device/info`
- Device profile API: `GET /api/v1/device/profile` - Device profile API: `GET /api/v1/device/profile`
- Device runtime status API: `GET /api/v1/device/runtime-status` - Device runtime status API: `GET /api/v1/device/runtime-status`
- Upgrade package upload API: `POST /api/v1/upgrade-packages`
- Upgrade preview API: `POST /api/v1/upgrades/previews`
- Upgrade create task API: `POST /api/v1/upgrades`
- Upgrade execute API: `POST /api/v1/upgrades/{taskId}/execute`
- Upgrade list API: `GET /api/v1/upgrades`
- PCIe crypto count API: `GET /api/v1/device/crypto/device-count` - PCIe crypto count API: `GET /api/v1/device/crypto/device-count`
- PCIe crypto HMAC API: `POST /api/v1/device/crypto/hmac` - PCIe crypto HMAC API: `POST /api/v1/device/crypto/hmac`
@@ -139,6 +149,70 @@ Open:
- `tms.init.executor.rabbitmq-ctl-command` 指向可执行的 `rabbitmqctl` - `tms.init.executor.rabbitmq-ctl-command` 指向可执行的 `rabbitmqctl`
- 运行时 Swagger 文档以 `http://<ip>:8080/swagger-ui.html` 和 `http://<ip>:8080/v3/api-docs/internal-api` 为准 - 运行时 Swagger 文档以 `http://<ip>:8080/swagger-ui.html` 和 `http://<ip>:8080/v3/api-docs/internal-api` 为准
## Offline Upgrade
最小调用顺序:
- `POST /api/v1/upgrade-packages`
- `POST /api/v1/upgrades/previews`
- `POST /api/v1/upgrades`
- `POST /api/v1/upgrades/{taskId}/execute`
- `GET /api/v1/upgrades`
- `GET /api/v1/upgrades/{taskId}`
- `GET /api/v1/upgrades/{taskId}/log`
当前支持范围:
- 仅支持离线升级
- 当前支持升级对象:
- `TMS`
- `STANDARD_APP`
- `DIRECT_APP`
- 当前不支持:
- 在线升级
- `CVE` / 操作系统补丁升级
- 密码卡固件升级
当前流程:
- 先上传离线升级包,复用现有文件上传能力,得到 `fileId`
- 后端基于 `fileId` 解析升级包并做预检
- 预检结果返回升级对象、当前版本、目标版本和版本变化提示
- 用户确认后创建升级任务
- 调用执行接口后,任务异步进入后台执行
- 前端通过列表、详情和日志接口轮询任务状态
当前升级包要求:
- 外层为 zip 包
- 至少包含:
- `manifest.json`
- `signature.sig`
- `payload.zip`
- `signature.sig` 需要能被设备密码卡使用“固件完整性密钥”完成验签
- 服务端需要配置 `tms.upgrade.signature-public-key-base64`(Base64 编码的公钥 blob)
- `manifest.json` 当前要求包含:
- `packageId`
- `targetComponent`
- `targetVersion`
- `minCompatibleVersion`
- `description`
- `orchestrator`
- `fileChecksums.payloadZip`
当前执行规则:
- 同一时刻只允许一个升级任务处于 `RUNNING`
- 不允许目标版本低于当前版本
- 不满足最小兼容版本时拒绝升级
- 升级成功后会更新 `tms_device_software_version`
- 升级执行日志统一写入 `tms.upgrade.log-dir`
当前配置项:
- `tms.upgrade.staging-root-dir`
- `tms.upgrade.log-dir`
- `tms.upgrade.signature-public-key-base64`
升级包规范见:
- [2026-03-19-upgrade-package-spec.md](/Users/waner/Work/CISD/文档/tms-framework/docs/plans/2026-03-19-upgrade-package-spec.md)
- [标准收发器升级包最小样例](/Users/waner/Work/CISD/文档/tms-framework/docs/examples/upgrade-package/standard-app/README.md)
- [离线升级包预检联调清单与示例](/Users/waner/Work/CISD/文档/tms-framework/docs/plans/2026-03-19-upgrade-preview-integration-guide.md)
## Architecture (Monolith + Modular) ## Architecture (Monolith + Modular)
- One deployable Spring Boot application. - One deployable Spring Boot application.
@@ -189,6 +263,13 @@ src/main/java/com/cisd/tms
│ └── entity │ └── entity
├── init ├── init
├── auth ├── auth
## Auth Compat
当前仓库只包含后端认证接口,不包含登录页前端工程。
兼容旧管理端的登录调用说明见:
- [2026-03-23-auth-compat-integration-guide.md](/Users/waner/Work/CISD/文档/tms-framework/docs/plans/2026-03-23-auth-compat-integration-guide.md)
├── device ├── device
├── upgrade ├── upgrade
├── cert ├── cert
@@ -238,8 +319,8 @@ tms:
crypto-card: crypto-card:
enabled: ${TMS_CRYPTO_CARD_ENABLED:false} enabled: ${TMS_CRYPTO_CARD_ENABLED:false}
mode: ${TMS_CRYPTO_CARD_MODE:MOCK} # MOCK / JNA mode: ${TMS_CRYPTO_CARD_MODE:MOCK} # MOCK / JNA
vendor-lib-path: ${TMS_CRYPTO_VENDOR_LIB_PATH:} vendor-lib-path: ${TMS_CRYPTO_VENDOR_LIB_PATH:/home/tms/libs/libsdf_ukey_syd1408-x64.so.1.0.0}
vendor-lib-name: ${TMS_CRYPTO_VENDOR_LIB_NAME:swsdsdf} vendor-lib-name: ${TMS_CRYPTO_VENDOR_LIB_NAME:sdf_ukey_syd1408}
``` ```
- Internal PCIe debug APIs (strong-typed service): - Internal PCIe debug APIs (strong-typed service):
@@ -280,8 +361,8 @@ tms:
crypto-card: crypto-card:
enabled: ${TMS_CRYPTO_CARD_ENABLED:false} enabled: ${TMS_CRYPTO_CARD_ENABLED:false}
mode: ${TMS_CRYPTO_CARD_MODE:JNA} # MOCK / JNA mode: ${TMS_CRYPTO_CARD_MODE:JNA} # MOCK / JNA
vendor-lib-path: ${TMS_CRYPTO_VENDOR_LIB_PATH:} vendor-lib-path: ${TMS_CRYPTO_VENDOR_LIB_PATH:/home/tms/libs/libsdf_ukey_syd1408-x64.so.1.0.0}
vendor-lib-name: ${TMS_CRYPTO_VENDOR_LIB_NAME:swsdsdf} vendor-lib-name: ${TMS_CRYPTO_VENDOR_LIB_NAME:sdf_ukey_syd1408}
``` ```
- JNA native mapping: `com.cisd.tms.integration.crypto.pcie.jna.PcieNativeLibrary` - JNA native mapping: `com.cisd.tms.integration.crypto.pcie.jna.PcieNativeLibrary`
+35
View File
@@ -6,8 +6,43 @@ server:
port: 8080 port: 8080
tms: tms:
web:
cors:
enabled: true
allowed-origin-patterns:
- http://localhost:*
- http://127.0.0.1:*
allowed-methods:
- GET
- POST
- PUT
- DELETE
- OPTIONS
allowed-headers:
- Authorization
- Content-Type
- X-Requested-With
- X-Internal-Token
- X-App-Id
- X-Timestamp
- X-Nonce
- X-Signature
- X-Trace-Id
exposed-headers:
- X-Trace-Id
allow-credentials: true
max-age-seconds: 3600
upgrade:
staging-root-dir: /home/tmp/tms-upgrade-staging
log-dir: /home/tms/tmp/tms-upgrade-logs
signature-public-key-base64: change-me-base64-public-key-blob
security: security:
internal-token: change-me-internal-token internal-token: change-me-internal-token
internal-auth:
enabled: true
debug-role-code: KEY_ADMIN
debug-auth-level: FULL
debug-session-token: DEBUG-BYPASS
openapi: openapi:
timestamp-skew-seconds: 300 timestamp-skew-seconds: 300
clients: clients:
+2 -1
View File
@@ -137,7 +137,7 @@ mvn -q -DskipTests package
### 6.2 创建运行目录 ### 6.2 创建运行目录
```bash ```bash
sudo mkdir -p /home/tms/{bin,config,scripts,logs,run,tmp,uploads} sudo mkdir -p /home/tms/{bin,config,scripts,logs,run,tmp,uploads,libs}
sudo chown -R "$(whoami)":"$(whoami)" /home/tms sudo chown -R "$(whoami)":"$(whoami)" /home/tms
``` ```
@@ -165,6 +165,7 @@ ls -l /home/tms/config/application.yml
TMS 从以下位置加载外部配置: TMS 从以下位置加载外部配置:
- `/home/tms/config/application.yml` - `/home/tms/config/application.yml`
- 显式设置 `SPRING_PROFILES_ACTIVE` 时,额外加载 `/home/tms/config/application-\${SPRING_PROFILES_ACTIVE}.yml`
运行控制脚本: 运行控制脚本:
- [tms.sh](/Users/waner/Work/CISD/文档/tms-framework/scripts/tms.sh) - [tms.sh](/Users/waner/Work/CISD/文档/tms-framework/scripts/tms.sh)
@@ -0,0 +1,49 @@
# 标准收发器离线升级包最小样例
本目录提供 `STANDARD_APP` 离线升级包的最小示例骨架,用于对齐打包方、后端和联调流程。
目录结构:
```text
standard-app/
├── manifest.json
└── payload/
└── scripts/
└── orchestrator.sh
```
说明:
- 本目录中的 `manifest.json` 使用了占位符 `__PAYLOAD_SHA256__`
- 真正打包前,需要先对 `payload.zip` 计算 `SHA-256`,再替换该字段
- `signature.sig` 不放在仓库示例里,必须由打包方使用“固件完整性密钥”私钥在打包阶段生成
推荐打包步骤:
1. 进入当前示例目录
2. 将 `payload/` 压缩为 `payload.zip`
3. 计算 `payload.zip` 的 `SHA-256`
4. 用实际摘要替换 `manifest.json` 中的 `__PAYLOAD_SHA256__`
5. 对 `manifest.json` 原始字节生成 `signature.sig`
6. 将 `manifest.json`、`signature.sig`、`payload.zip` 压缩成最终 `upgrade-package.zip`
参考命令:
```bash
cd docs/examples/upgrade-package/standard-app
zip -rq payload.zip payload
PAYLOAD_SHA256="$(shasum -a 256 payload.zip | awk '{print $1}')"
sed "s/__PAYLOAD_SHA256__/${PAYLOAD_SHA256}/g" manifest.json > manifest.generated.json
# 这里改为公司正式签名工具,生成 manifest.generated.json 对应的 signature.sig
# sign-manifest manifest.generated.json > signature.sig
zip -j upgrade-package.zip manifest.generated.json signature.sig payload.zip
```
联调时建议:
- 先用这个目录骨架做一个 `STANDARD_APP` 预检样包
- `orchestrator.sh` 可以先只输出日志并返回 `0`
- 等预检、建任务、执行链路跑通后,再替换成真实升级脚本
@@ -0,0 +1,13 @@
{
"packageFormatVersion": 1,
"packageId": "PKG-20260319-STANDARD-001",
"targetComponent": "STANDARD_APP",
"targetVersion": "V1.3.2",
"minCompatibleVersion": "V1.3.0",
"description": "标准收发器企业版离线升级包最小样例",
"createdAt": "2026-03-19T16:30:00+08:00",
"orchestrator": "scripts/orchestrator.sh",
"fileChecksums": {
"payloadZip": "__PAYLOAD_SHA256__"
}
}
@@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -euo pipefail
TASK_ID="${1:-UNKNOWN}"
echo "[upgrade] task=${TASK_ID} phase=backup message=start backup placeholder"
echo "[upgrade] task=${TASK_ID} phase=stop-service message=stop service placeholder"
echo "[upgrade] task=${TASK_ID} phase=deploy message=deploy placeholder"
echo "[upgrade] task=${TASK_ID} phase=health-check message=health check placeholder"
echo "[upgrade] task=${TASK_ID} phase=done message=upgrade completed"
+292 -48
View File
@@ -1,70 +1,314 @@
-- ----------------------------------------------------------------------------- -- -----------------------------------------------------------------------------
-- TMS 基础库结构(TiDB/MySQL) -- TMS 最终库结构初始化脚本
-- 适用范围:当前已实现的初始化任务、步骤、设备节点、用户表 -- 适用范围:新库初始化
-- 数据库:TiDB / MySQL 8+
--
-- 说明:
-- 1. 本文件按当前项目最终表结构整理,适合“空库初始化”。
-- 2. 不再按 V1-V11 逐步执行 ALTER / RENAME,避免初始化时出现兼容性问题。
-- 3. 若用于已有库升级,请继续使用 Flyway migration,而不是本文件。
-- ----------------------------------------------------------------------------- -- -----------------------------------------------------------------------------
-- 1) 建库(如已存在则跳过)
CREATE DATABASE IF NOT EXISTS tms CREATE DATABASE IF NOT EXISTS tms
DEFAULT CHARACTER SET utf8mb4 DEFAULT CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci; COLLATE utf8mb4_general_ci;
USE tms; USE tms;
-- 2) 初始化任务主表 -- -----------------------------------------------------------------------------
-- 初始化任务
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS tms_init_task ( CREATE TABLE IF NOT EXISTS tms_init_task (
id BIGINT PRIMARY KEY COMMENT '主键ID', id BIGINT PRIMARY KEY,
task_id VARCHAR(64) NOT NULL COMMENT '任务业务ID', task_id VARCHAR(64) NOT NULL,
version VARCHAR(32) NOT NULL COMMENT 'CIPS版本', task_type VARCHAR(32) NOT NULL DEFAULT 'INIT' COMMENT '任务类型:INIT/RESET',
status VARCHAR(32) NOT NULL COMMENT '任务状态', version VARCHAR(32) NOT NULL,
product_type VARCHAR(16) NULL COMMENT '产品版本: enterprise/interbank/lite_direct', status VARCHAR(32) NOT NULL,
org_code VARCHAR(32) NULL COMMENT '机构号', product_type VARCHAR(16) NULL,
mq_type VARCHAR(32) NULL COMMENT '消息队列类型', org_code VARCHAR(32) NULL,
deploy_mode VARCHAR(16) NULL COMMENT '部署模式: single/double/four', mq_type VARCHAR(32) NULL,
init_plan_json LONGTEXT NULL COMMENT '初始化计划快照(JSON)', deploy_mode VARCHAR(16) NULL,
create_time DATETIME(3) NOT NULL COMMENT '创建时间', init_plan_json LONGTEXT NULL,
update_time DATETIME(3) NOT NULL COMMENT '更新时间', create_time DATETIME(3) NOT NULL,
update_time DATETIME(3) NOT NULL,
UNIQUE KEY uk_tms_init_task_task_id (task_id) UNIQUE KEY uk_tms_init_task_task_id (task_id)
) COMMENT='初始化任务表'; );
-- 3) 初始化任务步骤表
CREATE TABLE IF NOT EXISTS tms_init_task_step ( CREATE TABLE IF NOT EXISTS tms_init_task_step (
id BIGINT PRIMARY KEY COMMENT '主键ID', id BIGINT PRIMARY KEY,
task_id VARCHAR(64) NOT NULL COMMENT '任务业务ID', task_id VARCHAR(64) NOT NULL,
step_no INT NOT NULL COMMENT '步骤序号', step_no INT NOT NULL,
step_code VARCHAR(64) NOT NULL COMMENT '步骤编码', step_code VARCHAR(64) NOT NULL,
status VARCHAR(32) NOT NULL COMMENT '步骤状态', status VARCHAR(32) NOT NULL,
message VARCHAR(512) NULL COMMENT '步骤信息', message VARCHAR(512) NULL,
command_line VARCHAR(2048) NULL COMMENT '实际执行命令', command_line VARCHAR(2048) NULL,
log_path VARCHAR(512) NULL COMMENT '日志文件路径', log_path VARCHAR(512) NULL,
exit_code INT NULL COMMENT '命令退出码', exit_code INT NULL,
create_time DATETIME(3) NOT NULL COMMENT '创建时间', create_time DATETIME(3) NOT NULL,
update_time DATETIME(3) NOT NULL COMMENT '更新时间', update_time DATETIME(3) NOT NULL,
UNIQUE KEY uk_tms_init_task_step_task_no (task_id, step_no), UNIQUE KEY uk_tms_init_task_step_task_no (task_id, step_no),
KEY idx_tms_init_task_step_task_id (task_id) KEY idx_tms_init_task_step_task_id (task_id)
) COMMENT='初始化任务步骤表'; );
-- -----------------------------------------------------------------------------
-- 认证与设备基础表
-- -----------------------------------------------------------------------------
-- 4) 系统用户表(简化)
CREATE TABLE IF NOT EXISTS tms_auth_user ( CREATE TABLE IF NOT EXISTS tms_auth_user (
id BIGINT PRIMARY KEY COMMENT '主键ID', id BIGINT PRIMARY KEY,
username VARCHAR(64) NOT NULL COMMENT '用户名', username VARCHAR(64) NOT NULL,
display_name VARCHAR(128) NOT NULL COMMENT '显示名', display_name VARCHAR(128) NOT NULL,
role VARCHAR(64) NOT NULL COMMENT '角色', role VARCHAR(64) NOT NULL,
create_time DATETIME(3) NOT NULL COMMENT '创建时间', create_time DATETIME(3) NOT NULL,
update_time DATETIME(3) NOT NULL COMMENT '更新时间', update_time DATETIME(3) NOT NULL,
UNIQUE KEY uk_tms_auth_user_username (username) UNIQUE KEY uk_tms_auth_user_username (username)
) COMMENT='系统用户表'; );
-- 5) 设备节点表
CREATE TABLE IF NOT EXISTS tms_device_node ( CREATE TABLE IF NOT EXISTS tms_device_node (
id BIGINT PRIMARY KEY COMMENT '主键ID', id BIGINT PRIMARY KEY,
node_id VARCHAR(64) NOT NULL COMMENT '节点业务ID', node_id VARCHAR(64) NOT NULL,
host VARCHAR(128) NOT NULL COMMENT '节点主机地址', host VARCHAR(128) NOT NULL,
status VARCHAR(32) NOT NULL COMMENT '节点状态', status VARCHAR(32) NOT NULL,
create_time DATETIME(3) NOT NULL COMMENT '创建时间', create_time DATETIME(3) NOT NULL,
update_time DATETIME(3) NOT NULL COMMENT '更新时间', update_time DATETIME(3) NOT NULL,
UNIQUE KEY uk_tms_device_node_node_id (node_id) UNIQUE KEY uk_tms_device_node_node_id (node_id)
) COMMENT='设备节点表'; );
-- 6) 可选初始化数据(按需启用) CREATE TABLE IF NOT EXISTS tms_file_record (
-- INSERT INTO tms_auth_user (id, username, display_name, role, create_time, update_time) id BIGINT PRIMARY KEY,
-- VALUES (10001, 'admin', '系统管理员', 'ADMIN', NOW(3), NOW(3)); file_id VARCHAR(64) NOT NULL,
original_filename VARCHAR(255) NOT NULL,
storage_path VARCHAR(1024) NOT NULL,
status VARCHAR(32) NOT NULL,
create_time DATETIME(3) NOT NULL,
update_time DATETIME(3) NOT NULL,
UNIQUE KEY uk_tms_file_record_file_id (file_id)
);
-- -----------------------------------------------------------------------------
-- 设备软件版本
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS tms_device_software_version (
id BIGINT PRIMARY KEY,
component_code VARCHAR(64) NOT NULL,
component_name VARCHAR(128) NOT NULL,
current_version VARCHAR(128) NOT NULL,
source_type VARCHAR(32) NOT NULL,
detected_at DATETIME NULL,
remarks VARCHAR(255) NULL,
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_tms_device_software_version_component_code (component_code)
);
INSERT INTO tms_device_software_version
(id, component_code, component_name, current_version, source_type, detected_at, remarks)
VALUES
(1001, 'RABBITMQ', '消息队列', 'RabbitMQ V3.8.21', 'MANUAL', NOW(), '出厂预置'),
(1002, 'TLQ', '消息队列', 'tonglink/Q V8.1.11', 'MANUAL', NOW(), '出厂预置'),
(1003, 'TIDB', '数据库', 'TiDB V8.1.0', 'MANUAL', NOW(), '出厂预置'),
(1004, 'NGINX', 'Nginx', 'Nginx V1.20', 'MANUAL', NOW(), '出厂预置'),
(1005, 'APP', '标准收发器企业版', '标准收发器企业版 V1.3.2', 'MANUAL', NOW(), '出厂预置'),
(1006, 'TMS', 'CISD终端管理服务', 'CISD终端管理服务 V1.0.0', 'MANUAL', NOW(), '出厂预置')
ON DUPLICATE KEY UPDATE
component_name = VALUES(component_name),
current_version = VALUES(current_version),
source_type = VALUES(source_type),
detected_at = VALUES(detected_at),
remarks = VALUES(remarks),
update_time = CURRENT_TIMESTAMP;
-- -----------------------------------------------------------------------------
-- 升级任务
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS t_upgrade_task (
id BIGINT PRIMARY KEY,
task_id VARCHAR(64) NOT NULL,
upgrade_mode VARCHAR(16) NOT NULL,
target_component VARCHAR(32) NOT NULL,
package_file_id VARCHAR(64) NOT NULL,
package_name VARCHAR(255) NOT NULL,
package_hash VARCHAR(128) NOT NULL,
current_version VARCHAR(64) NOT NULL,
target_version VARCHAR(64) NOT NULL,
status VARCHAR(32) NOT NULL,
verify_passed TINYINT(1) NOT NULL DEFAULT 0,
verify_message VARCHAR(1024) NULL,
manifest_json TEXT NULL,
detail_log_path VARCHAR(1024) NULL,
error_message VARCHAR(1024) NULL,
operator VARCHAR(64) NULL,
started_at DATETIME(3) NULL,
finished_at DATETIME(3) NULL,
create_time DATETIME(3) NOT NULL,
update_time DATETIME(3) NOT NULL,
UNIQUE KEY uk_t_upgrade_task_task_id (task_id),
KEY idx_t_upgrade_task_status (status),
KEY idx_t_upgrade_task_component (target_component),
KEY idx_t_upgrade_task_create_time (create_time)
);
-- -----------------------------------------------------------------------------
-- 新认证体系
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS tms_role_account (
id BIGINT PRIMARY KEY,
role_code VARCHAR(64) NOT NULL,
display_name VARCHAR(128) NOT NULL,
required_ukey_count INT NOT NULL,
password_hash VARCHAR(256) NOT NULL,
password_salt VARCHAR(128) NOT NULL,
status VARCHAR(32) NOT NULL,
need_change_password TINYINT(1) NOT NULL DEFAULT 1,
failed_count INT NOT NULL DEFAULT 0,
locked_until DATETIME(3) NULL,
last_login_at DATETIME(3) NULL,
last_active_at DATETIME(3) NULL,
create_time DATETIME(3) NOT NULL,
update_time DATETIME(3) NOT NULL,
UNIQUE KEY uk_tms_role_account_role_code (role_code)
);
CREATE TABLE IF NOT EXISTS tms_role_ukey_binding (
id BIGINT PRIMARY KEY,
role_code VARCHAR(64) NOT NULL,
slot_no INT NOT NULL,
ukey_serial VARCHAR(128) NOT NULL,
ukey_pubkey TEXT NOT NULL,
issuer_sign TEXT NULL,
status VARCHAR(32) NOT NULL,
bound_at DATETIME(3) NOT NULL,
unbound_at DATETIME(3) NULL,
create_time DATETIME(3) NOT NULL,
update_time DATETIME(3) NOT NULL,
UNIQUE KEY uk_tms_role_ukey_binding_role_slot (role_code, slot_no),
KEY idx_tms_role_ukey_binding_role_status (role_code, status)
);
CREATE TABLE IF NOT EXISTS tms_auth_session (
id BIGINT PRIMARY KEY,
session_token VARCHAR(128) NOT NULL,
role_code VARCHAR(64) NOT NULL,
auth_level VARCHAR(32) NOT NULL,
issued_at DATETIME(3) NOT NULL,
last_active_at DATETIME(3) NOT NULL,
expires_at DATETIME(3) NOT NULL,
logout_at DATETIME(3) NULL,
create_time DATETIME(3) NOT NULL,
update_time DATETIME(3) NOT NULL,
UNIQUE KEY uk_tms_auth_session_token (session_token),
KEY idx_tms_auth_session_role_code (role_code)
);
CREATE TABLE IF NOT EXISTS tms_auth_challenge (
id BIGINT PRIMARY KEY,
challenge_id VARCHAR(128) NOT NULL,
role_code VARCHAR(64) NOT NULL,
purpose VARCHAR(32) NOT NULL,
nonce VARCHAR(256) NOT NULL,
expires_at DATETIME(3) NOT NULL,
used TINYINT(1) NOT NULL DEFAULT 0,
create_time DATETIME(3) NOT NULL,
update_time DATETIME(3) NOT NULL,
UNIQUE KEY uk_tms_auth_challenge_challenge_id (challenge_id)
);
CREATE TABLE IF NOT EXISTS tms_auth_audit_log (
id BIGINT PRIMARY KEY,
event_type VARCHAR(64) NOT NULL,
role_code VARCHAR(64) NULL,
operator_role_code VARCHAR(64) NULL,
result VARCHAR(32) NOT NULL,
detail_json JSON NULL,
remote_ip VARCHAR(64) NULL,
create_time DATETIME(3) NOT NULL,
update_time DATETIME(3) NOT NULL,
KEY idx_tms_auth_audit_log_event_type (event_type),
KEY idx_tms_auth_audit_log_role_code (role_code)
);
INSERT IGNORE INTO tms_role_account (
id,
role_code,
display_name,
required_ukey_count,
password_hash,
password_salt,
status,
need_change_password,
failed_count,
locked_until,
last_login_at,
last_active_at,
create_time,
update_time
)
VALUES
(
11001,
'SUPER_ADMIN',
'超级管理员',
3,
'Viuew8WE+qziUZwGGU0x25cUpzsTF+QvQFU0uhb+yVA=',
'init-super-admin-salt-20260325',
'UNENABLED',
1,
0,
NULL,
NULL,
NULL,
CURRENT_TIMESTAMP(3),
CURRENT_TIMESTAMP(3)
),
(
11002,
'KEY_ADMIN',
'密钥管理员',
2,
'/xqmDy3A/q9X5p7XWznIPSBabQ6bxTLAPgtHQ6Be33c=',
'init-key-admin-salt-20260325',
'ACTIVE',
1,
0,
NULL,
NULL,
NULL,
CURRENT_TIMESTAMP(3),
CURRENT_TIMESTAMP(3)
),
(
11003,
'AUDIT_ADMIN',
'审计管理员',
1,
'j/J1LKyUOzVEJfbbHe2dwKBKNdabRk502olk/LWIwhg=',
'init-audit-admin-salt-20260325',
'UNENABLED',
1,
0,
NULL,
NULL,
NULL,
CURRENT_TIMESTAMP(3),
CURRENT_TIMESTAMP(3)
),
(
11004,
'OPS_ADMIN',
'运维管理员',
1,
'2e9F4aCgsDb+AuD1LrBEqEg8yjy3ODo27UHtx06vj/A=',
'init-ops-admin-salt-20260325',
'UNENABLED',
1,
0,
NULL,
NULL,
NULL,
CURRENT_TIMESTAMP(3),
CURRENT_TIMESTAMP(3)
);
+79
View File
@@ -0,0 +1,79 @@
# TMS 初始化 SQL 执行说明
当前推荐使用 [tms_schema_full.sql](/Users/waner/Work/CISD/文档/tms-framework/docs/sql/tms_schema_full.sql)。
这份脚本已经整理成“最终库结构初始化版”,适合空库直接初始化,不再按 migration 逐步执行 `ALTER / RENAME`。
如果你在 TiDB 客户端里一次性执行整份文件,出现下面这类报错:
```text
SQL Error [8130] [HY000]: client has multi-statement capability disabled
```
这不是 SQL 内容本身错误,而是“客户端把整份脚本作为一次多语句请求发送”,而当前 TiDB 连接未开启 multi-statement。
## 推荐做法
### 方式一:用脚本执行模式运行最终初始化脚本
优先用客户端的 `Run Script` / `Execute Script` 模式执行:
- [tms_schema_full.sql](/Users/waner/Work/CISD/文档/tms-framework/docs/sql/tms_schema_full.sql)
### 方式二:用“脚本执行”而不是“单次执行”
如果你的客户端支持 `Run Script` / `Execute Script`,优先用脚本模式。
脚本模式通常会把每条语句分开发送,而不是整份文件一次发给数据库。
### 方式三:使用命令行客户端导入
例如:
```bash
mysql -h <host> -P <port> -u <user> -p tms < /path/to/tms_schema_full.sql
```
或者先登录后:
```sql
source /path/to/tms_schema_full.sql;
```
### 方式四:实在只能单条执行时
先执行:
```sql
CREATE DATABASE IF NOT EXISTS tms
DEFAULT CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
USE tms;
```
然后把 [tms_schema_full.sql](/Users/waner/Work/CISD/文档/tms-framework/docs/sql/tms_schema_full.sql) 中的语句按分号逐条执行。
## 不推荐但可选
你也可以按报错提示开启:
```sql
SET GLOBAL tidb_multi_statement_mode='ON';
```
但这会影响实例级行为,有安全风险,不建议为了初始化脚本临时这么做,除非你明确知道数据库侧的风险边界并且由 DBA 允许。
## 当前默认账号种子
脚本已经包含 4 个固定角色账号:
- `SUPER_ADMIN`
- `KEY_ADMIN`
- `AUDIT_ADMIN`
- `OPS_ADMIN`
默认口令均为 `12345678`,其中:
- `KEY_ADMIN` 默认 `ACTIVE`
- 其他角色默认 `UNENABLED`
- 所有角色 `need_change_password = 1`
+2427
View File
File diff suppressed because it is too large Load Diff
+10
View File
@@ -105,6 +105,16 @@
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<configuration> <configuration>
<parameters>true</parameters> <parameters>true</parameters>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</path>
<path>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</path>
</annotationProcessorPaths>
</configuration> </configuration>
</plugin> </plugin>
<plugin> <plugin>
@@ -0,0 +1,17 @@
#!/bin/bash
set -euo pipefail
STANDARD_HOME_DIR="${STANDARD_HOME_DIR:?STANDARD_HOME_DIR is required}"
STANDARD_RUN_USER="${STANDARD_RUN_USER:?STANDARD_RUN_USER is required}"
if [ -x "${STANDARD_HOME_DIR}/cmsp/stop_cmsp.sh" ]; then
su - "${STANDARD_RUN_USER}" -s /bin/bash "${STANDARD_HOME_DIR}/cmsp/stop_cmsp.sh" || true
else
pkill -f "${STANDARD_HOME_DIR}/cmsp" >/dev/null 2>&1 || true
fi
if [ -x "${STANDARD_HOME_DIR}/cmtp/stop_cmtp.sh" ]; then
su - "${STANDARD_RUN_USER}" -s /bin/bash "${STANDARD_HOME_DIR}/cmtp/stop_cmtp.sh" || true
else
pkill -f "${STANDARD_HOME_DIR}/cmtp" >/dev/null 2>&1 || true
fi
@@ -0,0 +1,6 @@
#!/bin/bash
set -euo pipefail
if pgrep -f "/usr/local/nginx/sbin/nginx" >/dev/null; then
/usr/local/nginx/sbin/nginx -s stop || pkill -f "/usr/local/nginx/sbin/nginx" >/dev/null 2>&1 || true
fi
@@ -0,0 +1,4 @@
#!/bin/bash
set -euo pipefail
systemctl stop rabbitmq-server >/dev/null 2>&1 || true
+15 -3
View File
@@ -22,7 +22,7 @@ PID_FILE="${PID_FILE:-${RUN_DIR}/${APP_NAME}.pid}"
OUT_LOG="${OUT_LOG:-${LOG_DIR}/${APP_NAME}.out.log}" OUT_LOG="${OUT_LOG:-${LOG_DIR}/${APP_NAME}.out.log}"
ERR_LOG="${ERR_LOG:-${LOG_DIR}/${APP_NAME}.err.log}" ERR_LOG="${ERR_LOG:-${LOG_DIR}/${APP_NAME}.err.log}"
SPRING_PROFILES_ACTIVE="${SPRING_PROFILES_ACTIVE:-prod}" SPRING_PROFILES_ACTIVE="${SPRING_PROFILES_ACTIVE:-}"
JAVA_BIN="${JAVA_BIN:-java}" JAVA_BIN="${JAVA_BIN:-java}"
JAVA_OPTS="${JAVA_OPTS:- -Xms512m -Xmx1024m}" JAVA_OPTS="${JAVA_OPTS:- -Xms512m -Xmx1024m}"
ALLOW_MISSING_EXTERNAL_CONFIG="${ALLOW_MISSING_EXTERNAL_CONFIG:-false}" ALLOW_MISSING_EXTERNAL_CONFIG="${ALLOW_MISSING_EXTERNAL_CONFIG:-false}"
@@ -87,15 +87,27 @@ start_app() {
echo "[INFO] starting ${APP_NAME}" echo "[INFO] starting ${APP_NAME}"
echo "[INFO] app home: ${APP_HOME}" echo "[INFO] app home: ${APP_HOME}"
echo "[INFO] jar path: ${JAR_PATH}" echo "[INFO] jar path: ${JAR_PATH}"
echo "[INFO] profile: ${SPRING_PROFILES_ACTIVE}" if [[ -n "${SPRING_PROFILES_ACTIVE}" ]]; then
echo "[INFO] profile override: ${SPRING_PROFILES_ACTIVE}"
else
echo "[INFO] profile override: <none>"
fi
echo "[INFO] external config dir: ${CONF_DIR}" echo "[INFO] external config dir: ${CONF_DIR}"
if [[ -n "${SPRING_PROFILES_ACTIVE}" ]]; then
# shellcheck disable=SC2086 # shellcheck disable=SC2086
nohup "${JAVA_BIN}" ${JAVA_OPTS} \ nohup "${JAVA_BIN}" ${JAVA_OPTS} \
-jar "${JAR_PATH}" \ -jar "${JAR_PATH}" \
--spring.profiles.active="${SPRING_PROFILES_ACTIVE}" \ --spring.profiles.active="${SPRING_PROFILES_ACTIVE}" \
--spring.config.additional-location="${cfg_location}" \ --spring.config.additional-location="${cfg_location}" \
>"${OUT_LOG}" 2>"${ERR_LOG}" & >"${OUT_LOG}" 2>"${ERR_LOG}" &
else
# shellcheck disable=SC2086
nohup "${JAVA_BIN}" ${JAVA_OPTS} \
-jar "${JAR_PATH}" \
--spring.config.additional-location="${cfg_location}" \
>"${OUT_LOG}" 2>"${ERR_LOG}" &
fi
local pid="$!" local pid="$!"
echo "${pid}" > "${PID_FILE}" echo "${pid}" > "${PID_FILE}"
@@ -160,7 +172,7 @@ Environment variables:
CONF_DIR default: <app_home>/config CONF_DIR default: <app_home>/config
RUN_DIR default: <app_home>/run RUN_DIR default: <app_home>/run
LOG_DIR default: <app_home>/logs LOG_DIR default: <app_home>/logs
SPRING_PROFILES_ACTIVE default: prod SPRING_PROFILES_ACTIVE optional: override Spring profile
JAVA_BIN default: java JAVA_BIN default: java
JAVA_OPTS default: -Xms512m -Xmx1024m JAVA_OPTS default: -Xms512m -Xmx1024m
ALLOW_MISSING_EXTERNAL_CONFIG default: false ALLOW_MISSING_EXTERNAL_CONFIG default: false
@@ -1,5 +1,7 @@
package com.cisd.tms.common.config; package com.cisd.tms.common.config;
import com.cisd.tms.common.config.properties.TmsCorsProperties;
import com.cisd.tms.modules.auth.security.InternalAuthorizationInterceptor;
import com.cisd.tms.security.internal.InternalApiAuthInterceptor; import com.cisd.tms.security.internal.InternalApiAuthInterceptor;
import com.cisd.tms.security.openapi.OpenApiSignAuthInterceptor; import com.cisd.tms.security.openapi.OpenApiSignAuthInterceptor;
import com.cisd.tms.common.util.TraceIdUtil; import com.cisd.tms.common.util.TraceIdUtil;
@@ -12,6 +14,7 @@ import org.slf4j.MDC;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@@ -19,14 +22,20 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
public class WebMvcConfig implements WebMvcConfigurer { public class WebMvcConfig implements WebMvcConfigurer {
private final InternalApiAuthInterceptor internalApiAuthInterceptor; private final InternalApiAuthInterceptor internalApiAuthInterceptor;
private final InternalAuthorizationInterceptor internalAuthorizationInterceptor;
private final OpenApiSignAuthInterceptor openApiSignAuthInterceptor; private final OpenApiSignAuthInterceptor openApiSignAuthInterceptor;
private final TmsCorsProperties corsProperties;
public WebMvcConfig( public WebMvcConfig(
InternalApiAuthInterceptor internalApiAuthInterceptor, InternalApiAuthInterceptor internalApiAuthInterceptor,
OpenApiSignAuthInterceptor openApiSignAuthInterceptor InternalAuthorizationInterceptor internalAuthorizationInterceptor,
OpenApiSignAuthInterceptor openApiSignAuthInterceptor,
TmsCorsProperties corsProperties
) { ) {
this.internalApiAuthInterceptor = internalApiAuthInterceptor; this.internalApiAuthInterceptor = internalApiAuthInterceptor;
this.internalAuthorizationInterceptor = internalAuthorizationInterceptor;
this.openApiSignAuthInterceptor = openApiSignAuthInterceptor; this.openApiSignAuthInterceptor = openApiSignAuthInterceptor;
this.corsProperties = corsProperties;
} }
@Bean @Bean
@@ -56,10 +65,31 @@ public class WebMvcConfig implements WebMvcConfigurer {
.addPathPatterns("/api/**") .addPathPatterns("/api/**")
.excludePathPatterns( .excludePathPatterns(
"/api/v1/device/status", "/api/v1/device/status",
"/api/v1/auth/login" "/api/v1/auth/login",
"/api/v1/auth/password-login",
"/api/v1/auth/ukey-login",
"/api/v1/auth/ukey-login/randoms"
); );
registry.addInterceptor(internalAuthorizationInterceptor)
.addPathPatterns("/api/**");
registry.addInterceptor(openApiSignAuthInterceptor) registry.addInterceptor(openApiSignAuthInterceptor)
.addPathPatterns("/openapi/**"); .addPathPatterns("/openapi/**");
} }
@Override
public void addCorsMappings(CorsRegistry registry) {
if (!corsProperties.isEnabled() || corsProperties.getAllowedOriginPatterns().isEmpty()) {
return;
}
registry.addMapping("/**")
.allowedOriginPatterns(corsProperties.getAllowedOriginPatterns().toArray(String[]::new))
.allowedMethods(corsProperties.getAllowedMethods().toArray(String[]::new))
.allowedHeaders(corsProperties.getAllowedHeaders().toArray(String[]::new))
.exposedHeaders(corsProperties.getExposedHeaders().toArray(String[]::new))
.allowCredentials(corsProperties.isAllowCredentials())
.maxAge(corsProperties.getMaxAgeSeconds());
}
} }
@@ -0,0 +1,94 @@
package com.cisd.tms.common.config.properties;
import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "tms.web.cors")
public class TmsCorsProperties {
private boolean enabled = true;
private List<String> allowedOriginPatterns = new ArrayList<>(List.of(
"http://localhost:*",
"http://127.0.0.1:*"
));
private List<String> allowedMethods = new ArrayList<>(List.of(
"GET",
"POST",
"PUT",
"DELETE",
"OPTIONS"
));
private List<String> allowedHeaders = new ArrayList<>(List.of(
"Authorization",
"Content-Type",
"X-Requested-With",
"X-Internal-Token",
"X-App-Id",
"X-Timestamp",
"X-Nonce",
"X-Signature",
"X-Trace-Id"
));
private List<String> exposedHeaders = new ArrayList<>(List.of(
"X-Trace-Id"
));
private boolean allowCredentials = true;
private long maxAgeSeconds = 3600;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public List<String> getAllowedOriginPatterns() {
return allowedOriginPatterns;
}
public void setAllowedOriginPatterns(List<String> allowedOriginPatterns) {
this.allowedOriginPatterns = allowedOriginPatterns;
}
public List<String> getAllowedMethods() {
return allowedMethods;
}
public void setAllowedMethods(List<String> allowedMethods) {
this.allowedMethods = allowedMethods;
}
public List<String> getAllowedHeaders() {
return allowedHeaders;
}
public void setAllowedHeaders(List<String> allowedHeaders) {
this.allowedHeaders = allowedHeaders;
}
public List<String> getExposedHeaders() {
return exposedHeaders;
}
public void setExposedHeaders(List<String> exposedHeaders) {
this.exposedHeaders = exposedHeaders;
}
public boolean isAllowCredentials() {
return allowCredentials;
}
public void setAllowCredentials(boolean allowCredentials) {
this.allowCredentials = allowCredentials;
}
public long getMaxAgeSeconds() {
return maxAgeSeconds;
}
public void setMaxAgeSeconds(long maxAgeSeconds) {
this.maxAgeSeconds = maxAgeSeconds;
}
}
@@ -8,6 +8,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
public class TmsSecurityProperties { public class TmsSecurityProperties {
private String internalToken = "change-me-internal-token"; private String internalToken = "change-me-internal-token";
private final InternalAuth internalAuth = new InternalAuth();
private final Openapi openapi = new Openapi(); private final Openapi openapi = new Openapi();
public String getInternalToken() { public String getInternalToken() {
@@ -18,10 +19,53 @@ public class TmsSecurityProperties {
this.internalToken = internalToken; this.internalToken = internalToken;
} }
public InternalAuth getInternalAuth() {
return internalAuth;
}
public Openapi getOpenapi() { public Openapi getOpenapi() {
return openapi; return openapi;
} }
public static class InternalAuth {
private boolean enabled = true;
private String debugRoleCode = "KEY_ADMIN";
private String debugAuthLevel = "FULL";
private String debugSessionToken = "DEBUG-BYPASS";
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getDebugRoleCode() {
return debugRoleCode;
}
public void setDebugRoleCode(String debugRoleCode) {
this.debugRoleCode = debugRoleCode;
}
public String getDebugAuthLevel() {
return debugAuthLevel;
}
public void setDebugAuthLevel(String debugAuthLevel) {
this.debugAuthLevel = debugAuthLevel;
}
public String getDebugSessionToken() {
return debugSessionToken;
}
public void setDebugSessionToken(String debugSessionToken) {
this.debugSessionToken = debugSessionToken;
}
}
public static class Openapi { public static class Openapi {
private long timestampSkewSeconds = 300; private long timestampSkewSeconds = 300;
private Map<String, String> clients = new HashMap<>(); private Map<String, String> clients = new HashMap<>();
@@ -7,6 +7,7 @@ import com.sun.jna.Memory;
import com.sun.jna.Native; import com.sun.jna.Native;
import com.sun.jna.Pointer; import com.sun.jna.Pointer;
import com.sun.jna.ptr.PointerByReference; import com.sun.jna.ptr.PointerByReference;
import jakarta.annotation.PreDestroy;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -33,6 +34,7 @@ public class PcieSessionTemplate {
private final Object lock = new Object(); private final Object lock = new Object();
private volatile PcieNativeLibrary nativeLibrary; private volatile PcieNativeLibrary nativeLibrary;
private volatile Pointer sharedDeviceHandle;
public PcieSessionTemplate(CryptoCardProperties properties, PcieErrorMapper errorMapper) { public PcieSessionTemplate(CryptoCardProperties properties, PcieErrorMapper errorMapper) {
this.properties = properties; this.properties = properties;
@@ -42,32 +44,22 @@ public class PcieSessionTemplate {
public <T> T withDevice(String apiName, DeviceCallback<T> callback) { public <T> T withDevice(String apiName, DeviceCallback<T> callback) {
synchronized (lock) { synchronized (lock) {
PcieNativeLibrary lib = ensureLibraryLoaded(); PcieNativeLibrary lib = ensureLibraryLoaded();
Pointer device = ensureSharedDeviceOpened(apiName, lib);
PointerByReference deviceRef = new PointerByReference();
int openRc = lib.SDF_OpenDevice(deviceRef);
ensureSuccess(apiName + ":SDF_OpenDevice", openRc);
Pointer device = deviceRef.getValue();
try {
return callback.apply(lib, device); return callback.apply(lib, device);
} finally {
int closeRc = lib.SDF_CloseDevice(device);
if (closeRc != 0) {
log.warn("{} close device failed: {}", apiName, errorMapper.toMessage(closeRc));
}
}
} }
} }
public <T> T withSession(String apiName, SessionCallback<T> callback) { public <T> T withSession(String apiName, SessionCallback<T> callback) {
return withDevice(apiName, (lib, deviceHandle) -> { synchronized (lock) {
PointerByReference sessionRef = new PointerByReference(); PcieNativeLibrary lib = ensureLibraryLoaded();
Pointer ph = new Memory(8L); Pointer deviceHandle = ensureSharedDeviceOpened(apiName, lib);
sessionRef.setValue(ph); Pointer session = tryOpenSession(apiName, lib, deviceHandle);
int openSessionRc = lib.SDF_OpenSession(deviceHandle, sessionRef); if (session == null) {
ensureSuccess(apiName + ":SDF_OpenSession", openSessionRc); resetSharedDevice(lib);
deviceHandle = ensureSharedDeviceOpened(apiName, lib);
session = openSessionOrThrow(apiName, lib, deviceHandle);
}
Pointer session = sessionRef.getValue();
try { try {
return callback.apply(lib, deviceHandle, session); return callback.apply(lib, deviceHandle, session);
} finally { } finally {
@@ -76,7 +68,7 @@ public class PcieSessionTemplate {
log.warn("{} close session failed: {}", apiName, errorMapper.toMessage(closeSessionRc)); log.warn("{} close session failed: {}", apiName, errorMapper.toMessage(closeSessionRc));
} }
} }
}); }
} }
public void ensureSuccess(String apiName, int retCode) { public void ensureSuccess(String apiName, int retCode) {
@@ -85,6 +77,13 @@ public class PcieSessionTemplate {
} }
} }
@PreDestroy
void closeSharedDevice() {
synchronized (lock) {
closeSharedDevice(libOrNull());
}
}
private PcieNativeLibrary ensureLibraryLoaded() { private PcieNativeLibrary ensureLibraryLoaded() {
PcieNativeLibrary cached = nativeLibrary; PcieNativeLibrary cached = nativeLibrary;
if (cached != null) { if (cached != null) {
@@ -116,4 +115,55 @@ public class PcieSessionTemplate {
return nativeLibrary; return nativeLibrary;
} }
} }
private Pointer ensureSharedDeviceOpened(String apiName, PcieNativeLibrary lib) {
Pointer cached = sharedDeviceHandle;
if (cached != null) {
return cached;
}
PointerByReference deviceRef = new PointerByReference();
int openRc = lib.SDF_OpenDevice(deviceRef);
ensureSuccess(apiName + ":SDF_OpenDevice", openRc);
sharedDeviceHandle = deviceRef.getValue();
return sharedDeviceHandle;
}
private Pointer tryOpenSession(String apiName, PcieNativeLibrary lib, Pointer deviceHandle) {
PointerByReference sessionRef = new PointerByReference();
int openSessionRc = lib.SDF_OpenSession(deviceHandle, sessionRef);
if (openSessionRc == 0) {
return sessionRef.getValue();
}
log.warn("{} open session failed, will reset shared device and retry once: {}", apiName, errorMapper.toMessage(openSessionRc));
return null;
}
private Pointer openSessionOrThrow(String apiName, PcieNativeLibrary lib, Pointer deviceHandle) {
PointerByReference sessionRef = new PointerByReference();
int openSessionRc = lib.SDF_OpenSession(deviceHandle, sessionRef);
ensureSuccess(apiName + ":SDF_OpenSession", openSessionRc);
return sessionRef.getValue();
}
private void resetSharedDevice(PcieNativeLibrary lib) {
closeSharedDevice(lib);
}
private void closeSharedDevice(PcieNativeLibrary lib) {
Pointer device = sharedDeviceHandle;
if (device == null || lib == null) {
return;
}
sharedDeviceHandle = null;
int closeRc = lib.SDF_CloseDevice(device);
if (closeRc != 0) {
log.warn("close shared device failed: {}", errorMapper.toMessage(closeRc));
}
}
private PcieNativeLibrary libOrNull() {
return nativeLibrary;
}
} }
@@ -154,7 +154,7 @@ public interface PcieNativeLibrary extends Library {
int SDFE_InitIdentify(Pointer hSessionHandle, byte[] oldPin, int oldPinLen, byte[] newPin, int newPinLen); int SDFE_InitIdentify(Pointer hSessionHandle, byte[] oldPin, int oldPinLen, byte[] newPin, int newPinLen);
int SDFE_DeviceStatusGet(Pointer hSessionHandle, Pointer pDeviceStatus); int SDFE_DeviceStatusGet(Pointer hSessionHandle, SdfeDeviceStatus deviceStatus);
int SDFE_DeviceSelfTest(Pointer hSessionHandle); int SDFE_DeviceSelfTest(Pointer hSessionHandle);
@@ -245,15 +245,15 @@ public interface PcieNativeLibrary extends Library {
int SDFE_UnlockUkey(Pointer hSessionHandle, byte[] adminPin, int adminPinLen, byte[] userPin, int userPinLen); int SDFE_UnlockUkey(Pointer hSessionHandle, byte[] adminPin, int adminPinLen, byte[] userPin, int userPinLen);
int SDFE_ReadLMKComponent(Pointer hSessionHandle, byte[] ukeyPin, int ukeyPinLen); int SDFE_ReadUkeyLMKComponent(Pointer hSessionHandle, byte[] ukeyPin, int ukeyPinLen);
int SDFE_ReadUkeyIKComponent(Pointer hSessionHandle, int keyType, byte[] ukeyPin, int ukeyPinLen); int SDFE_ReadUkeyIKComponent(Pointer hSessionHandle, int keyType, byte[] ukeyPin, int ukeyPinLen);
int SDFE_GenerateCertificate(Pointer hSessionHandle, byte[] ukeyPin, int ukeyPinLen); int SDFE_GenerateUkeyCertificate(Pointer hSessionHandle, byte[] ukeyPin, int ukeyPinLen);
int SDFE_LoginoutAdmin(Pointer hSessionHandle, byte adminIndex); int SDFE_LoginoutUkey(Pointer hSessionHandle, byte adminIndex);
int SDFE_LoginStatus(Pointer hSessionHandle, SdfeLoginState loginState); int SDFE_UkeyLoginStatus(Pointer hSessionHandle, SdfeLoginState loginState);
int SDFE_HmacInit(Pointer hSessionHandle, int algId, byte[] key, int keyLen); int SDFE_HmacInit(Pointer hSessionHandle, int algId, byte[] key, int keyLen);
@@ -3,6 +3,7 @@ package com.cisd.tms.integration.crypto.pcie.jna;
import com.sun.jna.Structure; import com.sun.jna.Structure;
@Structure.FieldOrder({ @Structure.FieldOrder({
"type",
"level", "level",
"devCount", "devCount",
"rsaKeyCount", "rsaKeyCount",
@@ -13,6 +14,7 @@ import com.sun.jna.Structure;
}) })
public class SdfeDeviceConf extends Structure { public class SdfeDeviceConf extends Structure {
public int type;
public int level; public int level;
public int devCount; public int devCount;
public int rsaKeyCount; public int rsaKeyCount;
@@ -0,0 +1,51 @@
package com.cisd.tms.integration.crypto.pcie.jna;
import com.sun.jna.Structure;
@Structure.FieldOrder({
"fsmState",
"dkeyState",
"errorCode",
"version",
"commit",
"frequency",
"pcieSpeed",
"pcieWidth",
"chipTemp",
"chipVoltage",
"boardTemp",
"boardVoltage",
"boardCurrent",
"chipTempmin",
"chipTempmax",
"boardVolmin",
"boardVolmax",
"fsTotalKb",
"fsFreeKb"
})
public class SdfeDeviceStatus extends Structure {
public int fsmState;
public int dkeyState;
public int errorCode;
public int version;
public int commit;
public int frequency;
public int pcieSpeed;
public int pcieWidth;
public float chipTemp;
public float chipVoltage;
public float boardTemp;
public float boardVoltage;
public float boardCurrent;
public float chipTempmin;
public float chipTempmax;
public float boardVolmin;
public float boardVolmax;
public int fsTotalKb;
public int fsFreeKb;
public SdfeDeviceStatus() {
super(ALIGN_NONE);
}
}
@@ -2,6 +2,7 @@ package com.cisd.tms.integration.crypto.pcie.model;
public class DeviceConfResult { public class DeviceConfResult {
private int type;
private int level; private int level;
private int devCount; private int devCount;
private int rsaKeyCount; private int rsaKeyCount;
@@ -10,6 +11,14 @@ public class DeviceConfResult {
private int sessKeyCount; private int sessKeyCount;
private int fileSysCapacity; private int fileSysCapacity;
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public int getLevel() { public int getLevel() {
return level; return level;
} }
@@ -12,6 +12,13 @@ public class DeviceInfoResult {
private int symAlgAbility; private int symAlgAbility;
private int hashAlgAbility; private int hashAlgAbility;
private int bufferSize; private int bufferSize;
private String deviceVersionText;
private String standardVersionText;
private String asymmetricAlgorithmsText;
private String asymmetricKeyBitsText;
private String symmetricAlgorithmsText;
private String hashAlgorithmsText;
private String bufferSizeText;
public String getIssuerName() { public String getIssuerName() {
return issuerName; return issuerName;
@@ -92,4 +99,60 @@ public class DeviceInfoResult {
public void setBufferSize(int bufferSize) { public void setBufferSize(int bufferSize) {
this.bufferSize = bufferSize; this.bufferSize = bufferSize;
} }
public String getDeviceVersionText() {
return deviceVersionText;
}
public void setDeviceVersionText(String deviceVersionText) {
this.deviceVersionText = deviceVersionText;
}
public String getStandardVersionText() {
return standardVersionText;
}
public void setStandardVersionText(String standardVersionText) {
this.standardVersionText = standardVersionText;
}
public String getAsymmetricAlgorithmsText() {
return asymmetricAlgorithmsText;
}
public void setAsymmetricAlgorithmsText(String asymmetricAlgorithmsText) {
this.asymmetricAlgorithmsText = asymmetricAlgorithmsText;
}
public String getAsymmetricKeyBitsText() {
return asymmetricKeyBitsText;
}
public void setAsymmetricKeyBitsText(String asymmetricKeyBitsText) {
this.asymmetricKeyBitsText = asymmetricKeyBitsText;
}
public String getSymmetricAlgorithmsText() {
return symmetricAlgorithmsText;
}
public void setSymmetricAlgorithmsText(String symmetricAlgorithmsText) {
this.symmetricAlgorithmsText = symmetricAlgorithmsText;
}
public String getHashAlgorithmsText() {
return hashAlgorithmsText;
}
public void setHashAlgorithmsText(String hashAlgorithmsText) {
this.hashAlgorithmsText = hashAlgorithmsText;
}
public String getBufferSizeText() {
return bufferSizeText;
}
public void setBufferSizeText(String bufferSizeText) {
this.bufferSizeText = bufferSizeText;
}
} }
@@ -1,9 +1,30 @@
package com.cisd.tms.integration.crypto.pcie.model; package com.cisd.tms.integration.crypto.pcie.model;
import com.fasterxml.jackson.annotation.JsonIgnore;
public class DeviceStatusResult { public class DeviceStatusResult {
@JsonIgnore
private byte[] statusData; private byte[] statusData;
private int length; private int length;
private String statusHex;
private int fsmState;
private String fsmStateText;
private int commit;
private String commitHex;
private int frequencyMhz;
private String frequencyText;
private int pcieSpeed;
private int pcieWidth;
private String pcieInfoText;
private double boardTempC;
private String boardTempText;
private double boardVoltageV;
private String boardVoltageText;
private int fsTotalKb;
private String fsTotalText;
private int fsFreeKb;
private String fsFreeText;
public byte[] getStatusData() { public byte[] getStatusData() {
return statusData; return statusData;
@@ -20,4 +41,148 @@ public class DeviceStatusResult {
public void setLength(int length) { public void setLength(int length) {
this.length = length; this.length = length;
} }
public String getStatusHex() {
return statusHex;
}
public void setStatusHex(String statusHex) {
this.statusHex = statusHex;
}
public int getFsmState() {
return fsmState;
}
public void setFsmState(int fsmState) {
this.fsmState = fsmState;
}
public String getFsmStateText() {
return fsmStateText;
}
public void setFsmStateText(String fsmStateText) {
this.fsmStateText = fsmStateText;
}
public int getCommit() {
return commit;
}
public void setCommit(int commit) {
this.commit = commit;
}
public String getCommitHex() {
return commitHex;
}
public void setCommitHex(String commitHex) {
this.commitHex = commitHex;
}
public int getFrequencyMhz() {
return frequencyMhz;
}
public void setFrequencyMhz(int frequencyMhz) {
this.frequencyMhz = frequencyMhz;
}
public String getFrequencyText() {
return frequencyText;
}
public void setFrequencyText(String frequencyText) {
this.frequencyText = frequencyText;
}
public int getPcieSpeed() {
return pcieSpeed;
}
public void setPcieSpeed(int pcieSpeed) {
this.pcieSpeed = pcieSpeed;
}
public int getPcieWidth() {
return pcieWidth;
}
public void setPcieWidth(int pcieWidth) {
this.pcieWidth = pcieWidth;
}
public String getPcieInfoText() {
return pcieInfoText;
}
public void setPcieInfoText(String pcieInfoText) {
this.pcieInfoText = pcieInfoText;
}
public double getBoardTempC() {
return boardTempC;
}
public void setBoardTempC(double boardTempC) {
this.boardTempC = boardTempC;
}
public String getBoardTempText() {
return boardTempText;
}
public void setBoardTempText(String boardTempText) {
this.boardTempText = boardTempText;
}
public double getBoardVoltageV() {
return boardVoltageV;
}
public void setBoardVoltageV(double boardVoltageV) {
this.boardVoltageV = boardVoltageV;
}
public String getBoardVoltageText() {
return boardVoltageText;
}
public void setBoardVoltageText(String boardVoltageText) {
this.boardVoltageText = boardVoltageText;
}
public int getFsTotalKb() {
return fsTotalKb;
}
public void setFsTotalKb(int fsTotalKb) {
this.fsTotalKb = fsTotalKb;
}
public String getFsTotalText() {
return fsTotalText;
}
public void setFsTotalText(String fsTotalText) {
this.fsTotalText = fsTotalText;
}
public int getFsFreeKb() {
return fsFreeKb;
}
public void setFsFreeKb(int fsFreeKb) {
this.fsFreeKb = fsFreeKb;
}
public String getFsFreeText() {
return fsFreeText;
}
public void setFsFreeText(String fsFreeText) {
this.fsFreeText = fsFreeText;
}
} }
@@ -16,6 +16,7 @@ import com.cisd.tms.integration.crypto.pcie.jna.RsaRefPrivateKey;
import com.cisd.tms.integration.crypto.pcie.jna.RsaRefPublicKey; import com.cisd.tms.integration.crypto.pcie.jna.RsaRefPublicKey;
import com.cisd.tms.integration.crypto.pcie.jna.SdfDeviceInfo; import com.cisd.tms.integration.crypto.pcie.jna.SdfDeviceInfo;
import com.cisd.tms.integration.crypto.pcie.jna.SdfeDeviceConf; import com.cisd.tms.integration.crypto.pcie.jna.SdfeDeviceConf;
import com.cisd.tms.integration.crypto.pcie.jna.SdfeDeviceStatus;
import com.cisd.tms.integration.crypto.pcie.jna.SdfeKekComponent; import com.cisd.tms.integration.crypto.pcie.jna.SdfeKekComponent;
import com.cisd.tms.integration.crypto.pcie.jna.SdfeLmkComponent; import com.cisd.tms.integration.crypto.pcie.jna.SdfeLmkComponent;
import com.cisd.tms.integration.crypto.pcie.jna.SdfeLoginState; import com.cisd.tms.integration.crypto.pcie.jna.SdfeLoginState;
@@ -129,6 +130,7 @@ public class JnaPcieCryptoService implements PcieCryptoService {
conf.read(); conf.read();
DeviceConfResult result = new DeviceConfResult(); DeviceConfResult result = new DeviceConfResult();
result.setType(conf.type);
result.setLevel(conf.level); result.setLevel(conf.level);
result.setDevCount(conf.devCount); result.setDevCount(conf.devCount);
result.setRsaKeyCount(conf.rsaKeyCount); result.setRsaKeyCount(conf.rsaKeyCount);
@@ -158,6 +160,7 @@ public class JnaPcieCryptoService implements PcieCryptoService {
result.setSymAlgAbility(info.symAlgAbility); result.setSymAlgAbility(info.symAlgAbility);
result.setHashAlgAbility(info.hashAlgAbility); result.setHashAlgAbility(info.hashAlgAbility);
result.setBufferSize(info.bufferSize); result.setBufferSize(info.bufferSize);
PcieDeviceInfoFormatter.enrich(result);
return result; return result;
}); });
} }
@@ -192,6 +195,40 @@ public class JnaPcieCryptoService implements PcieCryptoService {
} }
} }
@Override
public byte[] initializeMasterKey(byte[] oldPin, byte[] newPin) {
byte[] oldPinValue = requireNonEmptyBytes("oldPin", oldPin);
byte[] newPinValue = requireNonEmptyBytes("newPin", newPin);
try {
return sessionTemplate.withSession("SDFE_InitializeMasterKey", (lib, deviceHandle, sessionHandle) -> {
sessionTemplate.ensureSuccess(
"SDFE_InitIdentify",
lib.SDFE_InitIdentify(sessionHandle, oldPinValue, oldPinValue.length, newPinValue, newPinValue.length)
);
sessionTemplate.ensureSuccess("SDFE_GenerateLMK", lib.SDFE_GenerateLMK(sessionHandle));
sessionTemplate.ensureSuccess("SDFE_LoadLMK", lib.SDFE_LoadLMK(sessionHandle));
sessionTemplate.ensureSuccess("SDFE_GenerateIK", lib.SDFE_GenerateIK(sessionHandle, IK_KEY_TYPE_DEVICE));
sessionTemplate.ensureSuccess("SDFE_GenerateIK", lib.SDFE_GenerateIK(sessionHandle, IK_KEY_TYPE_AUTH));
int retCode = lib.SDFE_CheckLMK(sessionHandle);
if (retCode != 0) {
if ((retCode & 0xFF) == SDFE_INIT_STATUS_LOW_BYTE) {
throw new IllegalStateException("master key initialization failed");
}
sessionTemplate.ensureSuccess("SDFE_CheckLMK", retCode);
}
byte[] mac = new byte[LMK_SEED_MAC_LENGTH];
sessionTemplate.ensureSuccess("SDFE_ExportLMKSeedMAC", lib.SDFE_ExportLMKSeedMAC(sessionHandle, mac));
return Arrays.copyOf(mac, mac.length);
});
} finally {
wipe(oldPinValue);
wipe(newPinValue);
}
}
@Override @Override
public void deviceSelfTest() { public void deviceSelfTest() {
sessionTemplate.withSession("SDFE_DeviceSelfTest", (lib, deviceHandle, sessionHandle) -> { sessionTemplate.withSession("SDFE_DeviceSelfTest", (lib, deviceHandle, sessionHandle) -> {
@@ -202,15 +239,16 @@ public class JnaPcieCryptoService implements PcieCryptoService {
@Override @Override
public DeviceStatusResult getDeviceStatus() { public DeviceStatusResult getDeviceStatus() {
int size = 8;
return sessionTemplate.withSession("SDFE_DeviceStatusGet", (lib, deviceHandle, sessionHandle) -> { return sessionTemplate.withSession("SDFE_DeviceStatusGet", (lib, deviceHandle, sessionHandle) -> {
Memory statusMemory = new Memory(8); SdfeDeviceStatus status = new SdfeDeviceStatus();
sessionTemplate.ensureSuccess("SDFE_DeviceStatusGet", lib.SDFE_DeviceStatusGet(sessionHandle, statusMemory)); sessionTemplate.ensureSuccess("SDFE_DeviceStatusGet", lib.SDFE_DeviceStatusGet(sessionHandle, status));
status.read();
int size = status.size();
DeviceStatusResult result = new DeviceStatusResult(); DeviceStatusResult result = new DeviceStatusResult();
// result.setLength(size); result.setLength(size);
result.setStatusData(statusMemory.getByteArray(0, size)); result.setStatusData(status.getPointer().getByteArray(0, size));
PcieDeviceStatusFormatter.enrich(result, status);
return result; return result;
}); });
} }
@@ -1611,15 +1649,9 @@ public class JnaPcieCryptoService implements PcieCryptoService {
} }
@Override @Override
public BackupDataResult backupLmkEx(int outBufferSize) { public BackupDataResult backupLmkEx() {
int size = requirePositive("outBufferSize", outBufferSize);
return sessionTemplate.withSession("SDFE_BackupLMK_EX", (lib, deviceHandle, sessionHandle) -> { return sessionTemplate.withSession("SDFE_BackupLMK_EX", (lib, deviceHandle, sessionHandle) -> {
SdfeLmkComponent output = new SdfeLmkComponent(); SdfeLmkComponent output = new SdfeLmkComponent();
int structSize = output.size();
if (size < structSize) {
throw new IllegalArgumentException("outBufferSize must be >= " + structSize);
}
sessionTemplate.ensureSuccess("SDFE_BackupLMK_EX", lib.SDFE_BackupLMK_EX(sessionHandle, output)); sessionTemplate.ensureSuccess("SDFE_BackupLMK_EX", lib.SDFE_BackupLMK_EX(sessionHandle, output));
byte[] outputBytes = toBytes(output); byte[] outputBytes = toBytes(output);
return toBackupResult("SDFE_BackupLMK_EX", outputBytes, outputBytes.length); return toBackupResult("SDFE_BackupLMK_EX", outputBytes, outputBytes.length);
@@ -1781,8 +1813,11 @@ public class JnaPcieCryptoService implements PcieCryptoService {
byte[] pin = requireNonEmptyBytes("ukeyPin", ukeyPin); byte[] pin = requireNonEmptyBytes("ukeyPin", ukeyPin);
try { try {
sessionTemplate.withSession("SDFE_ReadLMKComponent", (lib, deviceHandle, sessionHandle) -> { sessionTemplate.withSession("SDFE_ReadUkeyLMKComponent", (lib, deviceHandle, sessionHandle) -> {
sessionTemplate.ensureSuccess("SDFE_ReadLMKComponent", lib.SDFE_ReadLMKComponent(sessionHandle, pin, pin.length)); sessionTemplate.ensureSuccess(
"SDFE_ReadUkeyLMKComponent",
lib.SDFE_ReadUkeyLMKComponent(sessionHandle, pin, pin.length)
);
return null; return null;
}); });
} finally { } finally {
@@ -1813,8 +1848,11 @@ public class JnaPcieCryptoService implements PcieCryptoService {
byte[] pin = requireNonEmptyBytes("ukeyPin", ukeyPin); byte[] pin = requireNonEmptyBytes("ukeyPin", ukeyPin);
try { try {
sessionTemplate.withSession("SDFE_GenerateCertificate", (lib, deviceHandle, sessionHandle) -> { sessionTemplate.withSession("SDFE_GenerateUkeyCertificate", (lib, deviceHandle, sessionHandle) -> {
sessionTemplate.ensureSuccess("SDFE_GenerateCertificate", lib.SDFE_GenerateCertificate(sessionHandle, pin, pin.length)); sessionTemplate.ensureSuccess(
"SDFE_GenerateUkeyCertificate",
lib.SDFE_GenerateUkeyCertificate(sessionHandle, pin, pin.length)
);
return null; return null;
}); });
} finally { } finally {
@@ -1826,17 +1864,17 @@ public class JnaPcieCryptoService implements PcieCryptoService {
public void logoutAdmin(int adminIndex) { public void logoutAdmin(int adminIndex) {
int safeAdminIndex = requireByteRange("adminIndex", adminIndex); int safeAdminIndex = requireByteRange("adminIndex", adminIndex);
sessionTemplate.withSession("SDFE_LoginoutAdmin", (lib, deviceHandle, sessionHandle) -> { sessionTemplate.withSession("SDFE_LoginoutUkey", (lib, deviceHandle, sessionHandle) -> {
sessionTemplate.ensureSuccess("SDFE_LoginoutAdmin", lib.SDFE_LoginoutAdmin(sessionHandle, (byte) safeAdminIndex)); sessionTemplate.ensureSuccess("SDFE_LoginoutUkey", lib.SDFE_LoginoutUkey(sessionHandle, (byte) safeAdminIndex));
return null; return null;
}); });
} }
@Override @Override
public LoginStatusResult loginStatus() { public LoginStatusResult loginStatus() {
return sessionTemplate.withSession("SDFE_LoginStatus", (lib, deviceHandle, sessionHandle) -> { return sessionTemplate.withSession("SDFE_UkeyLoginStatus", (lib, deviceHandle, sessionHandle) -> {
SdfeLoginState state = new SdfeLoginState(); SdfeLoginState state = new SdfeLoginState();
sessionTemplate.ensureSuccess("SDFE_LoginStatus", lib.SDFE_LoginStatus(sessionHandle, state)); sessionTemplate.ensureSuccess("SDFE_UkeyLoginStatus", lib.SDFE_UkeyLoginStatus(sessionHandle, state));
state.read(); state.read();
LoginStatusResult result = new LoginStatusResult(); LoginStatusResult result = new LoginStatusResult();
@@ -2,6 +2,7 @@ package com.cisd.tms.integration.crypto.pcie.service;
import com.cisd.tms.integration.crypto.pcie.PcieKeyState; import com.cisd.tms.integration.crypto.pcie.PcieKeyState;
import com.cisd.tms.integration.crypto.pcie.PcieUserKeyType; import com.cisd.tms.integration.crypto.pcie.PcieUserKeyType;
import com.cisd.tms.integration.crypto.pcie.jna.SdfeDeviceStatus;
import com.cisd.tms.integration.crypto.pcie.model.AgreementDataAndKeyEccResult; import com.cisd.tms.integration.crypto.pcie.model.AgreementDataAndKeyEccResult;
import com.cisd.tms.integration.crypto.pcie.model.BackupDataResult; import com.cisd.tms.integration.crypto.pcie.model.BackupDataResult;
import com.cisd.tms.integration.crypto.pcie.model.ChangePrivateKeyPasswordRequest; import com.cisd.tms.integration.crypto.pcie.model.ChangePrivateKeyPasswordRequest;
@@ -100,6 +101,19 @@ public class MockPcieCryptoService implements PcieCryptoService {
requireNonEmptyBytes("newPin", newPin); requireNonEmptyBytes("newPin", newPin);
} }
@Override
public byte[] initializeMasterKey(byte[] oldPin, byte[] newPin) {
initIdentify(oldPin, newPin);
generateLmk();
loadLmk();
generateIk(1);
generateIk(2);
if (!checkLmk()) {
throw new IllegalStateException("master key initialization failed");
}
return exportLmkSeedMac();
}
@Override @Override
public void deviceSelfTest() { public void deviceSelfTest() {
// no-op // no-op
@@ -107,7 +121,7 @@ public class MockPcieCryptoService implements PcieCryptoService {
@Override @Override
public DeviceStatusResult getDeviceStatus() { public DeviceStatusResult getDeviceStatus() {
int size = 8; int size = new SdfeDeviceStatus().size();
DeviceStatusResult result = new DeviceStatusResult(); DeviceStatusResult result = new DeviceStatusResult();
result.setLength(size); result.setLength(size);
result.setStatusData(new byte[size]); result.setStatusData(new byte[size]);
@@ -679,9 +693,8 @@ public class MockPcieCryptoService implements PcieCryptoService {
} }
@Override @Override
public BackupDataResult backupLmkEx(int outBufferSize) { public BackupDataResult backupLmkEx() {
int size = requirePositive("outBufferSize", outBufferSize); return mockBackup(new com.cisd.tms.integration.crypto.pcie.jna.SdfeLmkComponent().size(), 0x11);
return mockBackup(size, 0x11);
} }
@Override @Override
@@ -71,6 +71,8 @@ public interface PcieCryptoService {
void initIdentify(byte[] oldPin, byte[] newPin); void initIdentify(byte[] oldPin, byte[] newPin);
byte[] initializeMasterKey(byte[] oldPin, byte[] newPin);
void deviceSelfTest(); void deviceSelfTest();
DeviceStatusResult getDeviceStatus(); DeviceStatusResult getDeviceStatus();
@@ -209,7 +211,7 @@ public interface PcieCryptoService {
byte[] exportLmkSeedMac(); byte[] exportLmkSeedMac();
BackupDataResult backupLmkEx(int outBufferSize); BackupDataResult backupLmkEx();
void recoverLmkEx(byte[] lmkComponent); void recoverLmkEx(byte[] lmkComponent);
@@ -0,0 +1,146 @@
package com.cisd.tms.integration.crypto.pcie.service;
import com.cisd.tms.integration.crypto.pcie.model.DeviceInfoResult;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
final class PcieDeviceInfoFormatter {
private static final int ASYM_RSA = 0x00010000;
private static final int ASYM_SM2_SIGN = 0x00020200;
private static final int ASYM_SM2_DH = 0x00020400;
private static final int ASYM_SM2_ENC = 0x00020800;
private static final int SYM_SM1 = 0x00000100;
private static final int SYM_SM4 = 0x00000400;
private static final int SYM_AES128 = 0x00008000;
private static final int SYM_AES192 = 0x00010000;
private static final int SYM_AES256 = 0x00020000;
private static final int HASH_SM3 = 0x00000001;
private static final int HASH_SHA256 = 0x00000004;
private static final int ASYM_BITS_256 = 0x00000100;
private static final int ASYM_BITS_2048 = 0x08000000;
private PcieDeviceInfoFormatter() {
}
static void enrich(DeviceInfoResult result) {
if (result == null) {
return;
}
result.setDeviceVersionText(formatDeviceVersion(result.getDeviceVersion()));
result.setStandardVersionText(formatStandardVersion(result.getStandardVersion()));
result.setAsymmetricAlgorithmsText(joinNonEmpty(decodeAsymmetricAlgorithms(result.getAsymAlgAbility0())));
result.setAsymmetricKeyBitsText(joinNonEmpty(decodeAsymmetricKeyBits(result.getAsymAlgAbility1())));
result.setSymmetricAlgorithmsText(joinNonEmpty(decodeSymmetricAlgorithms(result.getSymAlgAbility())));
result.setHashAlgorithmsText(joinNonEmpty(decodeHashAlgorithms(result.getHashAlgAbility())));
result.setBufferSizeText(formatBufferSize(result.getBufferSize()));
}
private static String formatDeviceVersion(int rawValue) {
byte[] bytes = ByteBuffer.allocate(Integer.BYTES)
.order(ByteOrder.LITTLE_ENDIAN)
.putInt(rawValue)
.array();
String decoded = new String(bytes, StandardCharsets.US_ASCII)
.replace("\u0000", "")
.trim();
if (!decoded.isEmpty() && decoded.chars().allMatch(PcieDeviceInfoFormatter::isPrintableVersionChar)) {
return decoded.startsWith("V") ? decoded : "V" + decoded;
}
return "0x" + Integer.toHexString(rawValue);
}
private static boolean isPrintableVersionChar(int value) {
return (value >= '0' && value <= '9') || value == '.' || value == 'V' || value == 'v';
}
private static String formatStandardVersion(int rawValue) {
int standard = (rawValue >>> 16) & 0xFFFF;
int yearHi = (rawValue >>> 8) & 0xFF;
int yearLo = rawValue & 0xFF;
if (standard > 0 && yearHi > 0 && yearLo > 0) {
return String.format("GMT %04x-%02x%02x", standard, yearHi, yearLo).toUpperCase();
}
return "0x" + Integer.toHexString(rawValue);
}
private static List<String> decodeAsymmetricAlgorithms(int rawValue) {
List<String> values = new ArrayList<>();
if ((rawValue & ASYM_SM2_SIGN) == ASYM_SM2_SIGN) {
values.add("SM2_SIGN");
}
if ((rawValue & ASYM_SM2_DH) == ASYM_SM2_DH) {
values.add("SM2_DH");
}
if ((rawValue & ASYM_SM2_ENC) == ASYM_SM2_ENC) {
values.add("SM2_ENC");
}
if ((rawValue & ASYM_RSA) == ASYM_RSA) {
values.add("RSA");
}
return values;
}
private static List<String> decodeAsymmetricKeyBits(int rawValue) {
List<String> values = new ArrayList<>();
if ((rawValue & ASYM_BITS_256) == ASYM_BITS_256) {
values.add("256");
}
if ((rawValue & ASYM_BITS_2048) == ASYM_BITS_2048) {
values.add("2048");
}
if (!values.isEmpty()) {
values.add("bits");
}
return values;
}
private static List<String> decodeSymmetricAlgorithms(int rawValue) {
List<String> values = new ArrayList<>();
if ((rawValue & SYM_SM1) == SYM_SM1) {
values.add("SM1");
}
if ((rawValue & SYM_SM4) == SYM_SM4) {
values.add("SM4");
}
if ((rawValue & SYM_AES128) == SYM_AES128) {
values.add("AES128");
}
if ((rawValue & SYM_AES192) == SYM_AES192) {
values.add("AES192");
}
if ((rawValue & SYM_AES256) == SYM_AES256) {
values.add("AES256");
}
return values;
}
private static List<String> decodeHashAlgorithms(int rawValue) {
List<String> values = new ArrayList<>();
if ((rawValue & HASH_SM3) == HASH_SM3) {
values.add("SM3");
}
if ((rawValue & HASH_SHA256) == HASH_SHA256) {
values.add("SHA256");
}
return values;
}
private static String formatBufferSize(int rawValue) {
if (rawValue > 0 && rawValue % 1024 == 0) {
return (rawValue / 1024) + " KB";
}
return rawValue + " B";
}
private static String joinNonEmpty(List<String> values) {
return String.join(" ", values);
}
}
@@ -0,0 +1,81 @@
package com.cisd.tms.integration.crypto.pcie.service;
import com.cisd.tms.integration.crypto.pcie.jna.SdfeDeviceStatus;
import com.cisd.tms.integration.crypto.pcie.model.DeviceStatusResult;
import java.util.Arrays;
import java.util.HexFormat;
import java.util.Locale;
public final class PcieDeviceStatusFormatter {
private PcieDeviceStatusFormatter() {
}
static void enrich(DeviceStatusResult result, SdfeDeviceStatus status) {
if (result == null || status == null) {
return;
}
byte[] normalized = normalize(result.getStatusData(), result.getLength());
result.setLength(normalized.length);
result.setStatusHex(HexFormat.of().formatHex(normalized));
result.setFsmState(status.fsmState);
result.setFsmStateText(formatFsmState(status.fsmState));
result.setCommit(status.commit);
result.setCommitHex(String.format(Locale.ROOT, "0x%08x", status.commit));
result.setFrequencyMhz(status.frequency);
result.setFrequencyText(status.frequency + "M");
result.setPcieSpeed(status.pcieSpeed);
result.setPcieWidth(status.pcieWidth);
result.setPcieInfoText(formatPcieInfo(status.pcieSpeed, status.pcieWidth));
result.setBoardTempC(round(status.boardTemp));
result.setBoardTempText(formatFloat(status.boardTemp, "°C"));
result.setBoardVoltageV(round(status.boardVoltage));
result.setBoardVoltageText(formatFloat(status.boardVoltage, "V"));
result.setFsTotalKb(status.fsTotalKb);
result.setFsTotalText(status.fsTotalKb + " KB");
result.setFsFreeKb(status.fsFreeKb);
result.setFsFreeText(status.fsFreeKb + " KB");
}
public static void ensureHex(DeviceStatusResult result) {
if (result == null) {
return;
}
if (result.getStatusHex() == null || result.getStatusHex().isBlank()) {
byte[] normalized = normalize(result.getStatusData(), result.getLength());
result.setLength(normalized.length);
result.setStatusHex(HexFormat.of().formatHex(normalized));
}
}
private static String formatFsmState(int fsmState) {
return switch (fsmState) {
case 0 -> "就绪状态";
case 1 -> "未初始化状态";
default -> "状态码:" + fsmState;
};
}
private static String formatPcieInfo(int speed, int width) {
if (speed <= 0 || width <= 0) {
return "";
}
return "GEN" + speed + " X" + width;
}
private static double round(float value) {
return Math.round(value * 100.0d) / 100.0d;
}
private static String formatFloat(float value, String unit) {
return String.format(Locale.ROOT, "%.2f %s", round(value), unit);
}
private static byte[] normalize(byte[] data, int length) {
if (data == null || data.length == 0) {
return new byte[0];
}
int safeLength = length > 0 && length <= data.length ? length : data.length;
return Arrays.copyOf(data, safeLength);
}
}
@@ -1,18 +1,26 @@
package com.cisd.tms.modules.auth.controller; package com.cisd.tms.modules.auth.controller;
import com.cisd.tms.common.api.ApiResponse; import com.cisd.tms.common.api.ApiResponse;
import com.cisd.tms.modules.auth.dto.ChangePasswordRequest;
import com.cisd.tms.modules.auth.dto.CurrentUserResponse; import com.cisd.tms.modules.auth.dto.CurrentUserResponse;
import com.cisd.tms.modules.auth.dto.LoginRequest; import com.cisd.tms.modules.auth.dto.LoginRequest;
import com.cisd.tms.modules.auth.dto.LoginResponse; import com.cisd.tms.modules.auth.dto.LoginResponse;
import com.cisd.tms.modules.auth.dto.UkeyBindRequest;
import com.cisd.tms.modules.auth.security.RequireAuthLevel;
import com.cisd.tms.modules.auth.security.RequireRole;
import com.cisd.tms.modules.auth.service.AuthAdminService;
import com.cisd.tms.modules.auth.service.AuthService; import com.cisd.tms.modules.auth.service.AuthService;
import com.cisd.tms.modules.mk.dto.UKeySignDTO;
import com.cisd.tms.modules.mk.dto.UKeySignResult;
import com.cisd.tms.security.internal.InternalApiAuthInterceptor;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@@ -22,23 +30,108 @@ import org.springframework.web.bind.annotation.RestController;
public class AuthController { public class AuthController {
private final AuthService authService; private final AuthService authService;
private final AuthAdminService authAdminService;
public AuthController(AuthService authService) { public AuthController(AuthService authService, AuthAdminService authAdminService) {
this.authService = authService; this.authService = authService;
this.authAdminService = authAdminService;
} }
@PostMapping("/login") @PostMapping("/login")
@Operation(summary = "内部用户登录", description = "校验用户名和密码,返回访问令牌及过期时间。") @Operation(summary = "内部用户登录", description = "校验角色口令,可选校验 UKey 序列号,返回访问令牌和认证等级。")
public ApiResponse<LoginResponse> login(@Valid @RequestBody LoginRequest request) { public ApiResponse<LoginResponse> login(@Valid @RequestBody LoginRequest request) {
return ApiResponse.success(authService.login(request)); return ApiResponse.success(authService.login(request));
} }
@GetMapping("/me") @GetMapping("/me")
@Operation(summary = "查询当前用户", description = "根据请求头中的用户标识返回当前登录用户信息。") @Operation(summary = "查询当前用户", description = "根据内部鉴权链路透传的当前角色标识返回当前登录用户信息。")
public ApiResponse<CurrentUserResponse> me( public ApiResponse<CurrentUserResponse> me(HttpServletRequest request) {
@Parameter(description = "当前用户标识,来自内部鉴权链路透传的 X-User 请求头") String user = (String) request.getAttribute(com.cisd.tms.security.internal.InternalApiAuthInterceptor.ATTR_ROLE_CODE);
@RequestHeader(value = "X-User", required = false) String user String sessionToken = (String) request.getAttribute(com.cisd.tms.security.internal.InternalApiAuthInterceptor.ATTR_SESSION_TOKEN);
return ApiResponse.success(authService.me(user, sessionToken));
}
@PostMapping("/logout")
@Operation(summary = "退出当前会话", description = "使当前内部管理端会话失效。")
public ApiResponse<Void> logout(HttpServletRequest request) {
authService.logout((String) request.getAttribute(InternalApiAuthInterceptor.ATTR_SESSION_TOKEN));
return ApiResponse.success();
}
@PostMapping("/change-password")
@Operation(summary = "修改当前角色口令", description = "基于当前登录会话校验并更新角色口令。")
public ApiResponse<Void> changePassword(@Valid @RequestBody ChangePasswordRequest request, HttpServletRequest httpRequest) {
authService.changePassword(
(String) httpRequest.getAttribute(InternalApiAuthInterceptor.ATTR_SESSION_TOKEN),
request.getCurrentPassword(),
request.getNewPassword()
);
return ApiResponse.success();
}
@PostMapping("/roles/{roleCode}/enable")
@Operation(summary = "启用角色", description = "仅允许密钥管理员 FULL 会话启用目标角色。")
@RequireRole("KEY_ADMIN")
@RequireAuthLevel("FULL")
public ApiResponse<Void> enableRole(@PathVariable("roleCode") String roleCode, HttpServletRequest request) {
authAdminService.enableRole(
(String) request.getAttribute(InternalApiAuthInterceptor.ATTR_ROLE_CODE),
(String) request.getAttribute(InternalApiAuthInterceptor.ATTR_AUTH_LEVEL),
roleCode
);
return ApiResponse.success();
}
@PostMapping("/roles/{roleCode}/reset-password")
@Operation(summary = "重置角色密码", description = "仅允许密钥管理员 FULL 会话重置目标角色密码。")
@RequireRole("KEY_ADMIN")
@RequireAuthLevel("FULL")
public ApiResponse<Void> resetPassword(@PathVariable("roleCode") String roleCode, HttpServletRequest request) {
authAdminService.resetPassword(
(String) request.getAttribute(InternalApiAuthInterceptor.ATTR_ROLE_CODE),
(String) request.getAttribute(InternalApiAuthInterceptor.ATTR_AUTH_LEVEL),
roleCode
);
return ApiResponse.success();
}
@PostMapping("/roles/{roleCode}/ukeys/bind")
@Operation(summary = "绑定角色 UKey", description = "仅允许密钥管理员 FULL 会话绑定或覆盖目标角色槽位上的 UKey。")
@RequireRole("KEY_ADMIN")
@RequireAuthLevel("FULL")
public ApiResponse<Void> bindUkey(
@PathVariable("roleCode") String roleCode,
@Valid @RequestBody UkeyBindRequest request,
HttpServletRequest httpRequest
) { ) {
return ApiResponse.success(authService.me(user)); authAdminService.bindIssuedUkey(
(String) httpRequest.getAttribute(InternalApiAuthInterceptor.ATTR_ROLE_CODE),
(String) httpRequest.getAttribute(InternalApiAuthInterceptor.ATTR_AUTH_LEVEL),
roleCode,
request.getSlotNo(),
request.getUkeySerial(),
request.getUkeyPubkey(),
request.getUid(),
request.getRid(),
request.getIssuerSign()
);
return ApiResponse.success();
}
@PostMapping("/roles/{roleCode}/ukeys/issue-sign")
@Operation(summary = "生成绑定用 UKey 发行签名", description = "按旧管理端绑定流程为目标角色的 UKey 信息出具发行签名。")
@RequireRole("KEY_ADMIN")
@RequireAuthLevel("FULL")
public ApiResponse<UKeySignResult> issueUkeyBindingSign(
@PathVariable("roleCode") String roleCode,
@RequestBody UKeySignDTO request,
HttpServletRequest httpRequest
) {
return ApiResponse.success(authAdminService.issueUkeyBindingSign(
(String) httpRequest.getAttribute(InternalApiAuthInterceptor.ATTR_ROLE_CODE),
(String) httpRequest.getAttribute(InternalApiAuthInterceptor.ATTR_AUTH_LEVEL),
roleCode,
request
));
} }
} }
@@ -0,0 +1,50 @@
package com.cisd.tms.modules.auth.controller;
import com.cisd.tms.common.api.ApiResponse;
import com.cisd.tms.modules.auth.dto.CompatPasswordLoginRequest;
import com.cisd.tms.modules.auth.dto.CompatUkeyLoginRequest;
import com.cisd.tms.modules.auth.dto.LoginResponse;
import com.cisd.tms.modules.auth.dto.UkeyLoginRandomResponse;
import com.cisd.tms.modules.auth.service.CompatAuthService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/v1/auth")
@Tag(name = "认证兼容接口", description = "兼容旧管理端的口令登录、UKey 登录与随机数接口")
public class CompatAuthController {
private final CompatAuthService compatAuthService;
public CompatAuthController(CompatAuthService compatAuthService) {
this.compatAuthService = compatAuthService;
}
@GetMapping("/ukey-login/randoms")
@Operation(summary = "生成 UKey 登录随机数", description = "兼容旧登录流程,为指定角色生成一组待签名随机数。")
public ApiResponse<UkeyLoginRandomResponse> issueUkeyLoginRandoms(
@RequestParam("role") String role,
@RequestParam(value = "count", required = false) Integer count
) {
return ApiResponse.success(compatAuthService.issueUkeyLoginRandoms(role, count));
}
@PostMapping("/password-login")
@Operation(summary = "兼容口令登录", description = "兼容旧管理端的角色口令登录语义,底层复用当前会话体系。")
public ApiResponse<LoginResponse> passwordLogin(@Valid @RequestBody CompatPasswordLoginRequest request) {
return ApiResponse.success(compatAuthService.passwordLogin(request));
}
@PostMapping("/ukey-login")
@Operation(summary = "兼容 UKey 登录", description = "兼容旧管理端的 UKey 登录语义,校验发行签名、随机数和登录签名。")
public ApiResponse<LoginResponse> ukeyLogin(@Valid @RequestBody CompatUkeyLoginRequest request) {
return ApiResponse.success(compatAuthService.ukeyLogin(request));
}
}
@@ -0,0 +1,32 @@
package com.cisd.tms.modules.auth.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
@Schema(description = "修改当前角色口令请求")
public class ChangePasswordRequest {
@NotBlank(message = "currentPassword is required")
@Schema(description = "当前口令", example = "12345678")
private String currentPassword;
@NotBlank(message = "newPassword is required")
@Schema(description = "新口令", example = "87654321")
private String newPassword;
public String getCurrentPassword() {
return currentPassword;
}
public void setCurrentPassword(String currentPassword) {
this.currentPassword = currentPassword;
}
public String getNewPassword() {
return newPassword;
}
public void setNewPassword(String newPassword) {
this.newPassword = newPassword;
}
}
@@ -0,0 +1,32 @@
package com.cisd.tms.modules.auth.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
@Schema(description = "兼容旧管理端的口令登录请求")
public class CompatPasswordLoginRequest {
@NotBlank(message = "role is required")
@Schema(description = "角色标识,兼容旧角色名称", example = "auditadmin")
private String role;
@NotBlank(message = "password is required")
@Schema(description = "登录口令", example = "12345678")
private String password;
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
@@ -0,0 +1,132 @@
package com.cisd.tms.modules.auth.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import java.util.List;
@Schema(description = "兼容旧管理端的 UKey 登录请求")
public class CompatUkeyLoginRequest {
@NotBlank(message = "role is required")
@Schema(description = "角色标识,兼容旧角色名称", example = "keyadmin")
private String role;
@NotBlank(message = "password is required")
@Schema(description = "登录口令", example = "12345678")
private String password;
@Valid
@NotEmpty(message = "authInfo is required")
@Schema(description = "UKey 登录认证材料")
private List<LoginAuthInfo> authInfo;
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public List<LoginAuthInfo> getAuthInfo() {
return authInfo;
}
public void setAuthInfo(List<LoginAuthInfo> authInfo) {
this.authInfo = authInfo;
}
@Schema(description = "单个 UKey 登录认证项")
public static class LoginAuthInfo {
@NotBlank(message = "pubKey is required")
private String pubKey;
@NotBlank(message = "uid is required")
private String uid;
@NotBlank(message = "rid is required")
private String rid;
@NotBlank(message = "ra is required")
private String ra;
@NotBlank(message = "rb is required")
private String rb;
@NotBlank(message = "issueSign is required")
private String issueSign;
@NotBlank(message = "loginSignData is required")
private String loginSignData;
@NotBlank(message = "loginSign is required")
private String loginSign;
public String getPubKey() {
return pubKey;
}
public void setPubKey(String pubKey) {
this.pubKey = pubKey;
}
public String getUid() {
return uid;
}
public void setUid(String uid) {
this.uid = uid;
}
public String getRid() {
return rid;
}
public void setRid(String rid) {
this.rid = rid;
}
public String getRa() {
return ra;
}
public void setRa(String ra) {
this.ra = ra;
}
public String getRb() {
return rb;
}
public void setRb(String rb) {
this.rb = rb;
}
public String getIssueSign() {
return issueSign;
}
public void setIssueSign(String issueSign) {
this.issueSign = issueSign;
}
public String getLoginSignData() {
return loginSignData;
}
public void setLoginSignData(String loginSignData) {
this.loginSignData = loginSignData;
}
public String getLoginSign() {
return loginSign;
}
public void setLoginSign(String loginSign) {
this.loginSign = loginSign;
}
}
}
@@ -11,6 +11,10 @@ public class CurrentUserResponse {
private String displayName; private String displayName;
@Schema(description = "角色标识", example = "ADMIN") @Schema(description = "角色标识", example = "ADMIN")
private String role; private String role;
@Schema(description = "当前会话等级", example = "FULL")
private String authLevel;
@Schema(description = "是否需要强制修改密码")
private Boolean needChangePassword;
public String getUsername() { public String getUsername() {
return username; return username;
@@ -35,4 +39,20 @@ public class CurrentUserResponse {
public void setRole(String role) { public void setRole(String role) {
this.role = role; this.role = role;
} }
public String getAuthLevel() {
return authLevel;
}
public void setAuthLevel(String authLevel) {
this.authLevel = authLevel;
}
public Boolean getNeedChangePassword() {
return needChangePassword;
}
public void setNeedChangePassword(Boolean needChangePassword) {
this.needChangePassword = needChangePassword;
}
} }
@@ -2,24 +2,28 @@ package com.cisd.tms.modules.auth.dto;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import java.util.List;
@Schema(description = "内部登录请求") @Schema(description = "内部登录请求")
public class LoginRequest { public class LoginRequest {
@NotBlank(message = "username is required") @NotBlank(message = "roleCode is required")
@Schema(description = "登录用户名", example = "admin") @Schema(description = "角色编码", example = "KEY_ADMIN")
private String username; private String roleCode;
@NotBlank(message = "password is required") @NotBlank(message = "password is required")
@Schema(description = "登录密码", example = "P@ssw0rd") @Schema(description = "登录密码", example = "P@ssw0rd")
private String password; private String password;
public String getUsername() { @Schema(description = "UKey 序列号列表,完整登录时需传入角色要求数量的序列号")
return username; private List<@NotBlank(message = "ukey serial must not be blank") String> ukeySerials;
public String getRoleCode() {
return roleCode;
} }
public void setUsername(String username) { public void setRoleCode(String roleCode) {
this.username = username; this.roleCode = roleCode;
} }
public String getPassword() { public String getPassword() {
@@ -29,4 +33,12 @@ public class LoginRequest {
public void setPassword(String password) { public void setPassword(String password) {
this.password = password; this.password = password;
} }
public List<String> getUkeySerials() {
return ukeySerials;
}
public void setUkeySerials(List<String> ukeySerials) {
this.ukeySerials = ukeySerials;
}
} }
@@ -5,19 +5,23 @@ import io.swagger.v3.oas.annotations.media.Schema;
@Schema(description = "内部登录响应") @Schema(description = "内部登录响应")
public class LoginResponse { public class LoginResponse {
@Schema(description = "登录用户名", example = "admin") @Schema(description = "角色编码", example = "KEY_ADMIN")
private String username; private String roleCode;
@Schema(description = "访问令牌") @Schema(description = "访问令牌")
private String token; private String token;
@Schema(description = "认证等级", example = "FULL")
private String authLevel;
@Schema(description = "令牌过期时间", example = "2026-03-11T18:00:00+08:00") @Schema(description = "令牌过期时间", example = "2026-03-11T18:00:00+08:00")
private String expiresAt; private String expiresAt;
@Schema(description = "是否需要首次修改密码")
private Boolean needChangePassword;
public String getUsername() { public String getRoleCode() {
return username; return roleCode;
} }
public void setUsername(String username) { public void setRoleCode(String roleCode) {
this.username = username; this.roleCode = roleCode;
} }
public String getToken() { public String getToken() {
@@ -28,6 +32,14 @@ public class LoginResponse {
this.token = token; this.token = token;
} }
public String getAuthLevel() {
return authLevel;
}
public void setAuthLevel(String authLevel) {
this.authLevel = authLevel;
}
public String getExpiresAt() { public String getExpiresAt() {
return expiresAt; return expiresAt;
} }
@@ -35,4 +47,12 @@ public class LoginResponse {
public void setExpiresAt(String expiresAt) { public void setExpiresAt(String expiresAt) {
this.expiresAt = expiresAt; this.expiresAt = expiresAt;
} }
public Boolean getNeedChangePassword() {
return needChangePassword;
}
public void setNeedChangePassword(Boolean needChangePassword) {
this.needChangePassword = needChangePassword;
}
} }
@@ -0,0 +1,81 @@
package com.cisd.tms.modules.auth.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
@Schema(description = "UKey 绑定请求")
public class UkeyBindRequest {
@NotNull(message = "slotNo is required")
@Schema(description = "角色 UKey 槽位号", example = "1")
private Integer slotNo;
@NotBlank(message = "ukeySerial is required")
@Schema(description = "UKey 序列号", example = "UK-001")
private String ukeySerial;
@NotBlank(message = "ukeyPubkey is required")
@Schema(description = "UKey 公钥")
private String ukeyPubkey;
@NotBlank(message = "uid is required")
@Schema(description = "UKey 认证信息 uid", example = "1")
private String uid;
@NotBlank(message = "rid is required")
@Schema(description = "UKey 认证信息 rid", example = "RID-001")
private String rid;
@NotBlank(message = "issuerSign is required")
@Schema(description = "认证公钥签名值")
private String issuerSign;
public Integer getSlotNo() {
return slotNo;
}
public void setSlotNo(Integer slotNo) {
this.slotNo = slotNo;
}
public String getUkeySerial() {
return ukeySerial;
}
public void setUkeySerial(String ukeySerial) {
this.ukeySerial = ukeySerial;
}
public String getUkeyPubkey() {
return ukeyPubkey;
}
public void setUkeyPubkey(String ukeyPubkey) {
this.ukeyPubkey = ukeyPubkey;
}
public String getIssuerSign() {
return issuerSign;
}
public void setIssuerSign(String issuerSign) {
this.issuerSign = issuerSign;
}
public String getUid() {
return uid;
}
public void setUid(String uid) {
this.uid = uid;
}
public String getRid() {
return rid;
}
public void setRid(String rid) {
this.rid = rid;
}
}
@@ -0,0 +1,30 @@
package com.cisd.tms.modules.auth.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.List;
@Schema(description = "UKey 登录随机数响应")
public class UkeyLoginRandomResponse {
@Schema(description = "标准化后的角色编码", example = "SUPER_ADMIN")
private String roleCode;
@Schema(description = "供 UKey 登录签名使用的随机数列表")
private List<String> randoms;
public String getRoleCode() {
return roleCode;
}
public void setRoleCode(String roleCode) {
this.roleCode = roleCode;
}
public List<String> getRandoms() {
return randoms;
}
public void setRandoms(List<String> randoms) {
this.randoms = randoms;
}
}
@@ -0,0 +1,73 @@
package com.cisd.tms.modules.auth.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.cisd.tms.infrastructure.persistence.entity.BaseEntity;
import java.time.LocalDateTime;
@TableName("tms_auth_session")
public class AuthSessionEntity extends BaseEntity {
private String sessionToken;
private String roleCode;
private String authLevel;
private LocalDateTime issuedAt;
private LocalDateTime lastActiveAt;
private LocalDateTime expiresAt;
private LocalDateTime logoutAt;
public String getSessionToken() {
return sessionToken;
}
public void setSessionToken(String sessionToken) {
this.sessionToken = sessionToken;
}
public String getRoleCode() {
return roleCode;
}
public void setRoleCode(String roleCode) {
this.roleCode = roleCode;
}
public String getAuthLevel() {
return authLevel;
}
public void setAuthLevel(String authLevel) {
this.authLevel = authLevel;
}
public LocalDateTime getIssuedAt() {
return issuedAt;
}
public void setIssuedAt(LocalDateTime issuedAt) {
this.issuedAt = issuedAt;
}
public LocalDateTime getLastActiveAt() {
return lastActiveAt;
}
public void setLastActiveAt(LocalDateTime lastActiveAt) {
this.lastActiveAt = lastActiveAt;
}
public LocalDateTime getExpiresAt() {
return expiresAt;
}
public void setExpiresAt(LocalDateTime expiresAt) {
this.expiresAt = expiresAt;
}
public LocalDateTime getLogoutAt() {
return logoutAt;
}
public void setLogoutAt(LocalDateTime logoutAt) {
this.logoutAt = logoutAt;
}
}
@@ -0,0 +1,109 @@
package com.cisd.tms.modules.auth.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.cisd.tms.infrastructure.persistence.entity.BaseEntity;
import java.time.LocalDateTime;
@TableName("tms_role_account")
public class RoleAccountEntity extends BaseEntity {
private String roleCode;
private String displayName;
private Integer requiredUkeyCount;
private String passwordHash;
private String passwordSalt;
private String status;
private Boolean needChangePassword;
private Integer failedCount;
private LocalDateTime lockedUntil;
private LocalDateTime lastLoginAt;
private LocalDateTime lastActiveAt;
public String getRoleCode() {
return roleCode;
}
public void setRoleCode(String roleCode) {
this.roleCode = roleCode;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public Integer getRequiredUkeyCount() {
return requiredUkeyCount;
}
public void setRequiredUkeyCount(Integer requiredUkeyCount) {
this.requiredUkeyCount = requiredUkeyCount;
}
public String getPasswordHash() {
return passwordHash;
}
public void setPasswordHash(String passwordHash) {
this.passwordHash = passwordHash;
}
public String getPasswordSalt() {
return passwordSalt;
}
public void setPasswordSalt(String passwordSalt) {
this.passwordSalt = passwordSalt;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public Boolean getNeedChangePassword() {
return needChangePassword;
}
public void setNeedChangePassword(Boolean needChangePassword) {
this.needChangePassword = needChangePassword;
}
public Integer getFailedCount() {
return failedCount;
}
public void setFailedCount(Integer failedCount) {
this.failedCount = failedCount;
}
public LocalDateTime getLockedUntil() {
return lockedUntil;
}
public void setLockedUntil(LocalDateTime lockedUntil) {
this.lockedUntil = lockedUntil;
}
public LocalDateTime getLastLoginAt() {
return lastLoginAt;
}
public void setLastLoginAt(LocalDateTime lastLoginAt) {
this.lastLoginAt = lastLoginAt;
}
public LocalDateTime getLastActiveAt() {
return lastActiveAt;
}
public void setLastActiveAt(LocalDateTime lastActiveAt) {
this.lastActiveAt = lastActiveAt;
}
}
@@ -0,0 +1,82 @@
package com.cisd.tms.modules.auth.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.cisd.tms.infrastructure.persistence.entity.BaseEntity;
import java.time.LocalDateTime;
@TableName("tms_role_ukey_binding")
public class RoleUkeyBindingEntity extends BaseEntity {
private String roleCode;
private Integer slotNo;
private String ukeySerial;
private String ukeyPubkey;
private String issuerSign;
private String status;
private LocalDateTime boundAt;
private LocalDateTime unboundAt;
public String getRoleCode() {
return roleCode;
}
public void setRoleCode(String roleCode) {
this.roleCode = roleCode;
}
public Integer getSlotNo() {
return slotNo;
}
public void setSlotNo(Integer slotNo) {
this.slotNo = slotNo;
}
public String getUkeySerial() {
return ukeySerial;
}
public void setUkeySerial(String ukeySerial) {
this.ukeySerial = ukeySerial;
}
public String getUkeyPubkey() {
return ukeyPubkey;
}
public void setUkeyPubkey(String ukeyPubkey) {
this.ukeyPubkey = ukeyPubkey;
}
public String getIssuerSign() {
return issuerSign;
}
public void setIssuerSign(String issuerSign) {
this.issuerSign = issuerSign;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public LocalDateTime getBoundAt() {
return boundAt;
}
public void setBoundAt(LocalDateTime boundAt) {
this.boundAt = boundAt;
}
public LocalDateTime getUnboundAt() {
return unboundAt;
}
public void setUnboundAt(LocalDateTime unboundAt) {
this.unboundAt = unboundAt;
}
}
@@ -0,0 +1,6 @@
package com.cisd.tms.modules.auth.enums;
public enum AuthLevel {
LIMITED,
FULL
}
@@ -0,0 +1,7 @@
package com.cisd.tms.modules.auth.enums;
public enum RoleAccountStatus {
UNENABLED,
ACTIVE,
LOCKED
}
@@ -0,0 +1,30 @@
package com.cisd.tms.modules.auth.enums;
public enum RoleCode {
SUPER_ADMIN("SUPER_ADMIN", "超级管理员", 3),
KEY_ADMIN("KEY_ADMIN", "密钥管理员", 2),
AUDIT_ADMIN("AUDIT_ADMIN", "审计管理员", 1),
OPS_ADMIN("OPS_ADMIN", "运维管理员", 1);
private final String code;
private final String displayName;
private final int requiredUkeyCount;
RoleCode(String code, String displayName, int requiredUkeyCount) {
this.code = code;
this.displayName = displayName;
this.requiredUkeyCount = requiredUkeyCount;
}
public String getCode() {
return code;
}
public String getDisplayName() {
return displayName;
}
public int getRequiredUkeyCount() {
return requiredUkeyCount;
}
}
@@ -0,0 +1,12 @@
package com.cisd.tms.modules.auth.mapper;
import com.cisd.tms.infrastructure.persistence.mapper.BaseMapperX;
import com.cisd.tms.modules.auth.entity.AuthSessionEntity;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface AuthSessionMapper extends BaseMapperX<AuthSessionEntity> {
AuthSessionEntity selectBySessionToken(@Param("sessionToken") String sessionToken);
}
@@ -0,0 +1,12 @@
package com.cisd.tms.modules.auth.mapper;
import com.cisd.tms.infrastructure.persistence.mapper.BaseMapperX;
import com.cisd.tms.modules.auth.entity.RoleAccountEntity;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface RoleAccountMapper extends BaseMapperX<RoleAccountEntity> {
RoleAccountEntity selectByRoleCode(@Param("roleCode") String roleCode);
}
@@ -0,0 +1,15 @@
package com.cisd.tms.modules.auth.mapper;
import com.cisd.tms.infrastructure.persistence.mapper.BaseMapperX;
import com.cisd.tms.modules.auth.entity.RoleUkeyBindingEntity;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface RoleUkeyBindingMapper extends BaseMapperX<RoleUkeyBindingEntity> {
RoleUkeyBindingEntity selectActiveByRoleCodeAndSlotNo(@Param("roleCode") String roleCode, @Param("slotNo") Integer slotNo);
List<RoleUkeyBindingEntity> selectActiveByRoleCode(@Param("roleCode") String roleCode);
}
@@ -0,0 +1,13 @@
package com.cisd.tms.modules.auth.repository;
import com.cisd.tms.modules.auth.entity.AuthSessionEntity;
import java.util.Optional;
public interface AuthSessionRepository {
Optional<AuthSessionEntity> findBySessionToken(String sessionToken);
void save(AuthSessionEntity entity);
void update(AuthSessionEntity entity);
}
@@ -0,0 +1,13 @@
package com.cisd.tms.modules.auth.repository;
import com.cisd.tms.modules.auth.entity.RoleAccountEntity;
import java.util.Optional;
public interface RoleAccountRepository {
Optional<RoleAccountEntity> findByRoleCode(String roleCode);
void save(RoleAccountEntity entity);
void update(RoleAccountEntity entity);
}
@@ -0,0 +1,16 @@
package com.cisd.tms.modules.auth.repository;
import com.cisd.tms.modules.auth.entity.RoleUkeyBindingEntity;
import java.util.List;
import java.util.Optional;
public interface RoleUkeyBindingRepository {
Optional<RoleUkeyBindingEntity> findActiveByRoleCodeAndSlotNo(String roleCode, Integer slotNo);
List<RoleUkeyBindingEntity> findActiveByRoleCode(String roleCode);
void save(RoleUkeyBindingEntity entity);
void update(RoleUkeyBindingEntity entity);
}
@@ -0,0 +1,32 @@
package com.cisd.tms.modules.auth.repository.impl;
import com.cisd.tms.modules.auth.entity.AuthSessionEntity;
import com.cisd.tms.modules.auth.mapper.AuthSessionMapper;
import com.cisd.tms.modules.auth.repository.AuthSessionRepository;
import java.util.Optional;
import org.springframework.stereotype.Repository;
@Repository
public class AuthSessionRepositoryImpl implements AuthSessionRepository {
private final AuthSessionMapper authSessionMapper;
public AuthSessionRepositoryImpl(AuthSessionMapper authSessionMapper) {
this.authSessionMapper = authSessionMapper;
}
@Override
public Optional<AuthSessionEntity> findBySessionToken(String sessionToken) {
return Optional.ofNullable(authSessionMapper.selectBySessionToken(sessionToken));
}
@Override
public void save(AuthSessionEntity entity) {
authSessionMapper.insert(entity);
}
@Override
public void update(AuthSessionEntity entity) {
authSessionMapper.updateById(entity);
}
}
@@ -0,0 +1,32 @@
package com.cisd.tms.modules.auth.repository.impl;
import com.cisd.tms.modules.auth.entity.RoleAccountEntity;
import com.cisd.tms.modules.auth.mapper.RoleAccountMapper;
import com.cisd.tms.modules.auth.repository.RoleAccountRepository;
import java.util.Optional;
import org.springframework.stereotype.Repository;
@Repository
public class RoleAccountRepositoryImpl implements RoleAccountRepository {
private final RoleAccountMapper roleAccountMapper;
public RoleAccountRepositoryImpl(RoleAccountMapper roleAccountMapper) {
this.roleAccountMapper = roleAccountMapper;
}
@Override
public Optional<RoleAccountEntity> findByRoleCode(String roleCode) {
return Optional.ofNullable(roleAccountMapper.selectByRoleCode(roleCode));
}
@Override
public void save(RoleAccountEntity entity) {
roleAccountMapper.insert(entity);
}
@Override
public void update(RoleAccountEntity entity) {
roleAccountMapper.updateById(entity);
}
}
@@ -0,0 +1,38 @@
package com.cisd.tms.modules.auth.repository.impl;
import com.cisd.tms.modules.auth.entity.RoleUkeyBindingEntity;
import com.cisd.tms.modules.auth.mapper.RoleUkeyBindingMapper;
import com.cisd.tms.modules.auth.repository.RoleUkeyBindingRepository;
import java.util.List;
import java.util.Optional;
import org.springframework.stereotype.Repository;
@Repository
public class RoleUkeyBindingRepositoryImpl implements RoleUkeyBindingRepository {
private final RoleUkeyBindingMapper roleUkeyBindingMapper;
public RoleUkeyBindingRepositoryImpl(RoleUkeyBindingMapper roleUkeyBindingMapper) {
this.roleUkeyBindingMapper = roleUkeyBindingMapper;
}
@Override
public Optional<RoleUkeyBindingEntity> findActiveByRoleCodeAndSlotNo(String roleCode, Integer slotNo) {
return Optional.ofNullable(roleUkeyBindingMapper.selectActiveByRoleCodeAndSlotNo(roleCode, slotNo));
}
@Override
public List<RoleUkeyBindingEntity> findActiveByRoleCode(String roleCode) {
return roleUkeyBindingMapper.selectActiveByRoleCode(roleCode);
}
@Override
public void save(RoleUkeyBindingEntity entity) {
roleUkeyBindingMapper.insert(entity);
}
@Override
public void update(RoleUkeyBindingEntity entity) {
roleUkeyBindingMapper.updateById(entity);
}
}
@@ -0,0 +1,68 @@
package com.cisd.tms.modules.auth.security;
import com.cisd.tms.common.api.ApiResponse;
import com.cisd.tms.common.enums.ErrorCode;
import com.cisd.tms.common.util.HttpResponseUtil;
import com.cisd.tms.security.internal.InternalApiAuthInterceptor;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
@Component
public class InternalAuthorizationInterceptor implements HandlerInterceptor {
private final ObjectMapper objectMapper;
public InternalAuthorizationInterceptor(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!(handler instanceof HandlerMethod handlerMethod)) {
return true;
}
RequireRole requireRole = findAnnotation(handlerMethod, RequireRole.class);
if (requireRole != null) {
String currentRole = (String) request.getAttribute(InternalApiAuthInterceptor.ATTR_ROLE_CODE);
if (!requireRole.value().equals(currentRole)) {
writeForbidden(response, "role not allowed");
return false;
}
}
RequireAuthLevel requireAuthLevel = findAnnotation(handlerMethod, RequireAuthLevel.class);
if (requireAuthLevel != null) {
String currentAuthLevel = (String) request.getAttribute(InternalApiAuthInterceptor.ATTR_AUTH_LEVEL);
if (!requireAuthLevel.value().equals(currentAuthLevel)) {
writeForbidden(response, "auth level not allowed");
return false;
}
}
return true;
}
private <T extends java.lang.annotation.Annotation> T findAnnotation(HandlerMethod handlerMethod, Class<T> annotationType) {
T methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(handlerMethod.getMethod(), annotationType);
if (methodAnnotation != null) {
return methodAnnotation;
}
return AnnotatedElementUtils.findMergedAnnotation(handlerMethod.getBeanType(), annotationType);
}
private void writeForbidden(HttpServletResponse response, String message) throws IOException {
HttpResponseUtil.writeJson(
response,
HttpServletResponse.SC_FORBIDDEN,
ApiResponse.fail(ErrorCode.UNAUTHORIZED.getCode(), message),
objectMapper
);
}
}
@@ -0,0 +1,13 @@
package com.cisd.tms.modules.auth.security;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequireAuthLevel {
String value();
}
@@ -0,0 +1,13 @@
package com.cisd.tms.modules.auth.security;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequireRole {
String value();
}
@@ -0,0 +1,276 @@
package com.cisd.tms.modules.auth.service;
import com.cisd.tms.common.enums.ErrorCode;
import com.cisd.tms.common.exception.BizException;
import com.cisd.tms.integration.crypto.pcie.service.MockPcieCryptoService;
import com.cisd.tms.modules.auth.entity.RoleAccountEntity;
import com.cisd.tms.modules.auth.entity.RoleUkeyBindingEntity;
import com.cisd.tms.modules.auth.enums.AuthLevel;
import com.cisd.tms.modules.auth.enums.RoleAccountStatus;
import com.cisd.tms.modules.auth.enums.RoleCode;
import com.cisd.tms.modules.auth.repository.RoleAccountRepository;
import com.cisd.tms.modules.auth.repository.RoleUkeyBindingRepository;
import com.cisd.tms.common.util.TraceIdUtil;
import com.cisd.tms.modules.mk.config.MasterKeyInitProperties;
import com.cisd.tms.modules.mk.dto.UKeySignDTO;
import com.cisd.tms.modules.mk.dto.UKeySignEntity;
import com.cisd.tms.modules.mk.dto.UKeySignResult;
import com.cisd.tms.modules.mk.service.LmkService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.time.Clock;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.function.Supplier;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class AuthAdminService {
static final String DEFAULT_PASSWORD = "12345678";
private final RoleAccountRepository roleAccountRepository;
private final RoleUkeyBindingRepository roleUkeyBindingRepository;
private final PasswordHasher passwordHasher;
private final LmkService lmkService;
private final ObjectMapper objectMapper;
private final Clock clock;
private final Supplier<String> saltSupplier;
@Autowired
public AuthAdminService(
RoleAccountRepository roleAccountRepository,
RoleUkeyBindingRepository roleUkeyBindingRepository,
PasswordHasher passwordHasher,
LmkService lmkService,
ObjectMapper objectMapper
) {
this(
roleAccountRepository,
roleUkeyBindingRepository,
passwordHasher,
lmkService,
objectMapper,
Clock.systemUTC(),
() -> UUID.randomUUID().toString().replace("-", "")
);
}
AuthAdminService(
RoleAccountRepository roleAccountRepository,
RoleUkeyBindingRepository roleUkeyBindingRepository,
LmkService lmkService,
PasswordHasher passwordHasher,
Clock clock,
Supplier<String> saltSupplier
) {
this(
roleAccountRepository,
roleUkeyBindingRepository,
passwordHasher,
lmkService,
new ObjectMapper(),
clock,
saltSupplier
);
}
AuthAdminService(
RoleAccountRepository roleAccountRepository,
RoleUkeyBindingRepository roleUkeyBindingRepository,
PasswordHasher passwordHasher,
Clock clock,
Supplier<String> saltSupplier
) {
this(
roleAccountRepository,
roleUkeyBindingRepository,
new LmkService(new MockPcieCryptoService(), new MasterKeyInitProperties()),
passwordHasher,
clock,
saltSupplier
);
}
AuthAdminService(
RoleAccountRepository roleAccountRepository,
RoleUkeyBindingRepository roleUkeyBindingRepository,
PasswordHasher passwordHasher,
LmkService lmkService,
ObjectMapper objectMapper,
Clock clock,
Supplier<String> saltSupplier
) {
this.roleAccountRepository = roleAccountRepository;
this.roleUkeyBindingRepository = roleUkeyBindingRepository;
this.passwordHasher = passwordHasher;
this.lmkService = lmkService;
this.objectMapper = objectMapper;
this.clock = clock;
this.saltSupplier = saltSupplier;
}
public void enableRole(String operatorRoleCode, String operatorAuthLevel, String targetRoleCode) {
requireKeyAdminFull(operatorRoleCode, operatorAuthLevel);
RoleAccountEntity target = loadRole(targetRoleCode);
target.setStatus(RoleAccountStatus.ACTIVE.name());
target.setNeedChangePassword(Boolean.TRUE);
target.setLockedUntil(null);
target.setFailedCount(0);
roleAccountRepository.update(target);
}
public void resetPassword(String operatorRoleCode, String operatorAuthLevel, String targetRoleCode) {
requireKeyAdminFull(operatorRoleCode, operatorAuthLevel);
RoleAccountEntity target = loadRole(targetRoleCode);
String newSalt = saltSupplier.get();
target.setPasswordSalt(newSalt);
target.setPasswordHash(passwordHasher.hash(DEFAULT_PASSWORD, newSalt));
target.setStatus(RoleAccountStatus.ACTIVE.name());
target.setNeedChangePassword(Boolean.TRUE);
target.setFailedCount(0);
target.setLockedUntil(null);
roleAccountRepository.update(target);
}
public void bindUkey(
String operatorRoleCode,
String operatorAuthLevel,
String targetRoleCode,
Integer slotNo,
String ukeySerial,
String ukeyPubkey,
String issuerSign
) {
requireKeyAdminFull(operatorRoleCode, operatorAuthLevel);
RoleCode targetRole = resolveRoleCode(targetRoleCode);
if (slotNo == null || slotNo < 1 || slotNo > targetRole.getRequiredUkeyCount()) {
throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "slotNo exceeds role ukey requirement");
}
RoleUkeyBindingEntity binding = roleUkeyBindingRepository
.findActiveByRoleCodeAndSlotNo(targetRoleCode, slotNo)
.orElseGet(RoleUkeyBindingEntity::new);
if (binding.getId() == null) {
binding.setId((long) Math.abs(Objects.hash(targetRoleCode, slotNo, ukeySerial, TraceIdUtil.newTraceId())));
binding.setRoleCode(targetRoleCode);
binding.setSlotNo(slotNo);
binding.setStatus("ACTIVE");
applyBinding(binding, ukeySerial, ukeyPubkey, issuerSign);
roleUkeyBindingRepository.save(binding);
return;
}
applyBinding(binding, ukeySerial, ukeyPubkey, issuerSign);
roleUkeyBindingRepository.update(binding);
}
public void bindIssuedUkey(
String operatorRoleCode,
String operatorAuthLevel,
String targetRoleCode,
Integer slotNo,
String ukeySerial,
String ukeyPubkey,
String uid,
String rid,
String issuerSign
) {
requireKeyAdminFull(operatorRoleCode, operatorAuthLevel);
resolveRoleCode(targetRoleCode);
verifyIssuerSignature(targetRoleCode, ukeyPubkey, uid, rid, issuerSign);
bindUkey(operatorRoleCode, operatorAuthLevel, targetRoleCode, slotNo, ukeySerial, ukeyPubkey, issuerSign);
}
public UKeySignResult issueUkeyBindingSign(
String operatorRoleCode,
String operatorAuthLevel,
String targetRoleCode,
UKeySignDTO request
) {
requireKeyAdminFull(operatorRoleCode, operatorAuthLevel);
RoleCode targetRole = resolveRoleCode(targetRoleCode);
String legacyRole = toLegacyRole(targetRole);
String authKeyPair = lmkService.exportIkPublicKeyHex();
UKeySignDTO dto = new UKeySignDTO();
dto.setPubKey(request.getPubKey());
dto.setRole(legacyRole);
dto.setUid(request.getUid());
dto.setRid(request.getRid());
dto.setExtra(request.getExtra());
String signValue = lmkService.signIk(toIssuePayload(dto, authKeyPair));
String extra = "";
return UKeySignResult.builder().sign(signValue).component(extra).build();
}
private void applyBinding(RoleUkeyBindingEntity binding, String ukeySerial, String ukeyPubkey, String issuerSign) {
binding.setUkeySerial(ukeySerial);
binding.setUkeyPubkey(ukeyPubkey);
binding.setIssuerSign(issuerSign);
binding.setStatus("ACTIVE");
binding.setBoundAt(now());
binding.setUnboundAt(null);
}
private void requireKeyAdminFull(String operatorRoleCode, String operatorAuthLevel) {
if (!RoleCode.KEY_ADMIN.getCode().equals(operatorRoleCode) || !AuthLevel.FULL.name().equals(operatorAuthLevel)) {
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "operation requires KEY_ADMIN FULL session");
}
}
private RoleAccountEntity loadRole(String roleCode) {
resolveRoleCode(roleCode);
return roleAccountRepository.findByRoleCode(roleCode)
.orElseThrow(() -> new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "target role account not found"));
}
private RoleCode resolveRoleCode(String roleCode) {
return List.of(RoleCode.values()).stream()
.filter(item -> item.getCode().equals(roleCode))
.findFirst()
.orElseThrow(() -> new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "invalid roleCode"));
}
private LocalDateTime now() {
return LocalDateTime.ofInstant(clock.instant(), ZoneOffset.UTC);
}
private void verifyIssuerSignature(String targetRoleCode, String ukeyPubkey, String uid, String rid, String issuerSign) {
try {
UKeySignDTO dto = new UKeySignDTO();
dto.setPubKey(ukeyPubkey);
dto.setRole(toLegacyRole(resolveRoleCode(targetRoleCode)));
dto.setUid(uid);
dto.setRid(rid);
String authKeyPair = lmkService.exportIkPublicKeyHex();
lmkService.verifyIk(toIssuePayload(dto, authKeyPair), issuerSign);
} catch (BizException ex) {
throw ex;
} catch (RuntimeException ex) {
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "ukey issuer signature verification failed");
}
}
private String toIssuePayload(UKeySignDTO dto, String authKeyPair) {
try {
return objectMapper.writeValueAsString(UKeySignEntity.getInstance(dto, authKeyPair));
} catch (JsonProcessingException ex) {
throw new IllegalStateException("serialize ukey issue payload failed", ex);
}
}
private String toLegacyRole(RoleCode roleCode) {
return switch (roleCode) {
case SUPER_ADMIN -> "superadmin";
case KEY_ADMIN -> "keyadmin";
case AUDIT_ADMIN -> "auditadmin";
case OPS_ADMIN -> "configadmin";
};
}
}
@@ -1,33 +1,126 @@
package com.cisd.tms.modules.auth.service; package com.cisd.tms.modules.auth.service;
import com.cisd.tms.common.enums.ErrorCode;
import com.cisd.tms.common.exception.BizException;
import com.cisd.tms.modules.auth.dto.CurrentUserResponse; import com.cisd.tms.modules.auth.dto.CurrentUserResponse;
import com.cisd.tms.modules.auth.dto.LoginRequest; import com.cisd.tms.modules.auth.dto.LoginRequest;
import com.cisd.tms.modules.auth.dto.LoginResponse; import com.cisd.tms.modules.auth.dto.LoginResponse;
import com.cisd.tms.modules.auth.entity.AuthSessionEntity;
import com.cisd.tms.modules.auth.entity.AuthUserEntity; import com.cisd.tms.modules.auth.entity.AuthUserEntity;
import com.cisd.tms.modules.auth.entity.RoleAccountEntity;
import com.cisd.tms.modules.auth.entity.RoleUkeyBindingEntity;
import com.cisd.tms.modules.auth.enums.AuthLevel;
import com.cisd.tms.modules.auth.enums.RoleAccountStatus;
import com.cisd.tms.modules.auth.repository.AuthUserRepository; import com.cisd.tms.modules.auth.repository.AuthUserRepository;
import com.cisd.tms.modules.auth.repository.AuthSessionRepository;
import com.cisd.tms.modules.auth.repository.RoleAccountRepository;
import com.cisd.tms.modules.auth.repository.RoleUkeyBindingRepository;
import java.time.Clock;
import java.time.LocalDateTime;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.function.Supplier;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@Service @Service
public class AuthService { public class AuthService {
private final AuthUserRepository authUserRepository; private static final int MAX_FAILED_ATTEMPTS = 5;
private static final int IDLE_TIMEOUT_MINUTES = 10;
public AuthService(AuthUserRepository authUserRepository) { private final RoleAccountRepository roleAccountRepository;
private final AuthUserRepository authUserRepository;
private final AuthSessionRepository authSessionRepository;
private final RoleUkeyBindingRepository roleUkeyBindingRepository;
private final PasswordHasher passwordHasher;
private final Clock clock;
private final Supplier<String> tokenSupplier;
@Autowired
public AuthService(
RoleAccountRepository roleAccountRepository,
AuthUserRepository authUserRepository,
AuthSessionRepository authSessionRepository,
RoleUkeyBindingRepository roleUkeyBindingRepository,
PasswordHasher passwordHasher
) {
this(
roleAccountRepository,
authUserRepository,
authSessionRepository,
roleUkeyBindingRepository,
passwordHasher,
Clock.systemUTC(),
() -> "tms-" + UUID.randomUUID()
);
}
AuthService(
RoleAccountRepository roleAccountRepository,
AuthUserRepository authUserRepository,
AuthSessionRepository authSessionRepository,
RoleUkeyBindingRepository roleUkeyBindingRepository,
PasswordHasher passwordHasher,
Clock clock,
Supplier<String> tokenSupplier
) {
this.roleAccountRepository = roleAccountRepository;
this.authUserRepository = authUserRepository; this.authUserRepository = authUserRepository;
this.authSessionRepository = authSessionRepository;
this.roleUkeyBindingRepository = roleUkeyBindingRepository;
this.passwordHasher = passwordHasher;
this.clock = clock;
this.tokenSupplier = tokenSupplier;
} }
public LoginResponse login(LoginRequest request) { public LoginResponse login(LoginRequest request) {
RoleAccountEntity roleAccount = roleAccountRepository.findByRoleCode(request.getRoleCode())
.orElseThrow(() -> new BizException(ErrorCode.UNAUTHORIZED.getCode(), "role account not found"));
validateRoleStatus(roleAccount);
if (!passwordHasher.matches(request.getPassword(), roleAccount.getPasswordSalt(), roleAccount.getPasswordHash())) {
onPasswordFailed(roleAccount);
}
resetFailureState(roleAccount);
AuthLevel authLevel = resolveAuthLevel(roleAccount, request.getUkeySerials());
AuthSessionEntity session = buildSession(roleAccount.getRoleCode(), authLevel);
authSessionRepository.save(session);
LoginResponse response = new LoginResponse(); LoginResponse response = new LoginResponse();
response.setUsername(request.getUsername()); response.setRoleCode(roleAccount.getRoleCode());
response.setToken("tms-" + UUID.randomUUID()); response.setAuthLevel(authLevel.name());
response.setExpiresAt(OffsetDateTime.now().plusHours(8).toString()); response.setToken(session.getSessionToken());
response.setExpiresAt(OffsetDateTime.of(session.getExpiresAt(), ZoneOffset.UTC).toString());
response.setNeedChangePassword(Boolean.TRUE.equals(roleAccount.getNeedChangePassword()));
return response; return response;
} }
public CurrentUserResponse me(String username) { public CurrentUserResponse me(String username) {
return me(username, null);
}
public CurrentUserResponse me(String username, String sessionToken) {
String normalized = username == null || username.isBlank() ? "admin" : username; String normalized = username == null || username.isBlank() ? "admin" : username;
RoleAccountEntity roleAccount = roleAccountRepository.findByRoleCode(normalized).orElse(null);
if (roleAccount != null) {
CurrentUserResponse response = new CurrentUserResponse();
response.setUsername(roleAccount.getRoleCode());
response.setDisplayName(roleAccount.getDisplayName());
response.setRole(roleAccount.getRoleCode());
AuthSessionEntity session = sessionToken == null ? null : authSessionRepository.findBySessionToken(sessionToken).orElse(null);
if (session != null) {
response.setAuthLevel(session.getAuthLevel());
}
response.setNeedChangePassword(Boolean.TRUE.equals(roleAccount.getNeedChangePassword()));
return response;
}
AuthUserEntity entity = authUserRepository.findByUsername(normalized).orElse(null); AuthUserEntity entity = authUserRepository.findByUsername(normalized).orElse(null);
CurrentUserResponse response = new CurrentUserResponse(); CurrentUserResponse response = new CurrentUserResponse();
@@ -43,4 +136,114 @@ public class AuthService {
response.setRole(entity.getRole()); response.setRole(entity.getRole());
return response; return response;
} }
public void logout(String sessionToken) {
AuthSessionEntity session = requireActiveSession(sessionToken);
session.setLogoutAt(now());
session.setExpiresAt(now());
authSessionRepository.update(session);
}
public void changePassword(String sessionToken, String currentPassword, String newPassword) {
AuthSessionEntity session = requireActiveSession(sessionToken);
RoleAccountEntity roleAccount = roleAccountRepository.findByRoleCode(session.getRoleCode())
.orElseThrow(() -> new BizException(ErrorCode.UNAUTHORIZED.getCode(), "role account not found"));
if (!passwordHasher.matches(currentPassword, roleAccount.getPasswordSalt(), roleAccount.getPasswordHash())) {
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "current password is incorrect");
}
String newSalt = UUID.randomUUID().toString().replace("-", "");
roleAccount.setPasswordSalt(newSalt);
roleAccount.setPasswordHash(passwordHasher.hash(newPassword, newSalt));
roleAccount.setNeedChangePassword(Boolean.FALSE);
roleAccount.setFailedCount(0);
roleAccount.setLockedUntil(null);
roleAccount.setStatus(RoleAccountStatus.ACTIVE.name());
roleAccountRepository.update(roleAccount);
}
private void validateRoleStatus(RoleAccountEntity roleAccount) {
if (RoleAccountStatus.UNENABLED.name().equals(roleAccount.getStatus())) {
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "role is not enabled");
}
if (RoleAccountStatus.LOCKED.name().equals(roleAccount.getStatus()) && isStillLocked(roleAccount)) {
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "role account is locked");
}
}
private boolean isStillLocked(RoleAccountEntity roleAccount) {
return roleAccount.getLockedUntil() == null || roleAccount.getLockedUntil().isAfter(now());
}
private void onPasswordFailed(RoleAccountEntity roleAccount) {
int failedCount = roleAccount.getFailedCount() == null ? 0 : roleAccount.getFailedCount();
failedCount++;
roleAccount.setFailedCount(failedCount);
if (failedCount >= MAX_FAILED_ATTEMPTS) {
roleAccount.setStatus(RoleAccountStatus.LOCKED.name());
roleAccount.setLockedUntil(now().plusMinutes(IDLE_TIMEOUT_MINUTES));
roleAccountRepository.update(roleAccount);
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "role account is locked");
}
roleAccountRepository.update(roleAccount);
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "password is incorrect");
}
private void resetFailureState(RoleAccountEntity roleAccount) {
roleAccount.setFailedCount(0);
roleAccount.setLockedUntil(null);
roleAccount.setStatus(RoleAccountStatus.ACTIVE.name());
roleAccount.setLastLoginAt(now());
roleAccount.setLastActiveAt(now());
roleAccountRepository.update(roleAccount);
}
private AuthLevel resolveAuthLevel(RoleAccountEntity roleAccount, List<String> ukeySerials) {
if (ukeySerials == null || ukeySerials.isEmpty()) {
return AuthLevel.LIMITED;
}
Set<String> uniqueSerials = new LinkedHashSet<>(ukeySerials);
if (uniqueSerials.size() != roleAccount.getRequiredUkeyCount()) {
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "ukey count does not satisfy role requirement");
}
List<RoleUkeyBindingEntity> bindings = roleUkeyBindingRepository.findActiveByRoleCode(roleAccount.getRoleCode());
if (bindings.size() != roleAccount.getRequiredUkeyCount()) {
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "ukey count does not satisfy role requirement");
}
Set<String> boundSerials = bindings.stream()
.map(RoleUkeyBindingEntity::getUkeySerial)
.collect(java.util.stream.Collectors.toSet());
if (!boundSerials.containsAll(uniqueSerials)) {
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "ukey verification failed");
}
return AuthLevel.FULL;
}
private AuthSessionEntity buildSession(String roleCode, AuthLevel authLevel) {
LocalDateTime issuedAt = now();
AuthSessionEntity entity = new AuthSessionEntity();
entity.setRoleCode(roleCode);
entity.setAuthLevel(authLevel.name());
entity.setSessionToken(tokenSupplier.get());
entity.setIssuedAt(issuedAt);
entity.setLastActiveAt(issuedAt);
entity.setExpiresAt(issuedAt.plusMinutes(IDLE_TIMEOUT_MINUTES));
return entity;
}
private LocalDateTime now() {
return LocalDateTime.ofInstant(clock.instant(), ZoneOffset.UTC);
}
private AuthSessionEntity requireActiveSession(String sessionToken) {
AuthSessionEntity session = authSessionRepository.findBySessionToken(sessionToken)
.orElseThrow(() -> new BizException(ErrorCode.UNAUTHORIZED.getCode(), "session not found"));
if (session.getLogoutAt() != null || session.getExpiresAt() == null || !session.getExpiresAt().isAfter(now())) {
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "session expired");
}
return session;
}
} }
@@ -0,0 +1,158 @@
package com.cisd.tms.modules.auth.service;
import com.cisd.tms.common.enums.ErrorCode;
import com.cisd.tms.common.exception.BizException;
import com.cisd.tms.modules.auth.dto.CompatPasswordLoginRequest;
import com.cisd.tms.modules.auth.dto.CompatUkeyLoginRequest;
import com.cisd.tms.modules.auth.dto.LoginRequest;
import com.cisd.tms.modules.auth.dto.LoginResponse;
import com.cisd.tms.modules.auth.dto.UkeyLoginRandomResponse;
import com.cisd.tms.modules.auth.entity.RoleUkeyBindingEntity;
import com.cisd.tms.modules.auth.enums.RoleCode;
import com.cisd.tms.modules.auth.repository.RoleUkeyBindingRepository;
import com.cisd.tms.modules.mk.dto.UKeySignDTO;
import com.cisd.tms.modules.mk.dto.UKeySignEntity;
import com.cisd.tms.modules.mk.enums.MasterKeyStatus;
import com.cisd.tms.modules.mk.service.LmkService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.stereotype.Service;
@Service
public class CompatAuthService {
private final AuthService authService;
private final RoleUkeyBindingRepository roleUkeyBindingRepository;
private final LmkService lmkService;
private final UkeyLoginRandomService ukeyLoginRandomService;
private final CompatUkeyVerifier compatUkeyVerifier;
private final ObjectMapper objectMapper;
public CompatAuthService(
AuthService authService,
RoleUkeyBindingRepository roleUkeyBindingRepository,
LmkService lmkService,
UkeyLoginRandomService ukeyLoginRandomService,
CompatUkeyVerifier compatUkeyVerifier,
ObjectMapper objectMapper
) {
this.authService = authService;
this.roleUkeyBindingRepository = roleUkeyBindingRepository;
this.lmkService = lmkService;
this.ukeyLoginRandomService = ukeyLoginRandomService;
this.compatUkeyVerifier = compatUkeyVerifier;
this.objectMapper = objectMapper;
}
public UkeyLoginRandomResponse issueUkeyLoginRandoms(String legacyRole, Integer count) {
RoleCode roleCode = resolveRoleCode(legacyRole);
int resolvedCount = count == null ? roleCode.getRequiredUkeyCount() : count;
UkeyLoginRandomResponse response = new UkeyLoginRandomResponse();
response.setRoleCode(roleCode.getCode());
response.setRandoms(ukeyLoginRandomService.issue(roleCode.getCode(), resolvedCount));
return response;
}
public LoginResponse passwordLogin(CompatPasswordLoginRequest request) {
ensureMasterKeyReady();
RoleCode roleCode = resolveRoleCode(request.getRole());
LoginRequest loginRequest = new LoginRequest();
loginRequest.setRoleCode(roleCode.getCode());
loginRequest.setPassword(request.getPassword());
return authService.login(loginRequest);
}
public LoginResponse ukeyLogin(CompatUkeyLoginRequest request) {
ensureMasterKeyReady();
RoleCode roleCode = resolveRoleCode(request.getRole());
List<RoleUkeyBindingEntity> activeBindings = roleUkeyBindingRepository.findActiveByRoleCode(roleCode.getCode());
validateUkeyCount(roleCode, activeBindings, request.getAuthInfo());
Map<String, RoleUkeyBindingEntity> bindingsByPubKey = activeBindings.stream()
.collect(Collectors.toMap(RoleUkeyBindingEntity::getUkeyPubkey, item -> item, (left, right) -> left, LinkedHashMap::new));
Set<String> requestPubKeys = request.getAuthInfo().stream()
.map(CompatUkeyLoginRequest.LoginAuthInfo::getPubKey)
.collect(Collectors.toSet());
if (requestPubKeys.size() != request.getAuthInfo().size() || !bindingsByPubKey.keySet().containsAll(requestPubKeys)) {
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "ukey auth info does not match bound role");
}
ukeyLoginRandomService.assertIssued(
roleCode.getCode(),
request.getAuthInfo().stream().map(CompatUkeyLoginRequest.LoginAuthInfo::getRb).toList()
);
String authKeyPair = lmkService.exportIkPublicKeyHex();
List<String> matchedSerials = new ArrayList<>();
for (CompatUkeyLoginRequest.LoginAuthInfo loginAuthInfo : request.getAuthInfo()) {
RoleUkeyBindingEntity binding = bindingsByPubKey.get(loginAuthInfo.getPubKey());
compatUkeyVerifier.verifyIssuedBinding(buildIssuePayload(request.getRole(), loginAuthInfo, authKeyPair), loginAuthInfo.getIssueSign());
compatUkeyVerifier.verifyLoginSignature(loginAuthInfo.getPubKey(), loginAuthInfo.getLoginSignData(), loginAuthInfo.getLoginSign());
matchedSerials.add(binding.getUkeySerial());
}
LoginRequest loginRequest = new LoginRequest();
loginRequest.setRoleCode(roleCode.getCode());
loginRequest.setPassword(request.getPassword());
loginRequest.setUkeySerials(matchedSerials);
return authService.login(loginRequest);
}
private void ensureMasterKeyReady() {
MasterKeyStatus.StatusDetail status = lmkService.getMasterKeyStatus();
if (status == null || status.getCode() == MasterKeyStatus.ABNORMAL.getCode()) {
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "master key is not ready");
}
}
private void validateUkeyCount(
RoleCode roleCode,
List<RoleUkeyBindingEntity> activeBindings,
List<CompatUkeyLoginRequest.LoginAuthInfo> authInfos
) {
if (authInfos == null || authInfos.size() != roleCode.getRequiredUkeyCount()) {
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "ukey count does not satisfy role requirement");
}
if (activeBindings.size() != roleCode.getRequiredUkeyCount()) {
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "ukey count does not satisfy role requirement");
}
}
private String buildIssuePayload(String legacyRole, CompatUkeyLoginRequest.LoginAuthInfo loginAuthInfo, String authKeyPair) {
UKeySignDTO uKeySignDTO = new UKeySignDTO();
uKeySignDTO.setPubKey(loginAuthInfo.getPubKey());
uKeySignDTO.setRole(normalizeLegacyRole(legacyRole));
uKeySignDTO.setUid(loginAuthInfo.getUid());
uKeySignDTO.setRid(loginAuthInfo.getRid());
try {
return objectMapper.writeValueAsString(UKeySignEntity.getInstance(uKeySignDTO, authKeyPair));
} catch (JsonProcessingException ex) {
throw new IllegalStateException("serialize ukey issue payload failed", ex);
}
}
private RoleCode resolveRoleCode(String role) {
return switch (normalizeLegacyRole(role)) {
case "superadmin" -> RoleCode.SUPER_ADMIN;
case "keyadmin" -> RoleCode.KEY_ADMIN;
case "auditadmin" -> RoleCode.AUDIT_ADMIN;
case "configadmin", "opsadmin" -> RoleCode.OPS_ADMIN;
case "super_admin" -> RoleCode.SUPER_ADMIN;
case "key_admin" -> RoleCode.KEY_ADMIN;
case "audit_admin" -> RoleCode.AUDIT_ADMIN;
case "ops_admin" -> RoleCode.OPS_ADMIN;
default -> throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "invalid role");
};
}
private String normalizeLegacyRole(String role) {
return role == null ? "" : role.trim().toLowerCase(Locale.ROOT);
}
}
@@ -0,0 +1,8 @@
package com.cisd.tms.modules.auth.service;
public interface CompatUkeyVerifier {
void verifyIssuedBinding(String payload, String issueSign);
void verifyLoginSignature(String pubKey, String loginSignData, String loginSign);
}
@@ -0,0 +1,63 @@
package com.cisd.tms.modules.auth.service;
import com.cisd.tms.common.enums.ErrorCode;
import com.cisd.tms.common.exception.BizException;
import com.cisd.tms.integration.crypto.pcie.service.PcieCryptoService;
import java.time.Clock;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.bouncycastle.util.encoders.Hex;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class InMemoryUkeyLoginRandomService implements UkeyLoginRandomService {
private static final int RANDOM_BYTES = 16;
private static final int EXPIRE_MINUTES = 10;
private final PcieCryptoService pcieCryptoService;
private final Clock clock;
private final Map<String, IssuedRandoms> store = new ConcurrentHashMap<>();
@Autowired
public InMemoryUkeyLoginRandomService(PcieCryptoService pcieCryptoService) {
this(pcieCryptoService, Clock.systemUTC());
}
InMemoryUkeyLoginRandomService(PcieCryptoService pcieCryptoService, Clock clock) {
this.pcieCryptoService = pcieCryptoService;
this.clock = clock;
}
@Override
public List<String> issue(String roleCode, int count) {
if (count <= 0) {
throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "count must be positive");
}
List<String> randoms = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
randoms.add(Hex.toHexString(pcieCryptoService.generateRandom(RANDOM_BYTES)));
}
store.put(roleCode, new IssuedRandoms(randoms, clock.instant().plus(EXPIRE_MINUTES, ChronoUnit.MINUTES)));
return randoms;
}
@Override
public void assertIssued(String roleCode, List<String> randoms) {
IssuedRandoms issuedRandoms = store.remove(roleCode);
if (issuedRandoms == null || issuedRandoms.expiresAt().isBefore(clock.instant())) {
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "login randoms are missing or expired");
}
if (randoms == null || randoms.size() != issuedRandoms.randoms().size() || !issuedRandoms.randoms().containsAll(randoms)) {
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "login random verification failed");
}
}
private record IssuedRandoms(List<String> randoms, Instant expiresAt) {
}
}
@@ -0,0 +1,8 @@
package com.cisd.tms.modules.auth.service;
public interface PasswordHasher {
String hash(String password, String salt);
boolean matches(String rawPassword, String salt, String expectedHash);
}
@@ -0,0 +1,34 @@
package com.cisd.tms.modules.auth.service;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.util.Base64;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import org.springframework.stereotype.Component;
@Component
public class Pbkdf2PasswordHasher implements PasswordHasher {
private static final int ITERATIONS = 65_536;
private static final int KEY_LENGTH = 256;
@Override
public String hash(String password, String salt) {
try {
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt.getBytes(StandardCharsets.UTF_8), ITERATIONS, KEY_LENGTH);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
return Base64.getEncoder().encodeToString(keyFactory.generateSecret(spec).getEncoded());
} catch (GeneralSecurityException ex) {
throw new IllegalStateException("failed to hash password", ex);
}
}
@Override
public boolean matches(String rawPassword, String salt, String expectedHash) {
byte[] actual = hash(rawPassword, salt).getBytes(StandardCharsets.UTF_8);
byte[] expected = expectedHash.getBytes(StandardCharsets.UTF_8);
return MessageDigest.isEqual(actual, expected);
}
}
@@ -0,0 +1,66 @@
package com.cisd.tms.modules.auth.service;
import com.cisd.tms.common.enums.ErrorCode;
import com.cisd.tms.common.exception.BizException;
import com.cisd.tms.integration.crypto.pcie.Gm0018AlgorithmIds;
import com.cisd.tms.integration.crypto.pcie.model.EccExternalVerifyRequest;
import com.cisd.tms.integration.crypto.pcie.service.PcieCryptoService;
import com.cisd.tms.modules.mk.service.LmkService;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import org.bouncycastle.util.encoders.Hex;
import org.springframework.stereotype.Component;
@Component
public class PcieCompatUkeyVerifier implements CompatUkeyVerifier {
private final LmkService lmkService;
private final PcieCryptoService pcieCryptoService;
public PcieCompatUkeyVerifier(LmkService lmkService, PcieCryptoService pcieCryptoService) {
this.lmkService = lmkService;
this.pcieCryptoService = pcieCryptoService;
}
@Override
public void verifyIssuedBinding(String payload, String issueSign) {
try {
lmkService.verifyIk(payload, issueSign);
} catch (BizException ex) {
throw ex;
} catch (RuntimeException ex) {
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "issue signature verification failed");
}
}
@Override
public void verifyLoginSignature(String pubKey, String loginSignData, String loginSign) {
EccExternalVerifyRequest request = new EccExternalVerifyRequest();
request.setAlgId(Gm0018AlgorithmIds.SM2_SIGN);
request.setPublicKeyBlob(decodeBlob(pubKey, "login public key is invalid"));
request.setData(loginSignData.getBytes(StandardCharsets.UTF_8));
request.setSignature(decodeBlob(loginSign, "login signature is invalid"));
try {
pcieCryptoService.eccExternalVerify(request);
} catch (BizException ex) {
throw ex;
} catch (RuntimeException ex) {
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "login signature verification failed");
}
}
private byte[] decodeBlob(String value, String message) {
String normalized = value == null ? "" : value.trim();
if (normalized.isEmpty()) {
throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), message);
}
if (normalized.matches("(?i)^[0-9a-f]+$") && normalized.length() % 2 == 0) {
return Hex.decode(normalized);
}
try {
return Base64.getDecoder().decode(normalized);
} catch (IllegalArgumentException ex) {
throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), message);
}
}
}
@@ -0,0 +1,10 @@
package com.cisd.tms.modules.auth.service;
import java.util.List;
public interface UkeyLoginRandomService {
List<String> issue(String roleCode, int count);
void assertIssued(String roleCode, List<String> randoms);
}
@@ -0,0 +1,53 @@
package com.cisd.tms.modules.device.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "tms.device.profile")
public class DeviceProfileProperties {
private String model = "SYD7108";
private String name = "跨境支付终端一体机";
private String cpuModelDefault = "";
private String memoryTotalDefault = "16GB";
private String diskTotalDefault = "256G";
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCpuModelDefault() {
return cpuModelDefault;
}
public void setCpuModelDefault(String cpuModelDefault) {
this.cpuModelDefault = cpuModelDefault;
}
public String getMemoryTotalDefault() {
return memoryTotalDefault;
}
public void setMemoryTotalDefault(String memoryTotalDefault) {
this.memoryTotalDefault = memoryTotalDefault;
}
public String getDiskTotalDefault() {
return diskTotalDefault;
}
public void setDiskTotalDefault(String diskTotalDefault) {
this.diskTotalDefault = diskTotalDefault;
}
}
File diff suppressed because it is too large Load Diff
@@ -8,12 +8,13 @@ import com.cisd.tms.modules.device.dto.DeviceRuntimeStatusResponse;
import com.cisd.tms.modules.device.service.DeviceProfileService; import com.cisd.tms.modules.device.service.DeviceProfileService;
import com.cisd.tms.modules.device.service.DeviceRuntimeStatusService; import com.cisd.tms.modules.device.service.DeviceRuntimeStatusService;
import com.cisd.tms.modules.device.service.DeviceService; import com.cisd.tms.modules.device.service.DeviceService;
import com.cisd.tms.security.internal.InternalApiAuthInterceptor;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@@ -62,10 +63,8 @@ public class DeviceController {
@PostMapping("/restart") @PostMapping("/restart")
@Operation(summary = "重启设备服务", description = "受理设备重启请求,并记录触发操作的内部用户。") @Operation(summary = "重启设备服务", description = "受理设备重启请求,并记录触发操作的内部用户。")
public ApiResponse<DeviceActionResponse> restart( public ApiResponse<DeviceActionResponse> restart(HttpServletRequest request) {
@Parameter(description = "触发重启的操作人,来自内部鉴权链路透传的 X-User 请求头") String operator = (String) request.getAttribute(InternalApiAuthInterceptor.ATTR_ROLE_CODE);
@RequestHeader(value = "X-User", required = false) String operator
) {
return ApiResponse.success(deviceService.restart(operator)); return ApiResponse.success(deviceService.restart(operator));
} }
} }
@@ -0,0 +1,29 @@
package com.cisd.tms.modules.device.dto;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(description = "加密卡运行状态")
public class CryptoCardRuntimeStatus {
@Schema(description = "状态", example = "NORMAL")
private String status;
@Schema(description = "状态说明", example = "就绪状态")
private String message;
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
@@ -0,0 +1,48 @@
package com.cisd.tms.modules.device.dto;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(description = "设备基础信息")
public class DeviceProfileDeviceSection {
@Schema(description = "设备型号", example = "SYD7108")
private String model;
@Schema(description = "设备名称", example = "跨境支付终端一体机")
private String name;
@Schema(description = "设备序列号", example = "1234567890ABCDEF")
private String serialNumber;
@Schema(description = "设备版本", example = "V1.00")
private String deviceVersion;
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSerialNumber() {
return serialNumber;
}
public void setSerialNumber(String serialNumber) {
this.serialNumber = serialNumber;
}
public String getDeviceVersion() {
return deviceVersion;
}
public void setDeviceVersion(String deviceVersion) {
this.deviceVersion = deviceVersion;
}
}
@@ -0,0 +1,48 @@
package com.cisd.tms.modules.device.dto;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(description = "设备硬件信息")
public class DeviceProfileHardwareSection {
@Schema(description = "密码卡名称", example = "SYD1408加密卡")
private String cryptoCardName;
@Schema(description = "CPU 型号", example = "Hygon C86-3G (OPN:3350)")
private String cpuModel;
@Schema(description = "内存总量", example = "32GB")
private String memoryTotal;
@Schema(description = "磁盘总量", example = "953.87GB")
private String diskTotal;
public String getCryptoCardName() {
return cryptoCardName;
}
public void setCryptoCardName(String cryptoCardName) {
this.cryptoCardName = cryptoCardName;
}
public String getCpuModel() {
return cpuModel;
}
public void setCpuModel(String cpuModel) {
this.cpuModel = cpuModel;
}
public String getMemoryTotal() {
return memoryTotal;
}
public void setMemoryTotal(String memoryTotal) {
this.memoryTotal = memoryTotal;
}
public String getDiskTotal() {
return diskTotal;
}
public void setDiskTotal(String diskTotal) {
this.diskTotal = diskTotal;
}
}
@@ -0,0 +1,40 @@
package com.cisd.tms.modules.device.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.ArrayList;
import java.util.List;
@Schema(description = "设备画像响应")
public class DeviceProfileResponse {
@Schema(description = "设备基础信息")
private DeviceProfileDeviceSection device;
@Schema(description = "设备硬件信息")
private DeviceProfileHardwareSection hardware;
@Schema(description = "软件版本信息")
private List<DeviceProfileSoftwareItem> software = new ArrayList<>();
public DeviceProfileDeviceSection getDevice() {
return device;
}
public void setDevice(DeviceProfileDeviceSection device) {
this.device = device;
}
public DeviceProfileHardwareSection getHardware() {
return hardware;
}
public void setHardware(DeviceProfileHardwareSection hardware) {
this.hardware = hardware;
}
public List<DeviceProfileSoftwareItem> getSoftware() {
return software;
}
public void setSoftware(List<DeviceProfileSoftwareItem> software) {
this.software = software;
}
}
@@ -0,0 +1,38 @@
package com.cisd.tms.modules.device.dto;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(description = "软件组件信息")
public class DeviceProfileSoftwareItem {
@Schema(description = "组件编码", example = "RABBITMQ")
private String componentCode;
@Schema(description = "组件名称", example = "RabbitMQ")
private String componentName;
@Schema(description = "组件版本", example = "RabbitMQ V3.8.21")
private String version;
public String getComponentCode() {
return componentCode;
}
public void setComponentCode(String componentCode) {
this.componentCode = componentCode;
}
public String getComponentName() {
return componentName;
}
public void setComponentName(String componentName) {
this.componentName = componentName;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
}
@@ -0,0 +1,42 @@
package com.cisd.tms.modules.device.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.ArrayList;
import java.util.List;
@Schema(description = "设备运行状态响应")
public class DeviceRuntimeStatusResponse {
@Schema(description = "硬件运行状态")
private HardwareRuntimeStatusSection hardware;
@Schema(description = "软件运行状态")
private List<SoftwareRuntimeStatusItem> software = new ArrayList<>();
@Schema(description = "采集时间", example = "2026-03-13T14:30:00+08:00")
private String collectedAt;
public HardwareRuntimeStatusSection getHardware() {
return hardware;
}
public void setHardware(HardwareRuntimeStatusSection hardware) {
this.hardware = hardware;
}
public List<SoftwareRuntimeStatusItem> getSoftware() {
return software;
}
public void setSoftware(List<SoftwareRuntimeStatusItem> software) {
this.software = software;
}
public String getCollectedAt() {
return collectedAt;
}
public void setCollectedAt(String collectedAt) {
this.collectedAt = collectedAt;
}
}
@@ -0,0 +1,51 @@
package com.cisd.tms.modules.device.dto;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(description = "硬件运行状态")
public class HardwareRuntimeStatusSection {
@Schema(description = "CPU 运行状态")
private UsageMetricStatus cpu;
@Schema(description = "内存运行状态")
private UsageMetricStatus memory;
@Schema(description = "磁盘运行状态")
private UsageMetricStatus disk;
@Schema(description = "加密卡运行状态")
private CryptoCardRuntimeStatus cryptoCard;
public UsageMetricStatus getCpu() {
return cpu;
}
public void setCpu(UsageMetricStatus cpu) {
this.cpu = cpu;
}
public UsageMetricStatus getMemory() {
return memory;
}
public void setMemory(UsageMetricStatus memory) {
this.memory = memory;
}
public UsageMetricStatus getDisk() {
return disk;
}
public void setDisk(UsageMetricStatus disk) {
this.disk = disk;
}
public CryptoCardRuntimeStatus getCryptoCard() {
return cryptoCard;
}
public void setCryptoCard(CryptoCardRuntimeStatus cryptoCard) {
this.cryptoCard = cryptoCard;
}
}
@@ -0,0 +1,62 @@
package com.cisd.tms.modules.device.dto;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(description = "软件运行状态项")
public class SoftwareRuntimeStatusItem {
@Schema(description = "组件编码", example = "RABBITMQ")
private String componentCode;
@Schema(description = "组件名称", example = "消息队列")
private String componentName;
@Schema(description = "展示名称", example = "RabbitMQ V3.8.21")
private String displayName;
@Schema(description = "状态", example = "NORMAL")
private String status;
@Schema(description = "状态说明", example = "正常")
private String message;
public String getComponentCode() {
return componentCode;
}
public void setComponentCode(String componentCode) {
this.componentCode = componentCode;
}
public String getComponentName() {
return componentName;
}
public void setComponentName(String componentName) {
this.componentName = componentName;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
@@ -0,0 +1,62 @@
package com.cisd.tms.modules.device.dto;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(description = "资源使用量状态")
public class UsageMetricStatus {
@Schema(description = "已使用容量(GB)", example = "23.55")
private Double usedGb;
@Schema(description = "总容量(GB)", example = "32.0")
private Double totalGb;
@Schema(description = "使用率百分比", example = "73.59")
private Double usagePercent;
@Schema(description = "状态", example = "NORMAL")
private String status;
@Schema(description = "状态说明", example = "正常")
private String message;
public Double getUsedGb() {
return usedGb;
}
public void setUsedGb(Double usedGb) {
this.usedGb = usedGb;
}
public Double getTotalGb() {
return totalGb;
}
public void setTotalGb(Double totalGb) {
this.totalGb = totalGb;
}
public Double getUsagePercent() {
return usagePercent;
}
public void setUsagePercent(Double usagePercent) {
this.usagePercent = usagePercent;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
@@ -0,0 +1,39 @@
package com.cisd.tms.modules.device.dto.crypto.request;
import jakarta.validation.constraints.Min;
public class CryptoBackupUserKeyInternalRequest {
@Min(0)
private int keyIndex;
@Min(0)
private int keyType;
@Min(1)
private int outBufferSize;
public int getKeyIndex() {
return keyIndex;
}
public void setKeyIndex(int keyIndex) {
this.keyIndex = keyIndex;
}
public int getKeyType() {
return keyType;
}
public void setKeyType(int keyType) {
this.keyType = keyType;
}
public int getOutBufferSize() {
return outBufferSize;
}
public void setOutBufferSize(int outBufferSize) {
this.outBufferSize = outBufferSize;
}
}
@@ -0,0 +1,40 @@
package com.cisd.tms.modules.device.dto.crypto.request;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
public class CryptoChangePrivateKeyPasswordInternalRequest {
@Min(0)
private int keyIndex;
@NotBlank
private String oldPasswordBase64;
@NotBlank
private String newPasswordBase64;
public int getKeyIndex() {
return keyIndex;
}
public void setKeyIndex(int keyIndex) {
this.keyIndex = keyIndex;
}
public String getOldPasswordBase64() {
return oldPasswordBase64;
}
public void setOldPasswordBase64(String oldPasswordBase64) {
this.oldPasswordBase64 = oldPasswordBase64;
}
public String getNewPasswordBase64() {
return newPasswordBase64;
}
public void setNewPasswordBase64(String newPasswordBase64) {
this.newPasswordBase64 = newPasswordBase64;
}
}
@@ -0,0 +1,40 @@
package com.cisd.tms.modules.device.dto.crypto.request;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
public class CryptoDigestInternalRequest {
@Min(1)
private int algId;
@NotBlank
private String dataBase64;
@Min(1)
private int outBufferSize = 64;
public int getAlgId() {
return algId;
}
public void setAlgId(int algId) {
this.algId = algId;
}
public String getDataBase64() {
return dataBase64;
}
public void setDataBase64(String dataBase64) {
this.dataBase64 = dataBase64;
}
public int getOutBufferSize() {
return outBufferSize;
}
public void setOutBufferSize(int outBufferSize) {
this.outBufferSize = outBufferSize;
}
}
@@ -0,0 +1,62 @@
package com.cisd.tms.modules.device.dto.crypto.request;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
public class CryptoExchangeEnvelopeEccInternalRequest {
@Min(0)
private int keyIndex;
@Min(1)
private int algId;
@NotBlank
private String publicKeyBase64;
@NotBlank
private String inputEnvelopeBase64;
@Min(1)
private int outBufferSize;
public int getKeyIndex() {
return keyIndex;
}
public void setKeyIndex(int keyIndex) {
this.keyIndex = keyIndex;
}
public int getAlgId() {
return algId;
}
public void setAlgId(int algId) {
this.algId = algId;
}
public String getPublicKeyBase64() {
return publicKeyBase64;
}
public void setPublicKeyBase64(String publicKeyBase64) {
this.publicKeyBase64 = publicKeyBase64;
}
public String getInputEnvelopeBase64() {
return inputEnvelopeBase64;
}
public void setInputEnvelopeBase64(String inputEnvelopeBase64) {
this.inputEnvelopeBase64 = inputEnvelopeBase64;
}
public int getOutBufferSize() {
return outBufferSize;
}
public void setOutBufferSize(int outBufferSize) {
this.outBufferSize = outBufferSize;
}
}
@@ -0,0 +1,51 @@
package com.cisd.tms.modules.device.dto.crypto.request;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
public class CryptoExchangeEnvelopeRsaInternalRequest {
@Min(0)
private int keyIndex;
@NotBlank
private String publicKeyBase64;
@NotBlank
private String inputEnvelopeBase64;
@Min(1)
private int outBufferSize;
public int getKeyIndex() {
return keyIndex;
}
public void setKeyIndex(int keyIndex) {
this.keyIndex = keyIndex;
}
public String getPublicKeyBase64() {
return publicKeyBase64;
}
public void setPublicKeyBase64(String publicKeyBase64) {
this.publicKeyBase64 = publicKeyBase64;
}
public String getInputEnvelopeBase64() {
return inputEnvelopeBase64;
}
public void setInputEnvelopeBase64(String inputEnvelopeBase64) {
this.inputEnvelopeBase64 = inputEnvelopeBase64;
}
public int getOutBufferSize() {
return outBufferSize;
}
public void setOutBufferSize(int outBufferSize) {
this.outBufferSize = outBufferSize;
}
}
@@ -0,0 +1,29 @@
package com.cisd.tms.modules.device.dto.crypto.request;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
public class CryptoFileCreateInternalRequest {
@NotBlank
private String fileName;
@Min(1)
private int fileSize;
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public int getFileSize() {
return fileSize;
}
public void setFileSize(int fileSize) {
this.fileSize = fileSize;
}
}
@@ -0,0 +1,17 @@
package com.cisd.tms.modules.device.dto.crypto.request;
import jakarta.validation.constraints.NotBlank;
public class CryptoFileDeleteInternalRequest {
@NotBlank
private String fileName;
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
}
@@ -0,0 +1,40 @@
package com.cisd.tms.modules.device.dto.crypto.request;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
public class CryptoFileReadInternalRequest {
@NotBlank
private String fileName;
@Min(0)
private int offset;
@Min(1)
private int readLength;
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public int getOffset() {
return offset;
}
public void setOffset(int offset) {
this.offset = offset;
}
public int getReadLength() {
return readLength;
}
public void setReadLength(int readLength) {
this.readLength = readLength;
}
}
@@ -0,0 +1,40 @@
package com.cisd.tms.modules.device.dto.crypto.request;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
public class CryptoFileWriteInternalRequest {
@NotBlank
private String fileName;
@Min(0)
private int offset;
@NotBlank
private String dataBase64;
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public int getOffset() {
return offset;
}
public void setOffset(int offset) {
this.offset = offset;
}
public String getDataBase64() {
return dataBase64;
}
public void setDataBase64(String dataBase64) {
this.dataBase64 = dataBase64;
}
}
@@ -0,0 +1,95 @@
package com.cisd.tms.modules.device.dto.crypto.request;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
public class CryptoGenerateAgreementDataAndKeyEccInternalRequest {
@Min(0)
private int iskIndex;
@Min(1)
private int keyBits;
@NotBlank
private String responseIdBase64;
@NotBlank
private String sponsorIdBase64;
@NotBlank
private String sponsorPublicKeyBase64;
@NotBlank
private String sponsorTmpPublicKeyBase64;
@Min(1)
private int responsePublicKeyBufferSize;
@Min(1)
private int responseTmpPublicKeyBufferSize;
public int getIskIndex() {
return iskIndex;
}
public void setIskIndex(int iskIndex) {
this.iskIndex = iskIndex;
}
public int getKeyBits() {
return keyBits;
}
public void setKeyBits(int keyBits) {
this.keyBits = keyBits;
}
public String getResponseIdBase64() {
return responseIdBase64;
}
public void setResponseIdBase64(String responseIdBase64) {
this.responseIdBase64 = responseIdBase64;
}
public String getSponsorIdBase64() {
return sponsorIdBase64;
}
public void setSponsorIdBase64(String sponsorIdBase64) {
this.sponsorIdBase64 = sponsorIdBase64;
}
public String getSponsorPublicKeyBase64() {
return sponsorPublicKeyBase64;
}
public void setSponsorPublicKeyBase64(String sponsorPublicKeyBase64) {
this.sponsorPublicKeyBase64 = sponsorPublicKeyBase64;
}
public String getSponsorTmpPublicKeyBase64() {
return sponsorTmpPublicKeyBase64;
}
public void setSponsorTmpPublicKeyBase64(String sponsorTmpPublicKeyBase64) {
this.sponsorTmpPublicKeyBase64 = sponsorTmpPublicKeyBase64;
}
public int getResponsePublicKeyBufferSize() {
return responsePublicKeyBufferSize;
}
public void setResponsePublicKeyBufferSize(int responsePublicKeyBufferSize) {
this.responsePublicKeyBufferSize = responsePublicKeyBufferSize;
}
public int getResponseTmpPublicKeyBufferSize() {
return responseTmpPublicKeyBufferSize;
}
public void setResponseTmpPublicKeyBufferSize(int responseTmpPublicKeyBufferSize) {
this.responseTmpPublicKeyBufferSize = responseTmpPublicKeyBufferSize;
}
}
@@ -0,0 +1,28 @@
package com.cisd.tms.modules.device.dto.crypto.request;
import jakarta.validation.constraints.Min;
public class CryptoGenerateKekInternalRequest {
@Min(1)
private int keyBits;
@Min(0)
private int keyIndex;
public int getKeyBits() {
return keyBits;
}
public void setKeyBits(int keyBits) {
this.keyBits = keyBits;
}
public int getKeyIndex() {
return keyIndex;
}
public void setKeyIndex(int keyIndex) {
this.keyIndex = keyIndex;
}
}
@@ -0,0 +1,39 @@
package com.cisd.tms.modules.device.dto.crypto.request;
import jakarta.validation.constraints.Min;
public class CryptoGenerateKeyPairInternalRequest {
@Min(1)
private int keyBits;
@Min(0)
private int keyIndex;
@Min(0)
private int keyType;
public int getKeyBits() {
return keyBits;
}
public void setKeyBits(int keyBits) {
this.keyBits = keyBits;
}
public int getKeyIndex() {
return keyIndex;
}
public void setKeyIndex(int keyIndex) {
this.keyIndex = keyIndex;
}
public int getKeyType() {
return keyType;
}
public void setKeyType(int keyType) {
this.keyType = keyType;
}
}
@@ -0,0 +1,106 @@
package com.cisd.tms.modules.device.dto.crypto.request;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
public class CryptoGenerateSessionKeyEccInternalRequest {
@Min(0)
private int iskIndex;
@Min(1)
private int keyBits;
@NotBlank
private String sponsorIdBase64;
@NotBlank
private String responseIdBase64;
@NotBlank
private String responsePublicKeyBase64;
@NotBlank
private String responseTmpPublicKeyBase64;
@Min(1)
private int sponsorPublicKeyBufferSize;
@Min(1)
private int sponsorTmpPublicKeyBufferSize;
@Min(1)
private int sessionKeyBufferSize;
public int getIskIndex() {
return iskIndex;
}
public void setIskIndex(int iskIndex) {
this.iskIndex = iskIndex;
}
public int getKeyBits() {
return keyBits;
}
public void setKeyBits(int keyBits) {
this.keyBits = keyBits;
}
public String getSponsorIdBase64() {
return sponsorIdBase64;
}
public void setSponsorIdBase64(String sponsorIdBase64) {
this.sponsorIdBase64 = sponsorIdBase64;
}
public String getResponseIdBase64() {
return responseIdBase64;
}
public void setResponseIdBase64(String responseIdBase64) {
this.responseIdBase64 = responseIdBase64;
}
public String getResponsePublicKeyBase64() {
return responsePublicKeyBase64;
}
public void setResponsePublicKeyBase64(String responsePublicKeyBase64) {
this.responsePublicKeyBase64 = responsePublicKeyBase64;
}
public String getResponseTmpPublicKeyBase64() {
return responseTmpPublicKeyBase64;
}
public void setResponseTmpPublicKeyBase64(String responseTmpPublicKeyBase64) {
this.responseTmpPublicKeyBase64 = responseTmpPublicKeyBase64;
}
public int getSponsorPublicKeyBufferSize() {
return sponsorPublicKeyBufferSize;
}
public void setSponsorPublicKeyBufferSize(int sponsorPublicKeyBufferSize) {
this.sponsorPublicKeyBufferSize = sponsorPublicKeyBufferSize;
}
public int getSponsorTmpPublicKeyBufferSize() {
return sponsorTmpPublicKeyBufferSize;
}
public void setSponsorTmpPublicKeyBufferSize(int sponsorTmpPublicKeyBufferSize) {
this.sponsorTmpPublicKeyBufferSize = sponsorTmpPublicKeyBufferSize;
}
public int getSessionKeyBufferSize() {
return sessionKeyBufferSize;
}
public void setSessionKeyBufferSize(int sessionKeyBufferSize) {
this.sessionKeyBufferSize = sessionKeyBufferSize;
}
}
@@ -0,0 +1,50 @@
package com.cisd.tms.modules.device.dto.crypto.request;
import jakarta.validation.constraints.Min;
public class CryptoGenerateStandardEccKeyPairInternalRequest {
@Min(1)
private int algId;
@Min(1)
private int keyBits;
@Min(1)
private int publicKeyBufferSize;
@Min(1)
private int privateKeyBufferSize;
public int getAlgId() {
return algId;
}
public void setAlgId(int algId) {
this.algId = algId;
}
public int getKeyBits() {
return keyBits;
}
public void setKeyBits(int keyBits) {
this.keyBits = keyBits;
}
public int getPublicKeyBufferSize() {
return publicKeyBufferSize;
}
public void setPublicKeyBufferSize(int publicKeyBufferSize) {
this.publicKeyBufferSize = publicKeyBufferSize;
}
public int getPrivateKeyBufferSize() {
return privateKeyBufferSize;
}
public void setPrivateKeyBufferSize(int privateKeyBufferSize) {
this.privateKeyBufferSize = privateKeyBufferSize;
}
}
@@ -0,0 +1,39 @@
package com.cisd.tms.modules.device.dto.crypto.request;
import jakarta.validation.constraints.Min;
public class CryptoGenerateStandardRsaKeyPairInternalRequest {
@Min(1)
private int keyBits;
@Min(1)
private int publicKeyBufferSize;
@Min(1)
private int privateKeyBufferSize;
public int getKeyBits() {
return keyBits;
}
public void setKeyBits(int keyBits) {
this.keyBits = keyBits;
}
public int getPublicKeyBufferSize() {
return publicKeyBufferSize;
}
public void setPublicKeyBufferSize(int publicKeyBufferSize) {
this.publicKeyBufferSize = publicKeyBufferSize;
}
public int getPrivateKeyBufferSize() {
return privateKeyBufferSize;
}
public void setPrivateKeyBufferSize(int privateKeyBufferSize) {
this.privateKeyBufferSize = privateKeyBufferSize;
}
}
@@ -0,0 +1,40 @@
package com.cisd.tms.modules.device.dto.crypto.request;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
public class CryptoHmacInternalRequest {
@Min(1)
private int algId;
@NotBlank
private String keyBase64;
@NotBlank
private String dataBase64;
public int getAlgId() {
return algId;
}
public void setAlgId(int algId) {
this.algId = algId;
}
public String getKeyBase64() {
return keyBase64;
}
public void setKeyBase64(String keyBase64) {
this.keyBase64 = keyBase64;
}
public String getDataBase64() {
return dataBase64;
}
public void setDataBase64(String dataBase64) {
this.dataBase64 = dataBase64;
}
}
@@ -0,0 +1,29 @@
package com.cisd.tms.modules.device.dto.crypto.request;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
public class CryptoImportEncryptedKeyPairEccInternalRequest {
@Min(0)
private int keyIndex;
@NotBlank
private String envelopedKeyBlobBase64;
public int getKeyIndex() {
return keyIndex;
}
public void setKeyIndex(int keyIndex) {
this.keyIndex = keyIndex;
}
public String getEnvelopedKeyBlobBase64() {
return envelopedKeyBlobBase64;
}
public void setEnvelopedKeyBlobBase64(String envelopedKeyBlobBase64) {
this.envelopedKeyBlobBase64 = envelopedKeyBlobBase64;
}
}
@@ -0,0 +1,51 @@
package com.cisd.tms.modules.device.dto.crypto.request;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
public class CryptoImportEncryptedKeyPairRsaInternalRequest {
@Min(0)
private int keyIndex;
@Min(1)
private int symAlgId;
@NotBlank
private String wrappedKeyBase64;
@NotBlank
private String encryptedDataBase64;
public int getKeyIndex() {
return keyIndex;
}
public void setKeyIndex(int keyIndex) {
this.keyIndex = keyIndex;
}
public int getSymAlgId() {
return symAlgId;
}
public void setSymAlgId(int symAlgId) {
this.symAlgId = symAlgId;
}
public String getWrappedKeyBase64() {
return wrappedKeyBase64;
}
public void setWrappedKeyBase64(String wrappedKeyBase64) {
this.wrappedKeyBase64 = wrappedKeyBase64;
}
public String getEncryptedDataBase64() {
return encryptedDataBase64;
}
public void setEncryptedDataBase64(String encryptedDataBase64) {
this.encryptedDataBase64 = encryptedDataBase64;
}
}
@@ -0,0 +1,29 @@
package com.cisd.tms.modules.device.dto.crypto.request;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
public class CryptoImportKekInternalRequest {
@Min(0)
private int keyIndex;
@NotBlank
private String keyDataBase64;
public int getKeyIndex() {
return keyIndex;
}
public void setKeyIndex(int keyIndex) {
this.keyIndex = keyIndex;
}
public String getKeyDataBase64() {
return keyDataBase64;
}
public void setKeyDataBase64(String keyDataBase64) {
this.keyDataBase64 = keyDataBase64;
}
}
@@ -0,0 +1,51 @@
package com.cisd.tms.modules.device.dto.crypto.request;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
public class CryptoImportKeyPairInternalRequest {
@Min(0)
private int keyIndex;
@Min(0)
private int keyType;
@NotBlank
private String publicKeyBase64;
@NotBlank
private String privateKeyBase64;
public int getKeyIndex() {
return keyIndex;
}
public void setKeyIndex(int keyIndex) {
this.keyIndex = keyIndex;
}
public int getKeyType() {
return keyType;
}
public void setKeyType(int keyType) {
this.keyType = keyType;
}
public String getPublicKeyBase64() {
return publicKeyBase64;
}
public void setPublicKeyBase64(String publicKeyBase64) {
this.publicKeyBase64 = publicKeyBase64;
}
public String getPrivateKeyBase64() {
return privateKeyBase64;
}
public void setPrivateKeyBase64(String privateKeyBase64) {
this.privateKeyBase64 = privateKeyBase64;
}
}
@@ -0,0 +1,28 @@
package com.cisd.tms.modules.device.dto.crypto.request;
import jakarta.validation.constraints.NotBlank;
public class CryptoInitIdentifyInternalRequest {
@NotBlank
private String oldPinBase64;
@NotBlank
private String newPinBase64;
public String getOldPinBase64() {
return oldPinBase64;
}
public void setOldPinBase64(String oldPinBase64) {
this.oldPinBase64 = oldPinBase64;
}
public String getNewPinBase64() {
return newPinBase64;
}
public void setNewPinBase64(String newPinBase64) {
this.newPinBase64 = newPinBase64;
}
}
@@ -0,0 +1,39 @@
package com.cisd.tms.modules.device.dto.crypto.request;
import jakarta.validation.constraints.Min;
public class CryptoKekComponentExportInternalRequest {
@Min(0)
private int keyIndex;
@Min(0)
private int componentNum;
@Min(1)
private int componentSize;
public int getKeyIndex() {
return keyIndex;
}
public void setKeyIndex(int keyIndex) {
this.keyIndex = keyIndex;
}
public int getComponentNum() {
return componentNum;
}
public void setComponentNum(int componentNum) {
this.componentNum = componentNum;
}
public int getComponentSize() {
return componentSize;
}
public void setComponentSize(int componentSize) {
this.componentSize = componentSize;
}
}

Some files were not shown because too many files have changed in this diff Show More