1063 lines
21 KiB
Markdown
1063 lines
21 KiB
Markdown
# 接口加密方案设计(适配 MrleeAdmin / Madong 项目)
|
||||
|
|
|
|||
|
|
## 1. 文档目标
|
|||
|
|
|
|||
|
|
本文档用于为当前项目设计一套**符合现有架构、可渐进落地、支持前后台统一接入**的接口加密方案。
|
|||
|
|
|
|||
|
|
设计目标:
|
|||
|
|
|
|||
|
|
- 在**不破坏现有接口返回规范**的前提下,为 API 增加可配置的请求/响应加密能力。
|
|||
|
|
- 同时适配:
|
|||
|
|
- `backend`(Webman / PHP)
|
|||
|
|
- `frontend/admin`(Vue 3 + Axios)
|
|||
|
|
- `frontend/web`(Nuxt 4 + fetch 封装)
|
|||
|
|
- 支持:
|
|||
|
|
- **关闭加密**
|
|||
|
|
- **部分加密**
|
|||
|
|
- **全局加密**
|
|||
|
|
- 支持后续扩展:
|
|||
|
|
- 密钥轮换
|
|||
|
|
- 按模块排除
|
|||
|
|
- 注解/Attribute 标记接口是否加密
|
|||
|
|
- 管理端可视化配置页面
|
|||
|
|
|
|||
|
|
本文档是**总体方案设计文档**,不是直接实现文档;但所有设计都以当前项目真实结构为前提,可直接指导后续开发。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 2. 当前项目现状结论
|
|||
|
|
|
|||
|
|
### 2.1 当前接口未做统一加密
|
|||
|
|
|
|||
|
|
经核实,当前项目主链路没有统一接口加密能力。
|
|||
|
|
|
|||
|
|
现状如下:
|
|||
|
|
|
|||
|
|
- 后端统一返回封装 `backend/core/tool/Json.php`
|
|||
|
|
- 直接输出标准 JSON:`code / msg / data`
|
|||
|
|
- 没有 AES / RSA / 自定义密文包装逻辑
|
|||
|
|
- 前台 Web 请求封装 `frontend/web/app/utils/request.ts`
|
|||
|
|
- 直接 `response.json()`
|
|||
|
|
- 按 `code / msg / data` 处理
|
|||
|
|
- 没有解密逻辑
|
|||
|
|
- Admin 请求封装 `frontend/admin/src/utils/http/index.ts`
|
|||
|
|
- `transformResponse` 直接 `JSON.parse`
|
|||
|
|
- 响应拦截器直接读取 `response.data.code / msg / data`
|
|||
|
|
- 没有解密逻辑
|
|||
|
|
|
|||
|
|
### 2.2 当前项目没有代码层强制 HTTPS
|
|||
|
|
|
|||
|
|
经核实,当前仓库代码没有发现全局强制 HTTPS 的逻辑:
|
|||
|
|
|
|||
|
|
- 没有发现统一 `forceScheme('https')`
|
|||
|
|
- 没有发现 HTTP -> HTTPS 跳转中间件
|
|||
|
|
- 前端 `.env` 使用相对 API 路径:
|
|||
|
|
- `frontend/admin/.env.production` 中 `VITE_API_URL=/adminapi`
|
|||
|
|
- `frontend/web/.env` 中 `VITE_APP_BASE_URL=/api`
|
|||
|
|
- `backend/config/session.php` 中 `secure => false`
|
|||
|
|
|
|||
|
|
结论:
|
|||
|
|
|
|||
|
|
- 当前生产环境是否使用 HTTPS,取决于 **Nginx / 反向代理 / 宝塔站点 / CDN** 等部署层配置。
|
|||
|
|
- 接口加密方案**不能替代 HTTPS**,建议生产环境必须启用 HTTPS。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 3. 设计原则
|
|||
|
|
|
|||
|
|
### 3.1 不使用“前端持有私钥”方案
|
|||
|
|
|
|||
|
|
必须明确:
|
|||
|
|
|
|||
|
|
- **RSA 私钥不能下发到前端**
|
|||
|
|
- 前端只能持有 **RSA 公钥**
|
|||
|
|
- 后端持有 **RSA 私钥**
|
|||
|
|
|
|||
|
|
如果将私钥配置到浏览器侧,则任何接口加密都失去意义。
|
|||
|
|
|
|||
|
|
### 3.2 采用 RSA + AES 混合加密
|
|||
|
|
|
|||
|
|
不直接使用 RSA 加密整个请求/响应包体,原因:
|
|||
|
|
|
|||
|
|
- RSA 性能较差
|
|||
|
|
- RSA 明文长度有限制
|
|||
|
|
- JSON 包体、分页数据、列表数据通常不适合直接用 RSA 全量加密
|
|||
|
|
|
|||
|
|
采用混合加密:
|
|||
|
|
|
|||
|
|
- **请求体 / 响应体**:使用 AES 加密
|
|||
|
|
- **AES 会话密钥**:使用 RSA 公钥加密传输
|
|||
|
|
- **AES 解密密钥**:后端使用 RSA 私钥解出
|
|||
|
|
|
|||
|
|
这是兼顾安全性、可用性、性能和扩展性的主流方案。
|
|||
|
|
|
|||
|
|
### 3.3 保持现有响应协议语义不变
|
|||
|
|
|
|||
|
|
当前项目大量前后端逻辑依赖如下结构:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"code": 0,
|
|||
|
|
"msg": "获取成功",
|
|||
|
|
"data": {}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
加密改造后,必须尽量保持**业务语义兼容**,避免影响错误处理、Token 刷新、UI 消息提示、分页通用逻辑。
|
|||
|
|
|
|||
|
|
### 3.4 先支持 JSON 接口,再扩展其他流类型
|
|||
|
|
|
|||
|
|
第一阶段仅支持:
|
|||
|
|
|
|||
|
|
- `application/json`
|
|||
|
|
- 普通 GET / POST / PUT / DELETE 业务接口
|
|||
|
|
|
|||
|
|
第一阶段不直接纳入:
|
|||
|
|
|
|||
|
|
- 文件上传 `multipart/form-data`
|
|||
|
|
- 文件下载 `blob / stream`
|
|||
|
|
- SSE / 长连接
|
|||
|
|
- 第三方回调接口
|
|||
|
|
- Swagger / OpenAPI 文档接口
|
|||
|
|
|
|||
|
|
这些接口应通过**排除策略**先保持明文。
|
|||
|
|
|
|||
|
|
### 3.5 渐进落地,先可切换后全量接入
|
|||
|
|
|
|||
|
|
建议分阶段:
|
|||
|
|
|
|||
|
|
- 第一阶段:支持开关和部分加密
|
|||
|
|
- 第二阶段:支持全局加密
|
|||
|
|
- 第三阶段:补齐密钥轮换、排除规则、监控、告警
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 4. 目标能力范围
|
|||
|
|
|
|||
|
|
方案最终应支持以下能力:
|
|||
|
|
|
|||
|
|
### 4.1 加密开关
|
|||
|
|
|
|||
|
|
- 关闭
|
|||
|
|
- 部分加密
|
|||
|
|
- 全局加密
|
|||
|
|
|
|||
|
|
### 4.2 加密方向
|
|||
|
|
|
|||
|
|
- 请求体加密
|
|||
|
|
- 响应体加密
|
|||
|
|
|
|||
|
|
说明:
|
|||
|
|
|
|||
|
|
- 只做响应加密意义有限
|
|||
|
|
- 推荐请求与响应一起做
|
|||
|
|
|
|||
|
|
### 4.3 前后端统一接入
|
|||
|
|
|
|||
|
|
- Admin 端统一请求封装接入
|
|||
|
|
- Web 端统一请求封装接入
|
|||
|
|
- 后端统一请求解密中间件 + 响应加密中间件接入
|
|||
|
|
|
|||
|
|
### 4.4 接口排除与例外能力
|
|||
|
|
|
|||
|
|
需要支持排除:
|
|||
|
|
|
|||
|
|
- 登录前置公钥获取接口
|
|||
|
|
- Token 刷新接口
|
|||
|
|
- 文件上传/下载接口
|
|||
|
|
- SSE 接口
|
|||
|
|
- 第三方回调接口
|
|||
|
|
- OpenAPI / Swagger 文档接口
|
|||
|
|
|
|||
|
|
### 4.5 密钥管理能力
|
|||
|
|
|
|||
|
|
- RSA 密钥对生成
|
|||
|
|
- RSA 公钥发布
|
|||
|
|
- RSA 私钥仅服务端存储
|
|||
|
|
- 支持重新生成密钥
|
|||
|
|
- 后续支持密钥版本号和轮换
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 5. 总体架构设计
|
|||
|
|
|
|||
|
|
### 5.1 核心流程
|
|||
|
|
|
|||
|
|
#### 请求流程
|
|||
|
|
|
|||
|
|
1. 前端生成一次性 AES Key + IV
|
|||
|
|
2. 前端使用 AES 加密业务请求数据
|
|||
|
|
3. 前端使用 RSA 公钥加密 AES Key(必要时也包含 IV)
|
|||
|
|
4. 前端将密文提交给后端
|
|||
|
|
5. 后端使用 RSA 私钥解出 AES Key
|
|||
|
|
6. 后端使用 AES 解密业务数据
|
|||
|
|
7. 后端将解密结果重新写回请求上下文,供控制器正常读取
|
|||
|
|
|
|||
|
|
#### 响应流程
|
|||
|
|
|
|||
|
|
1. 控制器照常返回 `Json::success()` / `Json::fail()`
|
|||
|
|
2. 响应加密中间件判断当前接口是否需要加密
|
|||
|
|
3. 将原始 JSON 响应体用 AES 加密
|
|||
|
|
4. 按统一密文结构返回前端
|
|||
|
|
5. 前端响应拦截器识别密文结构并解密
|
|||
|
|
6. 还原为原始 `code / msg / data` 结构,再继续走现有业务流程
|
|||
|
|
|
|||
|
|
### 5.2 推荐密文协议结构
|
|||
|
|
|
|||
|
|
#### 请求体结构
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"encrypted": true,
|
|||
|
|
"algorithm": "rsa-aes",
|
|||
|
|
"key_version": "v1",
|
|||
|
|
"key": "<RSA加密后的AES密钥>",
|
|||
|
|
"iv": "<Base64 IV 或并入 key 元数据>",
|
|||
|
|
"data": "<AES加密后的业务密文>",
|
|||
|
|
"timestamp": 1720000000,
|
|||
|
|
"nonce": "random-string"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 响应体结构
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"encrypted": true,
|
|||
|
|
"algorithm": "rsa-aes",
|
|||
|
|
"key_version": "v1",
|
|||
|
|
"data": "<AES加密后的响应密文>"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
说明:
|
|||
|
|
|
|||
|
|
- 如果请求与响应共用同一个 AES Key,则响应体中可以不再返回 `key`
|
|||
|
|
- 如果设计为响应重新生成一次性响应 AES Key,则响应体还要额外返回新的加密 key
|
|||
|
|
- 对本项目第一阶段,建议:
|
|||
|
|
- **响应复用请求里的 AES Key**
|
|||
|
|
- 可降低协议复杂度
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 6. 加密算法建议
|
|||
|
|
|
|||
|
|
### 6.1 RSA 方案
|
|||
|
|
|
|||
|
|
建议:
|
|||
|
|
|
|||
|
|
- `RSA 2048` 起步
|
|||
|
|
- 填充模式建议:`OAEP`
|
|||
|
|
- 密钥格式:PEM
|
|||
|
|
|
|||
|
|
用途:
|
|||
|
|
|
|||
|
|
- 仅用于加密 AES 会话密钥
|
|||
|
|
- 不直接加密大包体 JSON
|
|||
|
|
|
|||
|
|
### 6.2 AES 方案
|
|||
|
|
|
|||
|
|
建议:
|
|||
|
|
|
|||
|
|
- `AES-256-CBC` 或 `AES-256-GCM`
|
|||
|
|
|
|||
|
|
建议优先级:
|
|||
|
|
|
|||
|
|
- 如果当前 PHP / 前端依赖兼容良好,优先 `AES-256-GCM`
|
|||
|
|
- 如果需要兼顾实现稳定性与现有库接入复杂度,可先落地 `AES-256-CBC`
|
|||
|
|
|
|||
|
|
说明:
|
|||
|
|
|
|||
|
|
- `GCM` 更现代,带认证标签
|
|||
|
|
- `CBC` 更容易快速落地,但需要额外做好完整性校验
|
|||
|
|
|
|||
|
|
### 6.3 编码格式
|
|||
|
|
|
|||
|
|
建议统一:
|
|||
|
|
|
|||
|
|
- 二进制密文转 `Base64`
|
|||
|
|
- JSON 结构继续使用标准 JSON
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 7. 本项目推荐的实现分层
|
|||
|
|
|
|||
|
|
### 7.1 后端分层设计
|
|||
|
|
|
|||
|
|
建议新增以下模块:
|
|||
|
|
|
|||
|
|
#### 1. 配置层
|
|||
|
|
|
|||
|
|
建议新增:
|
|||
|
|
|
|||
|
|
- `backend/config/api_encrypt.php`
|
|||
|
|
|
|||
|
|
用途:
|
|||
|
|
|
|||
|
|
- 全局开关
|
|||
|
|
- 加密范围
|
|||
|
|
- 排除规则
|
|||
|
|
- 密钥读取配置
|
|||
|
|
- 算法配置
|
|||
|
|
|
|||
|
|
#### 2. 密钥服务层
|
|||
|
|
|
|||
|
|
建议新增:
|
|||
|
|
|
|||
|
|
- `backend/core/security/ApiEncrypt/KeyManager.php`
|
|||
|
|
- `backend/core/security/ApiEncrypt/CryptoService.php`
|
|||
|
|
|
|||
|
|
职责:
|
|||
|
|
|
|||
|
|
- 读取公钥/私钥
|
|||
|
|
- 生成密钥对
|
|||
|
|
- RSA 加解密
|
|||
|
|
- AES 加解密
|
|||
|
|
|
|||
|
|
#### 3. 请求解密中间件
|
|||
|
|
|
|||
|
|
建议新增:
|
|||
|
|
|
|||
|
|
- `backend/app/middleware/DecryptApiRequestMiddleware.php`
|
|||
|
|
|
|||
|
|
职责:
|
|||
|
|
|
|||
|
|
- 判断当前请求是否需要解密
|
|||
|
|
- 解密请求体
|
|||
|
|
- 将解密后的 JSON 数据重新注入 Request
|
|||
|
|
- 解密失败时统一返回错误响应
|
|||
|
|
|
|||
|
|
#### 4. 响应加密中间件
|
|||
|
|
|
|||
|
|
建议新增:
|
|||
|
|
|
|||
|
|
- `backend/app/middleware/EncryptApiResponseMiddleware.php`
|
|||
|
|
|
|||
|
|
职责:
|
|||
|
|
|
|||
|
|
- 判断当前响应是否需要加密
|
|||
|
|
- 对 `Json::success / Json::fail` 的响应体进行加密包装
|
|||
|
|
- 排除文件流、SSE、下载等响应类型
|
|||
|
|
|
|||
|
|
#### 5. 注解 / Attribute
|
|||
|
|
|
|||
|
|
建议新增:
|
|||
|
|
|
|||
|
|
- `backend/core/attribute/EncryptResponse.php`
|
|||
|
|
- `backend/core/attribute/SkipApiEncrypt.php`
|
|||
|
|
|
|||
|
|
职责:
|
|||
|
|
|
|||
|
|
- 部分加密模式下,用于标记哪些接口需要加密
|
|||
|
|
- 某些接口在全局加密时仍需排除
|
|||
|
|
|
|||
|
|
### 7.2 前端分层设计
|
|||
|
|
|
|||
|
|
#### Admin 端
|
|||
|
|
|
|||
|
|
接入点:
|
|||
|
|
|
|||
|
|
- `frontend/admin/src/utils/http/index.ts`
|
|||
|
|
|
|||
|
|
建议新增:
|
|||
|
|
|
|||
|
|
- `frontend/admin/src/utils/security/api-encrypt.ts`
|
|||
|
|
|
|||
|
|
职责:
|
|||
|
|
|
|||
|
|
- 获取公钥
|
|||
|
|
- 生成 AES Key
|
|||
|
|
- 请求加密
|
|||
|
|
- 响应解密
|
|||
|
|
- 根据配置判断是否对当前请求启用加密
|
|||
|
|
|
|||
|
|
#### Web 端
|
|||
|
|
|
|||
|
|
接入点:
|
|||
|
|
|
|||
|
|
- `frontend/web/app/utils/request.ts`
|
|||
|
|
|
|||
|
|
建议新增:
|
|||
|
|
|
|||
|
|
- `frontend/web/app/utils/security/api-encrypt.ts`
|
|||
|
|
|
|||
|
|
职责同上。
|
|||
|
|
|
|||
|
|
#### 共通原则
|
|||
|
|
|
|||
|
|
- 不直接把加密逻辑散落在业务页面
|
|||
|
|
- 统一在请求封装层处理
|
|||
|
|
- 页面和业务 API 文件无感知
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 8. 配置设计
|
|||
|
|
|
|||
|
|
### 8.1 后端全局配置文件
|
|||
|
|
|
|||
|
|
建议新增:
|
|||
|
|
|
|||
|
|
`backend/config/api_encrypt.php`
|
|||
|
|
|
|||
|
|
建议结构:
|
|||
|
|
|
|||
|
|
```php
|
|||
|
|
return [
|
|||
|
|
'enabled' => env('API_ENCRYPT_ENABLED', false),
|
|||
|
|
'scope' => env('API_ENCRYPT_SCOPE', 'off'), // off | partial | all
|
|||
|
|
'algorithm' => env('API_ENCRYPT_ALGORITHM', 'rsa-aes'),
|
|||
|
|
'rsa_public_key_path' => env('API_ENCRYPT_RSA_PUBLIC_KEY_PATH', base_path('runtime/keys/api_encrypt_public.pem')),
|
|||
|
|
'rsa_private_key_path' => env('API_ENCRYPT_RSA_PRIVATE_KEY_PATH', base_path('runtime/keys/api_encrypt_private.pem')),
|
|||
|
|
'aes_mode' => env('API_ENCRYPT_AES_MODE', 'AES-256-CBC'),
|
|||
|
|
'exclude_paths' => [
|
|||
|
|
'/auth/public-key',
|
|||
|
|
'/auth/refresh',
|
|||
|
|
'/upload',
|
|||
|
|
'/swagger',
|
|||
|
|
],
|
|||
|
|
];
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 8.2 环境变量设计
|
|||
|
|
|
|||
|
|
建议新增:
|
|||
|
|
|
|||
|
|
```env
|
|||
|
|
API_ENCRYPT_ENABLED=false
|
|||
|
|
API_ENCRYPT_SCOPE=off
|
|||
|
|
API_ENCRYPT_ALGORITHM=rsa-aes
|
|||
|
|
API_ENCRYPT_AES_MODE=AES-256-CBC
|
|||
|
|
API_ENCRYPT_RSA_PUBLIC_KEY_PATH=runtime/keys/api_encrypt_public.pem
|
|||
|
|
API_ENCRYPT_RSA_PRIVATE_KEY_PATH=runtime/keys/api_encrypt_private.pem
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 8.3 动态配置设计
|
|||
|
|
|
|||
|
|
如果后续要做成你截图里的后台配置页,建议将以下配置支持数据库存储:
|
|||
|
|
|
|||
|
|
- 是否启用接口加密
|
|||
|
|
- 加密范围:`partial / all`
|
|||
|
|
- 当前密钥版本号
|
|||
|
|
- RSA 公钥展示
|
|||
|
|
- RSA 私钥只允许后端存储,不在前端展示原文
|
|||
|
|
|
|||
|
|
说明:
|
|||
|
|
|
|||
|
|
- 如果需要“重新生成密钥”,由后端管理命令或管理接口生成
|
|||
|
|
- 页面只触发生成动作,不直接在浏览器生成私钥
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 9. 加密范围设计
|
|||
|
|
|
|||
|
|
### 9.1 模式一:关闭
|
|||
|
|
|
|||
|
|
- 所有接口明文传输
|
|||
|
|
- 保持当前项目行为
|
|||
|
|
|
|||
|
|
### 9.2 模式二:部分加密
|
|||
|
|
|
|||
|
|
只加密满足以下条件的接口:
|
|||
|
|
|
|||
|
|
- 控制器方法标记了 `#[EncryptResponse]`
|
|||
|
|
- 或者请求经过明确的加密白名单匹配
|
|||
|
|
|
|||
|
|
适用场景:
|
|||
|
|
|
|||
|
|
- 登录
|
|||
|
|
- 用户资料
|
|||
|
|
- 支付前置接口
|
|||
|
|
- 涉及敏感个人信息的查询与更新
|
|||
|
|
|
|||
|
|
### 9.3 模式三:全局加密
|
|||
|
|
|
|||
|
|
默认所有 JSON API 都加密,以下接口默认排除:
|
|||
|
|
|
|||
|
|
- 公钥获取接口
|
|||
|
|
- 登录前预检接口
|
|||
|
|
- Token 刷新
|
|||
|
|
- 文件上传
|
|||
|
|
- 文件下载
|
|||
|
|
- SSE
|
|||
|
|
- Swagger/OpenAPI 文档接口
|
|||
|
|
- 第三方回调
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 10. 必须排除的接口类型
|
|||
|
|
|
|||
|
|
### 10.1 公钥获取接口
|
|||
|
|
|
|||
|
|
需要提供一个**明文可访问**的公钥接口,例如:
|
|||
|
|
|
|||
|
|
- `GET /system/security/api-encrypt/public-key`
|
|||
|
|
|
|||
|
|
返回:
|
|||
|
|
|
|||
|
|
- `key_version`
|
|||
|
|
- `public_key`
|
|||
|
|
- `enabled`
|
|||
|
|
- `scope`
|
|||
|
|
|
|||
|
|
该接口必须明文,否则前端无法开始第一次加密通信。
|
|||
|
|
|
|||
|
|
### 10.2 Token 刷新接口
|
|||
|
|
|
|||
|
|
建议第一阶段排除:
|
|||
|
|
|
|||
|
|
- `/auth/refresh`
|
|||
|
|
|
|||
|
|
原因:
|
|||
|
|
|
|||
|
|
- Token 过期后的刷新链路要尽量简单可靠
|
|||
|
|
- 避免与解密失败 / Token 失效形成耦合死循环
|
|||
|
|
|
|||
|
|
### 10.3 文件上传接口
|
|||
|
|
|
|||
|
|
如:
|
|||
|
|
|
|||
|
|
- `multipart/form-data`
|
|||
|
|
|
|||
|
|
原因:
|
|||
|
|
|
|||
|
|
- 上传流不适合直接套 JSON 密文结构
|
|||
|
|
- 第一阶段先排除
|
|||
|
|
|
|||
|
|
### 10.4 文件下载 / Blob / 导出接口
|
|||
|
|
|
|||
|
|
原因:
|
|||
|
|
|
|||
|
|
- Admin 和 Web 当前请求封装对 `blob` 有专门处理
|
|||
|
|
- 这类返回体不是标准 JSON,不应进入统一加密中间件
|
|||
|
|
|
|||
|
|
### 10.5 SSE / 流式接口
|
|||
|
|
|
|||
|
|
原因:
|
|||
|
|
|
|||
|
|
- SSE 是持续输出,不适合标准 JSON 包装加密
|
|||
|
|
|
|||
|
|
### 10.6 Swagger / OpenAPI 接口
|
|||
|
|
|
|||
|
|
原因:
|
|||
|
|
|
|||
|
|
- 文档接口要便于调试、对接和生成 SDK
|
|||
|
|
|
|||
|
|
### 10.7 第三方回调接口
|
|||
|
|
|
|||
|
|
原因:
|
|||
|
|
|
|||
|
|
- 第三方平台无法适配你本项目自定义加密协议
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 11. 前端接入设计
|
|||
|
|
|
|||
|
|
### 11.1 Admin 端接入点
|
|||
|
|
|
|||
|
|
目标文件:
|
|||
|
|
|
|||
|
|
- `frontend/admin/src/utils/http/index.ts`
|
|||
|
|
|
|||
|
|
建议改造点:
|
|||
|
|
|
|||
|
|
#### 请求前
|
|||
|
|
|
|||
|
|
- 判断当前接口是否启用加密
|
|||
|
|
- 若启用:
|
|||
|
|
- 获取缓存公钥
|
|||
|
|
- 生成 AES Key / IV
|
|||
|
|
- 加密请求体
|
|||
|
|
- 用 RSA 公钥加密 AES Key
|
|||
|
|
- 将请求体替换为密文协议
|
|||
|
|
- 标记请求头,例如:
|
|||
|
|
- `X-Api-Encrypt: 1`
|
|||
|
|
- `X-Api-Encrypt-Version: v1`
|
|||
|
|
|
|||
|
|
#### 响应后
|
|||
|
|
|
|||
|
|
- 判断响应体是否为加密协议
|
|||
|
|
- 若是:
|
|||
|
|
- 使用本次请求缓存的 AES Key 解密响应
|
|||
|
|
- 还原为 `code / msg / data`
|
|||
|
|
- 再进入现有业务处理链
|
|||
|
|
|
|||
|
|
### 11.2 Web 端接入点
|
|||
|
|
|
|||
|
|
目标文件:
|
|||
|
|
|
|||
|
|
- `frontend/web/app/utils/request.ts`
|
|||
|
|
|
|||
|
|
改造逻辑与 Admin 一致。
|
|||
|
|
|
|||
|
|
### 11.3 公钥缓存策略
|
|||
|
|
|
|||
|
|
建议:
|
|||
|
|
|
|||
|
|
- 启动后首次调用公钥接口并缓存
|
|||
|
|
- 缓存内容包含:
|
|||
|
|
- `key_version`
|
|||
|
|
- `public_key`
|
|||
|
|
- `expire_at`(可选)
|
|||
|
|
|
|||
|
|
当出现以下情况时刷新公钥:
|
|||
|
|
|
|||
|
|
- 服务端返回“密钥版本不匹配”
|
|||
|
|
- 公钥缓存过期
|
|||
|
|
- 管理员手动重新生成密钥
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 12. 后端接入设计
|
|||
|
|
|
|||
|
|
### 12.1 请求解密中间件
|
|||
|
|
|
|||
|
|
职责:
|
|||
|
|
|
|||
|
|
- 识别密文协议
|
|||
|
|
- 验证请求头与请求体格式
|
|||
|
|
- RSA 解出 AES Key
|
|||
|
|
- AES 解出业务 JSON
|
|||
|
|
- 写回 `$request` 可读取结构
|
|||
|
|
- 校验时间戳 / nonce(防重放,第二阶段可增强)
|
|||
|
|
|
|||
|
|
失败返回建议:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"code": 400,
|
|||
|
|
"msg": "请求解密失败",
|
|||
|
|
"data": null
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 12.2 响应加密中间件
|
|||
|
|
|
|||
|
|
职责:
|
|||
|
|
|
|||
|
|
- 仅拦截 JSON 响应
|
|||
|
|
- 判断当前接口是否需加密
|
|||
|
|
- 读取原始响应体
|
|||
|
|
- 使用当前请求上下文里的 AES Key 对响应整体 JSON 加密
|
|||
|
|
- 替换成密文结构返回
|
|||
|
|
|
|||
|
|
### 12.3 中间件挂载建议
|
|||
|
|
|
|||
|
|
当前项目全局中间件在:
|
|||
|
|
|
|||
|
|
- `backend/config/middleware.php`
|
|||
|
|
|
|||
|
|
建议:
|
|||
|
|
|
|||
|
|
- 先作为**主项目全局中间件**挂载
|
|||
|
|
- 中间件内部再根据:
|
|||
|
|
- 配置开关
|
|||
|
|
- 路由前缀
|
|||
|
|
- 排除名单
|
|||
|
|
- Attribute
|
|||
|
|
做是否启用的判断
|
|||
|
|
|
|||
|
|
推荐顺序:
|
|||
|
|
|
|||
|
|
1. 跨域 / 语言中间件
|
|||
|
|
2. 请求解密中间件
|
|||
|
|
3. 业务鉴权 / Token 验证中间件
|
|||
|
|
4. 控制器处理
|
|||
|
|
5. 响应加密中间件
|
|||
|
|
|
|||
|
|
原因:
|
|||
|
|
|
|||
|
|
- 鉴权前应拿到解密后的真实请求参数
|
|||
|
|
- 响应加密应放在业务执行后
|
|||
|
|
|
|||
|
|
### 12.4 Attribute 风格保持一致
|
|||
|
|
|
|||
|
|
当前项目已大量使用 PHP Attribute,例如:
|
|||
|
|
|
|||
|
|
- `#[AllowAnonymous]`
|
|||
|
|
- OpenAPI `#[OA\Get] / #[OA\Post]`
|
|||
|
|
|
|||
|
|
因此接口加密标记也建议采用 Attribute 风格,而不是单独再引入一套注释体系。
|
|||
|
|
|
|||
|
|
建议:
|
|||
|
|
|
|||
|
|
- `#[EncryptResponse]`
|
|||
|
|
- `#[SkipApiEncrypt]`
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 13. 与现有返回结构的兼容策略
|
|||
|
|
|
|||
|
|
### 13.1 明文模式
|
|||
|
|
|
|||
|
|
仍返回:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"code": 0,
|
|||
|
|
"msg": "成功",
|
|||
|
|
"data": {}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 13.2 密文模式
|
|||
|
|
|
|||
|
|
传输层返回:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"encrypted": true,
|
|||
|
|
"algorithm": "rsa-aes",
|
|||
|
|
"key_version": "v1",
|
|||
|
|
"data": "..."
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
前端解密后,还原为:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"code": 0,
|
|||
|
|
"msg": "成功",
|
|||
|
|
"data": {}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
这样可以最大程度复用当前:
|
|||
|
|
|
|||
|
|
- Admin 响应拦截器
|
|||
|
|
- Web 请求封装错误处理
|
|||
|
|
- 页面业务逻辑
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 14. 密钥管理设计
|
|||
|
|
|
|||
|
|
### 14.1 密钥存储原则
|
|||
|
|
|
|||
|
|
- **公钥**:可向前端公开
|
|||
|
|
- **私钥**:仅允许服务端读取
|
|||
|
|
- **私钥绝不进入前端页面、接口返回、构建产物**
|
|||
|
|
|
|||
|
|
### 14.2 密钥存储位置建议
|
|||
|
|
|
|||
|
|
建议文件存储:
|
|||
|
|
|
|||
|
|
- `runtime/keys/api_encrypt_public.pem`
|
|||
|
|
- `runtime/keys/api_encrypt_private.pem`
|
|||
|
|
|
|||
|
|
也可后续支持:
|
|||
|
|
|
|||
|
|
- 数据库存储公钥元信息
|
|||
|
|
- 私钥文件仅存磁盘
|
|||
|
|
- 或接入云 KMS(长期方案)
|
|||
|
|
|
|||
|
|
### 14.3 密钥生成方式
|
|||
|
|
|
|||
|
|
建议新增后端命令,例如:
|
|||
|
|
|
|||
|
|
- `php webman api-encrypt:generate-keypair`
|
|||
|
|
|
|||
|
|
职责:
|
|||
|
|
|
|||
|
|
- 生成 RSA 密钥对
|
|||
|
|
- 写入目标路径
|
|||
|
|
- 记录 `key_version`
|
|||
|
|
|
|||
|
|
### 14.4 密钥轮换设计
|
|||
|
|
|
|||
|
|
建议第二阶段支持:
|
|||
|
|
|
|||
|
|
- 当前生效版本 `v1`
|
|||
|
|
- 待切换版本 `v2`
|
|||
|
|
- 前端获取公钥时拿到当前版本
|
|||
|
|
- 服务端解密时可兼容多个版本一段时间
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 15. 安全增强建议
|
|||
|
|
|
|||
|
|
### 15.1 时间戳校验
|
|||
|
|
|
|||
|
|
请求带:
|
|||
|
|
|
|||
|
|
- `timestamp`
|
|||
|
|
|
|||
|
|
后端校验:
|
|||
|
|
|
|||
|
|
- 请求时间与服务器时间差不得超过 5 分钟
|
|||
|
|
|
|||
|
|
防止离线重放攻击。
|
|||
|
|
|
|||
|
|
### 15.2 nonce 去重
|
|||
|
|
|
|||
|
|
请求带:
|
|||
|
|
|
|||
|
|
- `nonce`
|
|||
|
|
|
|||
|
|
后端使用 Redis 记录一段时间内已用 nonce:
|
|||
|
|
|
|||
|
|
- 防止短时间重复提交同一密文包
|
|||
|
|
|
|||
|
|
### 15.3 签名校验(可选二阶段)
|
|||
|
|
|
|||
|
|
如果后续安全要求更高,可加:
|
|||
|
|
|
|||
|
|
- 对密文包进行签名
|
|||
|
|
- 服务端验签
|
|||
|
|
|
|||
|
|
但第一阶段不建议同时引入过多复杂度。
|
|||
|
|
|
|||
|
|
### 15.4 敏感日志脱敏
|
|||
|
|
|
|||
|
|
启用接口加密后,后端日志中不能直接打印:
|
|||
|
|
|
|||
|
|
- 原始请求密钥
|
|||
|
|
- AES 明文
|
|||
|
|
- 私钥内容
|
|||
|
|
|
|||
|
|
日志只能打印:
|
|||
|
|
|
|||
|
|
- 路径
|
|||
|
|
- key_version
|
|||
|
|
- nonce
|
|||
|
|
- 时间戳
|
|||
|
|
- 错误码
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 16. 性能与运维影响
|
|||
|
|
|
|||
|
|
### 16.1 性能影响
|
|||
|
|
|
|||
|
|
加密会增加:
|
|||
|
|
|
|||
|
|
- 前端加密耗时
|
|||
|
|
- 后端解密 / 加密耗时
|
|||
|
|
- 大列表响应的 CPU 消耗
|
|||
|
|
|
|||
|
|
建议:
|
|||
|
|
|
|||
|
|
- 第一阶段仅在敏感接口上验证性能
|
|||
|
|
- 对大列表、报表、导出接口默认排除
|
|||
|
|
|
|||
|
|
### 16.2 调试复杂度
|
|||
|
|
|
|||
|
|
启用后:
|
|||
|
|
|
|||
|
|
- 浏览器 Network 面板看到的是密文
|
|||
|
|
- 抓包看到的也是密文
|
|||
|
|
- 后端日志不能打印明文
|
|||
|
|
|
|||
|
|
需要增加:
|
|||
|
|
|
|||
|
|
- 开发环境关闭开关
|
|||
|
|
- 测试环境单独验证
|
|||
|
|
- 日志打印加密链路状态而不是明文内容
|
|||
|
|
|
|||
|
|
### 16.3 故障排查
|
|||
|
|
|
|||
|
|
建议新增错误码:
|
|||
|
|
|
|||
|
|
- `API_ENCRYPT_INVALID_PAYLOAD`
|
|||
|
|
- `API_ENCRYPT_DECRYPT_FAILED`
|
|||
|
|
- `API_ENCRYPT_KEY_VERSION_INVALID`
|
|||
|
|
- `API_ENCRYPT_TIMESTAMP_EXPIRED`
|
|||
|
|
- `API_ENCRYPT_NONCE_REPLAY`
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 17. 与本项目现有模块的兼容策略
|
|||
|
|
|
|||
|
|
### 17.1 Admin 端
|
|||
|
|
|
|||
|
|
兼容点:
|
|||
|
|
|
|||
|
|
- 当前 Axios 响应拦截器依赖 `code / msg / data`
|
|||
|
|
- 解密必须发生在这套业务判断之前
|
|||
|
|
|
|||
|
|
### 17.2 Web 端
|
|||
|
|
|
|||
|
|
兼容点:
|
|||
|
|
|
|||
|
|
- 当前 `request.ts` 里有 401 刷新 Token 逻辑
|
|||
|
|
- 解密必须在业务 code 判断之前完成
|
|||
|
|
- `showErrorMessage: false` 等语义必须保持不变
|
|||
|
|
|
|||
|
|
### 17.3 插件系统
|
|||
|
|
|
|||
|
|
兼容点:
|
|||
|
|
|
|||
|
|
- 插件接口默认也应继承主项目加密能力
|
|||
|
|
- 插件中如有上传/下载/SSE,需要支持排除
|
|||
|
|
|
|||
|
|
### 17.4 Swagger / OpenAPI
|
|||
|
|
|
|||
|
|
建议保持明文访问,不进入统一加密链路。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 18. 管理端配置页面建议
|
|||
|
|
|
|||
|
|
如果后续做成你截图里的配置页面,建议字段如下:
|
|||
|
|
|
|||
|
|
### 18.1 基础开关
|
|||
|
|
|
|||
|
|
- 接口加密:开 / 关
|
|||
|
|
- 加密范围:
|
|||
|
|
- 部分加密
|
|||
|
|
- 全局加密
|
|||
|
|
|
|||
|
|
### 18.2 密钥信息
|
|||
|
|
|
|||
|
|
- 当前密钥版本
|
|||
|
|
- RSA 公钥(可复制)
|
|||
|
|
- RSA 私钥:
|
|||
|
|
- 不直接展示完整明文
|
|||
|
|
- 仅支持“已配置 / 未配置”状态展示
|
|||
|
|
- 仅服务端生成和存储
|
|||
|
|
|
|||
|
|
### 18.3 操作项
|
|||
|
|
|
|||
|
|
- 重新生成密钥
|
|||
|
|
- 导出公钥
|
|||
|
|
- 同步前端缓存失效标记
|
|||
|
|
|
|||
|
|
### 18.4 排除规则
|
|||
|
|
|
|||
|
|
- 路径排除列表
|
|||
|
|
- 控制器/模块排除列表
|
|||
|
|
- 文件上传/下载排除开关
|
|||
|
|
|
|||
|
|
### 18.5 安全参数
|
|||
|
|
|
|||
|
|
- 请求时间窗口
|
|||
|
|
- nonce 有效期
|
|||
|
|
- 是否启用重放校验
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 19. 分阶段实施计划
|
|||
|
|
|
|||
|
|
### 第一阶段:最小可用方案
|
|||
|
|
|
|||
|
|
目标:先跑通主链路。
|
|||
|
|
|
|||
|
|
内容:
|
|||
|
|
|
|||
|
|
- 新增全局配置 `api_encrypt.php`
|
|||
|
|
- 新增 RSA 密钥生成命令
|
|||
|
|
- 新增公钥获取接口
|
|||
|
|
- 后端新增:
|
|||
|
|
- 请求解密中间件
|
|||
|
|
- 响应加密中间件
|
|||
|
|
- Web 前端接入请求/响应加解密
|
|||
|
|
- 仅支持:
|
|||
|
|
- JSON 接口
|
|||
|
|
- 部分加密模式
|
|||
|
|
- 排除:
|
|||
|
|
- 登录刷新
|
|||
|
|
- 上传下载
|
|||
|
|
- SSE
|
|||
|
|
- Swagger
|
|||
|
|
|
|||
|
|
### 第二阶段:全局加密方案
|
|||
|
|
|
|||
|
|
内容:
|
|||
|
|
|
|||
|
|
- 支持 `scope=all`
|
|||
|
|
- 支持路径排除
|
|||
|
|
- 支持 `#[SkipApiEncrypt]`
|
|||
|
|
- 支持 Admin 端统一接入
|
|||
|
|
|
|||
|
|
### 第三阶段:增强安全与运维能力
|
|||
|
|
|
|||
|
|
内容:
|
|||
|
|
|
|||
|
|
- 支持 nonce 防重放
|
|||
|
|
- 支持时间戳过期校验
|
|||
|
|
- 支持密钥轮换 / 多版本兼容
|
|||
|
|
- 支持后台可视化配置
|
|||
|
|
- 支持链路日志和告警
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 20. 推荐实施顺序
|
|||
|
|
|
|||
|
|
建议按以下顺序推进:
|
|||
|
|
|
|||
|
|
1. **先强制生产 HTTPS**
|
|||
|
|
- 不先做 HTTPS,接口加密的实际收益会打折
|
|||
|
|
2. **实现后端密钥管理与公钥接口**
|
|||
|
|
3. **先接 Web 端请求加密**
|
|||
|
|
- 范围更可控
|
|||
|
|
4. **再接 Admin 端统一请求封装**
|
|||
|
|
5. **最后接全局开关与管理端配置页面**
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 21. 风险清单
|
|||
|
|
|
|||
|
|
### 21.1 风险:前后端版本不一致
|
|||
|
|
|
|||
|
|
可能出现:
|
|||
|
|
|
|||
|
|
- 前端已发密文,后端还未解密
|
|||
|
|
- 后端已返回密文,前端还未解密
|
|||
|
|
|
|||
|
|
规避:
|
|||
|
|
|
|||
|
|
- 必须通过配置开关灰度上线
|
|||
|
|
- 先测试环境联调,再小范围启用
|
|||
|
|
|
|||
|
|
### 21.2 风险:Token 刷新链路被加密耦合破坏
|
|||
|
|
|
|||
|
|
规避:
|
|||
|
|
|
|||
|
|
- 第一阶段将 `/auth/refresh` 排除加密
|
|||
|
|
|
|||
|
|
### 21.3 风险:文件下载/上传被误加密
|
|||
|
|
|
|||
|
|
规避:
|
|||
|
|
|
|||
|
|
- 根据 `Content-Type` / `responseType` 明确排除
|
|||
|
|
|
|||
|
|
### 21.4 风险:前端调试困难
|
|||
|
|
|
|||
|
|
规避:
|
|||
|
|
|
|||
|
|
- 开发环境默认关闭
|
|||
|
|
- 提供调试日志开关,仅打印协议状态,不打印明文
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 22. 最终建议结论
|
|||
|
|
|
|||
|
|
对于当前项目,推荐的接口加密方案是:
|
|||
|
|
|
|||
|
|
- **以 HTTPS 为基础**
|
|||
|
|
- **采用 RSA + AES 混合加密**
|
|||
|
|
- **后端私钥、前端公钥**
|
|||
|
|
- **请求与响应统一加密**
|
|||
|
|
- **支持关闭 / 部分加密 / 全局加密**
|
|||
|
|
- **第一阶段只覆盖 JSON 业务接口**
|
|||
|
|
- **排除上传、下载、SSE、刷新 Token、Swagger、第三方回调**
|
|||
|
|
- **前后端统一在请求封装层与中间件层接入**
|
|||
|
|
- **保持现有 `code / msg / data` 业务语义不变**
|
|||
|
|
|
|||
|
|
这是最符合本项目现状、改造成本、后续可维护性和安全收益平衡的一套方案。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 23. 后续开发文档建议
|
|||
|
|
|
|||
|
|
本方案文档确认后,建议继续拆成 4 份执行文档:
|
|||
|
|
|
|||
|
|
1. **后端实现设计**
|
|||
|
|
- 中间件、配置、命令、公钥接口、Attribute
|
|||
|
|
2. **Web 前端接入设计**
|
|||
|
|
- `frontend/web/app/utils/request.ts` 改造方案
|
|||
|
|
3. **Admin 前端接入设计**
|
|||
|
|
- `frontend/admin/src/utils/http/index.ts` 改造方案
|
|||
|
|
4. **联调与灰度上线清单**
|
|||
|
|
- 测试用例、排除清单、回滚方案
|