必须用自定义responsewriter包装器劫持写入流:记录状态码、缓冲响应体、handler执行完再aes加密并重设content-length/type;密钥需配置注入,iv随机生成前置,避免全局启用和实时调用kms。

中间件里不能直接加密 c.Response().Writer
因为 c.Response().Writer 是一个 http.ResponseWriter 接口,一旦调用 c.JSON()、c.String() 等方法,底层就可能已向客户端 flush 数据——此时再想“拦截并加密响应体”,已经晚了。常见错误是直接在中间件里读取 ResponseWriter 的缓冲区,结果要么为空,要么 panic(比如类型断言失败或被 hijacked)。
必须用 ResponseWriter 包装器劫持写入流
真正可行的做法是:在中间件中替换 c.Response().Writer 为自定义的 ResponseWriter 实现,把原始响应内容先写入内存 buffer,等 handler 执行完、所有 header 设置完毕后,再对 body 加密、重写 status code 和 content-length。
Echo框架 5.1.0 版本源码包下载,适合关注 RealIP 行为变化、StartConfig.Listener、NewDefaultFS 和观测性中间件入口的开发团队。
- 定义包装器:
type encryptedWriter struct { http.ResponseWriter; buf *bytes.Buffer; statusCode int } - 重写
WriteHeader()记录状态码,不立即透传 - 重写
Write([]byte)把数据写入buf,而非直接输出 - 在
next(c)返回后,对w.buf.Bytes()做 AES 加密(如使用golang.org/x/crypto/aes),再调用原ResponseWriter.WriteHeader()和Write() - 注意:加解密密钥不能硬编码,应从
config或环境变量注入;IV 必须随机生成并前置到密文开头
加密后必须重设 Content-Length 和 Content-Type
原始响应头里的 Content-Length 是明文长度,加密后字节长度必然变化;如果用 application/json 明文响应,加密后应改为 application/octet-stream 或自定义类型(如 application/vnd.myapp.encrypted+json),否则前端解析会失败。
- 调用
w.ResponseWriter.Header().Set("Content-Type", "application/octet-stream") - 加密完成后调用
w.ResponseWriter.Header().Set("Content-Length", strconv.Itoa(len(encrypted))) - 别漏掉
w.ResponseWriter.Header().Del("Content-Length")—— 否则 net/http 可能自动补一个错误值 - 若需保留原始语义(如让网关识别 JSON),可在加密 payload 外层套一层 JSON wrapper:
{"encrypted": "base64..."},但要同步更新前端解密逻辑
别在中间件里做耗时加解密操作
加密是 CPU 密集型操作,尤其对大响应体(如文件下载、列表分页数据)。若所有接口都走同一条加密中间件,吞吐量会明显下降,且无法按路径/角色差异化控制。
- 只对敏感路由启用,例如
/api/v1/user/profile,而不是全局e.Use(encryptMiddleware) - 避免在中间件里调用外部密钥服务(如 KMS)实时获取密钥——每次请求都网络 IO,延迟不可控
- 考虑用
sync.Pool复用bytes.Buffer和aes.Cipher实例,减少 GC 压力 - 测试时务必验证:HTTP 流式响应(
c.Stream())、大文件c.Attachment()、以及panic场景下加密逻辑是否仍安全兜底(比如 recover 中间件必须在加密中间件外层)
WriteHeader() 被多次调用的情况,或者忘了加密后清空原始 buffer 导致重复写入。










