抓包还能看到密码?Vue3 + Spring Boot 3 接口 AES/RSA 加解密(@ApiEncrypt)全链路实战

抓包还能看到密码?Vue3 + Spring Boot 3 接口 AES/RSA 加解密(@ApiEncrypt)全链路实战

一句话 :HTTPS 管「路上」,@ApiEncrypt 管「包里」------敏感接口的 Body 在到达 Controller 之前就该是密文,前端 axios 一眼看不见明文 JSON。

开源仓库:GitCode · RuoyiOfficeAtomGit · RuoyiOffice


为什么「已经上了 HTTPS」还不够?

场景 HTTPS 帮得上吗 密文 Body 帮得上吗
公网链路被窃听 ✅(双重)
员工本机 Fiddler / Charles 装根证书 ❌ 明文尽收眼底 ✅ Body 仍是乱码
前端误把密码打进 console / 埋点 部分 减少传输侧泄露面
数据库被拖库 ❌(要靠落库字段加密)
接口被重放 ❌(要靠签名 + nonce + 时效)

很多团队把「传输安全」和「存储安全」「防重放」揉成一锅粥。RuoyiOffice 里其实是三件独立兵器:

能力 解决什么 关键词
API 传输加解密 抓包看不到业务 JSON @ApiEncrypt / Filter / X-Api-Encrypt
字段落库加密 DBA/备份文件看不到手机号 TypeHandler / 列级密文
API 签名 防篡改、防重放 appId + timestamp + sign

本文只打透第一件:请求解密 + 响应加密 的前后端全链路。

登录页本身是敏感入口的典型------密码绝不能以「可复制的明文 JSON」躺在抓包工具里。


全链路长什么样?

text 复制代码
Vue3 RequestClient
  │  headers.isEncrypt = true
  │  data = AES/RSA.encrypt(JSON)
  │  Header: X-Api-Encrypt: true
  ▼
HTTP POST /admin-api/...   (Body = Base64/密文串)
  ▼
ApiEncryptFilter(早于 Controller、早于多数业务 Filter 可读点)
  │  有加密头 → DecryptRequestWrapper 还原明文 Body
  │  方法标了 @ApiEncrypt(request=true) 却无加密头 → 直接拒
  ▼
Controller / Service   (以为自己在收普通 VO)
  ▼
若 response=true → EncryptResponseWrapper 写出密文 + 响应头
  ▼
前端响应拦截器:见加密头 → decrypt → 再走统一 Result 解析

设计上有个关键取舍:不用 RequestBodyAdvice / ResponseBodyAdvice ,而用 Servlet Filter。原因很务实------访问日志、异常日志、签名校验往往要在「业务反序列化之前」看到原始字节流;Filter 更靠前、更可控。


后端:一个注解,两种开关

java 复制代码
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiEncrypt {
    /** 是否解密请求体,默认 true */
    boolean request() default true;
    /** 是否加密响应体,默认 true */
    boolean response() default true;
}

用法示例(示意):

java 复制代码
@PostMapping("/update-password")
@ApiEncrypt // 请求解密 + 响应加密
public CommonResult<Boolean> updatePassword(@RequestBody @Valid PwdReq req) {
    // req 里已是明文,业务无感知加解密
    return success(userService.updatePassword(req));
}

@GetMapping("/profile")
@ApiEncrypt(request = false, response = true) // 只加密出参
public CommonResult<ProfileVO> getProfile() { ... }

Filter 核心分支(概念伪代码)

java 复制代码
ApiEncrypt ann = resolveHandlerAnnotation(request);
boolean needReq = ann != null && ann.request();
boolean needResp = ann != null && ann.response();
String encryptHeader = request.getHeader("X-Api-Encrypt");

if (!needReq && !needResp && isBlank(encryptHeader)) {
    chain.doFilter(request, response); // 普通接口放行
    return;
}

// POST/PUT/DELETE:有加密头则包装解密;标注了 request 却没头 → 参数错误
if (isWriteMethod && isNotBlank(encryptHeader)) {
    request = new DecryptRequestWrapper(request, decryptor);
} else if (needReq) {
    throw invalidParam("请求未包含加密标头");
}

if (needResp) {
    response = new EncryptResponseWrapper(response); // 先包着,链尾再真正加密
}
chain.doFilter(request, response);
// 链结束后:若 needResp,把缓冲内容加密写回,并打上加密响应头

要点:

  1. 注解驱动 + 请求头双保险:前端必须带加密头,后端方法必须声明需要解密,避免「误加密普通上传」或「漏加密敏感口」。
  2. 只处理写方法 Body:GET 通常无 Body;加密主要打在 JSON POST。
  3. 算法可配置:AES(对称,性能好)或 RSA(非对称,密钥角色对调要小心)。

配置:算法、密钥、请求头

配置项语义(勿把密钥提交进 Git):

含义
enable 总开关;关闭则整条 Filter 可短路
header 默认 X-Api-Encrypt,请求/响应共用语义
algorithm AESRSA(国密 SM2/SM4 可二次扩展)
requestKey 后端用来解密请求的钥
responseKey 后端用来加密响应的钥

AES vs RSA:密钥角色别搞反

算法 后端 requestKey 前端请求加密用 后端 responseKey 前端响应解密用
AES 共享密钥 同一共享密钥 共享密钥(可与请求同或不同) 对应共享密钥
RSA 私钥 公钥 公钥 私钥

AES 密钥长度要对齐实现(常见 16/32 字节);前端 CryptoJS ECB + PKCS7 必须与后端 Hutool AES 模式一致,否则「能加密不能解密」。


前端:Vue3 RequestClient 怎么接

RuoyiOffice web-antd 在 axios 封装里挂了加解密工具:

ts 复制代码
const apiEncrypt = createApiEncrypt(import.meta.env);

// 请求拦截:按调用方标记加密
if ((config.headers || {}).isEncrypt) {
  config.data = apiEncrypt.encryptRequest(config.data);
  config.headers[apiEncrypt.getEncryptHeader()] = 'true';
}

// 响应拦截:后端打了加密头则先解密再交给业务
const encryptHeader = apiEncrypt.getEncryptHeader();
if (
  (response.headers[encryptHeader] === 'true' ||
    response.headers[encryptHeader.toLowerCase()] === 'true') &&
  typeof response.data === 'string'
) {
  response.data = apiEncrypt.decryptResponse(response.data);
}

业务侧敏感调用:

ts 复制代码
await requestClient.post('/system/user/profile/update-password', data, {
  headers: { isEncrypt: true },
});

环境变量里配置与后端一致的算法、header 名、请求加密钥、响应解密钥。生产密钥走私密配置/CI Secret,禁止写进公开仓库的 .env.example 真值。


和「字段加密」「API 签名」怎么分工?

text 复制代码
浏览器 ──(1 传输加密)──▶ API ──(2 签名校验)──▶ Service ──(3 字段加密)──▶ DB
         @ApiEncrypt           appId+sign            TypeHandler
问法 该用谁
Charles 里密码是明文? 传输加密
别人改了金额字段重放? 签名 + 时效 + nonce
备份 SQL 里手机号裸奔? 字段加密 / 脱敏展示
双击提交两单? 幂等 @Idempotent,不是加密

加密解决「看不懂」,签名解决「改不了、重放难」,幂等解决「做两次等于做一次」------别指望一个注解包打天下。

对比落库加密架构(另一条线):


落地清单与踩坑

上线前核对

  • 前后端 algorithm / header 名一致
  • AES 模式与 padding 一致(ECB/CBC 混用必炸)
  • RSA 公私钥角色按上表对齐
  • 仅敏感接口开 isEncrypt,大文件上传、multipart 不要整包 AES
  • 网关/Nginx 勿二次改写 Body 编码
  • 访问日志若打印 Body,注意打的是解密前还是解密后(合规)
  • 密钥轮换预案:双钥窗口期或强制客户端升级

常见故障

现象 排查
后端报「未包含加密标头」 前端没带 isEncrypt 或 header 名不一致
解密结果为空 密钥错、算法错、或前端加密了已经是字符串的二次 JSON
响应业务码解析失败 响应拦截器没先解密,把密文当 JSON parse
本地 Postman 调不通 要用脚本先加密 Body,或临时关该接口注解(勿关生产总开关)
文件上传 400 multipart 被当成 JSON 解密------排除加密

FAQ

Q1:全站所有接口都加密会不会很慢?

A:AES 对 JSON 级别开销通常可接受;全站加密会让排障、开放 API、第三方回调变地狱。建议:登录改密、支付相关、证件号提交 等点状开启。

Q2:为什么 Filter 要在「有注解却无加密头」时直接失败?

A:防止攻击者去掉加密头、企图走明文旁路;也防止前端漏配却「看起来成功」造成假安全感。

Q3:和国密 SM4/SM2?

A:扩展点在算法工厂;加依赖与加解密器分支即可,注解与前端 isEncrypt 协议可复用。

Q4:微服务 / 网关要解密几次?

A:推荐在业务服务入口解密一次;网关只做 TLS 与鉴权,避免双端持钥扩散。若网关必须看到明文做路由,再评估「网关终止加密 + mTLS 到后端」。

Q5:响应也加密后,前端错误提示怎么办?

A:全局异常仍返回统一 Result;只要 Filter 在写出前加密、前端先解密,错误码体验与明文接口一致。


和 RuoyiOffice 的对应关系

能力 位置(概念)
@ApiEncrypt + Filter Web Starter · encrypt 包
加解密配置 api-encrypt 配置项(enable/header/algorithm/keys)
前端工具 @vben/utils · createApiEncrypt
请求封装 apps/web-antd · api/request.ts
对照:字段加密 MyBatis TypeHandler
对照:API 签名 签名设计专题

完整后端:GitCode / AtomGit

前端:GitCode · vben


总结

  1. HTTPS ≠ 抓包安全;敏感 Body 需要应用层加解密。
  2. @ApiEncrypt + Filter 让 Controller 继续写明文 VO,密钥与算法集中配置。
  3. Vue3 用 isEncrypt 点状开启,响应头驱动解密,避免全站误伤。
  4. 字段加密、签名、幂等正交组合,才是企业接口安全的完整拼图。

下一篇换业务编排视角:审批红点、短信、邮件如何用「场景 × 渠道 × WebSocket」一次性打穿。


GitCode · AtomGit 点星,夏日活动攒积分。

在线体验:RuoyiOffice

商业版源码授权:联系页 · 企业微信 RuoyiOffice


关键词:@ApiEncrypt、AES、RSA、Vue3 请求加密、Spring Boot 3 Filter、X-Api-Encrypt、接口传输安全、防抓包

相关推荐
凤山老林1 小时前
零停机数据库演进:Spring Boot 集成 Flyway 与平滑DDL变更策略
数据库·spring boot·后端
抓哇小菜鸡1 小时前
Spring Boot + 本地大模型(Ollama/DeepSeek) + MyBatis-Plus 企业级智能体数据分析系统从零到一源码全解析
spring boot·后端·mybatis
趴下吧1 小时前
spring boot启动流程总结
java·spring boot·spring
码农进化录2 小时前
Java 程序员的 AI 进化论 | AI 帮我做 MySQL 表结构升级,三天活变半天
java·spring boot·openai
JacksonMx3 小时前
ContentCachingRequestWrapper 实战:解决请求体“只能读一次”的官方方案
java·spring boot·spring
A黄俊辉A4 小时前
java RedisTemplate 的使用
java·spring boot·spring
2602_959960924 小时前
大厂Java面试:Spring Boot、JVM、Redis、Kafka与Elasticsearch在内容社区UGC场景下的实战拷问
java·jvm·spring boot·redis·面试题
凤山老林13 小时前
精细化流量治理:Spring Boot 动态特性开关与灰度发布体系
java·spring boot·后端
Aphelios38015 小时前
一次锁内网络IO引发的Tomcat线程池“饿死”事故
java·开发语言·spring boot·elasticsearch·tomcat·网络io阻塞·线程池耗尽