# 接口加密方案设计(适配 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": "", "iv": "", "data": "", "timestamp": 1720000000, "nonce": "random-string" } ``` #### 响应体结构 ```json { "encrypted": true, "algorithm": "rsa-aes", "key_version": "v1", "data": "" } ``` 说明: - 如果请求与响应共用同一个 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. **联调与灰度上线清单** - 测试用例、排除清单、回滚方案