21 KiB
接口加密方案设计(适配 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 / 自定义密文包装逻辑
- 直接输出标准 JSON:
- 前台 Web 请求封装
frontend/web/app/utils/request.ts- 直接
response.json() - 按
code / msg / data处理 - 没有解密逻辑
- 直接
- Admin 请求封装
frontend/admin/src/utils/http/index.tstransformResponse直接JSON.parse- 响应拦截器直接读取
response.data.code / msg / data - 没有解密逻辑
2.2 当前项目没有代码层强制 HTTPS
经核实,当前仓库代码没有发现全局强制 HTTPS 的逻辑:
- 没有发现统一
forceScheme('https') - 没有发现 HTTP -> HTTPS 跳转中间件
- 前端
.env使用相对 API 路径:frontend/admin/.env.production中VITE_API_URL=/adminapifrontend/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 核心流程
请求流程
- 前端生成一次性 AES Key + IV
- 前端使用 AES 加密业务请求数据
- 前端使用 RSA 公钥加密 AES Key(必要时也包含 IV)
- 前端将密文提交给后端
- 后端使用 RSA 私钥解出 AES Key
- 后端使用 AES 解密业务数据
- 后端将解密结果重新写回请求上下文,供控制器正常读取
响应流程
- 控制器照常返回
Json::success()/Json::fail() - 响应加密中间件判断当前接口是否需要加密
- 将原始 JSON 响应体用 AES 加密
- 按统一密文结构返回前端
- 前端响应拦截器识别密文结构并解密
- 还原为原始
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.phpbackend/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.phpbackend/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_versionpublic_keyenabledscope
该接口必须明文,否则前端无法开始第一次加密通信。
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: 1X-Api-Encrypt-Version: v1
响应后
- 判断响应体是否为加密协议
- 若是:
- 使用本次请求缓存的 AES Key 解密响应
- 还原为
code / msg / data - 再进入现有业务处理链
11.2 Web 端接入点
目标文件:
frontend/web/app/utils/request.ts
改造逻辑与 Admin 一致。
11.3 公钥缓存策略
建议:
- 启动后首次调用公钥接口并缓存
- 缓存内容包含:
key_versionpublic_keyexpire_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 做是否启用的判断
推荐顺序:
- 跨域 / 语言中间件
- 请求解密中间件
- 业务鉴权 / Token 验证中间件
- 控制器处理
- 响应加密中间件
原因:
- 鉴权前应拿到解密后的真实请求参数
- 响应加密应放在业务执行后
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.pemruntime/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_PAYLOADAPI_ENCRYPT_DECRYPT_FAILEDAPI_ENCRYPT_KEY_VERSION_INVALIDAPI_ENCRYPT_TIMESTAMP_EXPIREDAPI_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. 推荐实施顺序
建议按以下顺序推进:
- 先强制生产 HTTPS
- 不先做 HTTPS,接口加密的实际收益会打折
- 实现后端密钥管理与公钥接口
- 先接 Web 端请求加密
- 范围更可控
- 再接 Admin 端统一请求封装
- 最后接全局开关与管理端配置页面
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 份执行文档:
- 后端实现设计
- 中间件、配置、命令、公钥接口、Attribute
- Web 前端接入设计
frontend/web/app/utils/request.ts改造方案
- Admin 前端接入设计
frontend/admin/src/utils/http/index.ts改造方案
- 联调与灰度上线清单
- 测试用例、排除清单、回滚方案