Files
MrleeAdmin/doc/api-encryption-design.md
T
2026-08-31 11:33:59 +08:00

21 KiB
Raw Blame History

接口加密方案设计(适配 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 保持现有响应协议语义不变

当前项目大量前后端逻辑依赖如下结构:

{
  "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 推荐密文协议结构

请求体结构

{
  "encrypted": true,
  "algorithm": "rsa-aes",
  "key_version": "v1",
  "key": "<RSA加密后的AES密钥>",
  "iv": "<Base64 IV 或并入 key 元数据>",
  "data": "<AES加密后的业务密文>",
  "timestamp": 1720000000,
  "nonce": "random-string"
}

响应体结构

{
  "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

建议结构:

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 环境变量设计

建议新增:

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(防重放,第二阶段可增强)

失败返回建议:

{
  "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 明文模式

仍返回:

{
  "code": 0,
  "msg": "成功",
  "data": {}
}

13.2 密文模式

传输层返回:

{
  "encrypted": true,
  "algorithm": "rsa-aes",
  "key_version": "v1",
  "data": "..."
}

前端解密后,还原为:

{
  "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. 联调与灰度上线清单
    • 测试用例、排除清单、回滚方案