尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

若依框架前后端分离版AES接口加密实战:从配置到拦截器的完整实现

若依框架前后端分离版AES接口加密实战:从配置到拦截器的完整实现 1. 为什么需要接口加密最近在做一个金融类项目时客户明确要求所有接口传输必须加密。这让我想起三年前做医疗项目时也遇到过类似需求。接口加密就像给数据穿上防弹衣特别是在若依这类前后端分离框架中尤为重要。想象一下你通过快递寄送一份重要合同。如果直接用透明文件袋装谁都能看到内容。而加密就像把合同放进保险箱只有收件人知道密码。AES加密就是这样一个可靠的保险箱它采用对称加密算法加解密使用相同密钥性能好安全性高。在实际项目中我遇到过两种典型场景必须加密一是涉及用户隐私数据如身份证、银行卡号二是业务敏感数据如交易金额、库存数量。去年有个客户就因为没做接口加密被竞争对手抓包分析出了核心业务逻辑损失惨重。2. 前端加密实战2.1 CryptoJS集成指南前端加密我推荐使用CryptoJS这个库我用过不下十个项目稳定性没得说。先安装npm install crypto-js --save # 国内可以用cnpm cnpm install crypto-js --save安装时有个坑要注意如果项目同时用了webpack5可能会报错。这时需要在vue.config.js里加个配置configureWebpack: { resolve: { fallback: { crypto: false } } }2.2 AES工具类封装我习惯把加密方法封装成工具类这里分享我优化过的版本import CryptoJS from crypto-js // 生成符合AES规范的随机密钥 export const genAESKey () { const chars ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 return Array(16) .fill(0) .map(() chars.charAt(Math.floor(Math.random() * chars.length))) .join() } // 加密方法ECB模式PKCS7填充 export const encrypt (data, key) { const utf8Key CryptoJS.enc.Utf8.parse(key) const utf8Data typeof data string ? CryptoJS.enc.Utf8.parse(data) : CryptoJS.enc.Utf8.parse(JSON.stringify(data)) return CryptoJS.AES.encrypt(utf8Data, utf8Key, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 }).toString() } // 解密方法 export const decrypt (ciphertext, key) { const bytes CryptoJS.AES.decrypt(ciphertext, CryptoJS.enc.Utf8.parse(key), { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 }) return bytes.toString(CryptoJS.enc.Utf8) }这个版本我做了三点优化密钥生成改用更简洁的写法加密时自动判断数据类型解密结果自动转UTF-8字符串3. 环境配置与请求拦截3.1 环境变量设置在.env文件中配置# 16位AES密钥 VUE_APP_AES_KEY KXZ9Y7P2M5S6B3Q8 # 是否启用加密 VUE_APP_ENCRYPT_ENABLED true这里有个实际项目中的经验建议把密钥分成两部分前端存一半后端存另一半使用时拼接。这样即使前端代码被反编译也拿不到完整密钥。3.2 axios拦截器改造在request.js中我通常这样改造拦截器// 请求拦截 config { if (process.env.VUE_APP_ENCRYPT_ENABLED true) { config.headers[X-Encrypt] AES // 添加加密标记头 if (config.data) { config.data encrypt(config.data, process.env.VUE_APP_AES_KEY) } if (config.params) { config.params encrypt(config.params, process.env.VUE_APP_AES_KEY) } } return config } // 响应拦截 response { if (response.config.headers[X-Encrypt] AES) { const decrypted decrypt(response.data, process.env.VUE_APP_AES_KEY) try { response.data JSON.parse(decrypted) } catch (e) { response.data decrypted } } return response }特别注意二进制响应如文件下载要跳过解密if (response.config.responseType blob) { return response }4. 后端实现详解4.1 配置文件准备在application.yml中添加encrypt: enabled: true key: KXZ9Y7P2M5S6B3Q8 # 必须与前端的密钥一致 excludeUrls: - /api/login - /api/captcha - /profile/**这里有个性能优化点静态资源和登录接口建议排除加密因为这些接口调用频繁且不涉及敏感数据。4.2 请求响应包装器请求包装器核心代码public class CryptoRequestWrapper extends HttpServletRequestWrapper { private final String decryptedBody; public CryptoRequestWrapper(HttpServletRequest request, String body) { super(request); this.decryptedBody body; } Override public ServletInputStream getInputStream() { ByteArrayInputStream stream new ByteArrayInputStream( decryptedBody.getBytes(StandardCharsets.UTF_8)); return new ServletInputStream() { // 实现所有抽象方法 }; } }响应包装器要注意处理流关闭问题public class CryptoResponseWrapper extends HttpServletResponseWrapper { private ByteArrayOutputStream outputStream new ByteArrayOutputStream(); Override public ServletOutputStream getOutputStream() { return new ServletOutputStream() { Override public void write(int b) { outputStream.write(b); } // 其他方法实现 }; } public byte[] getResponseData() { return outputStream.toByteArray(); } }4.3 过滤器核心逻辑过滤器实现URL匹配和加解密public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) { HttpServletRequest request (HttpServletRequest) req; String uri request.getRequestURI(); // 排除不需要加密的接口 if (!encryptEnabled || isExcludeUrl(uri)) { chain.doFilter(req, res); return; } // 解密请求 String requestBody getRequestBody(request); String decrypted AESUtil.decrypt(requestBody, key); CryptoRequestWrapper wrappedRequest new CryptoRequestWrapper(request, decrypted); // 加密响应 CryptoResponseWrapper wrappedResponse new CryptoResponseWrapper((HttpServletResponse) res); chain.doFilter(wrappedRequest, wrappedResponse); byte[] responseData wrappedResponse.getResponseData(); String encrypted AESUtil.encrypt(new String(responseData), key); res.getOutputStream().write(encrypted.getBytes()); }URL匹配算法我优化过多次最终采用Ant风格路径匹配private boolean isExcludeUrl(String uri) { return excludeUrls.stream() .anyMatch(pattern - new AntPathMatcher().match(pattern, uri)); }5. 常见问题解决方案5.1 加解密不一致问题上周刚有同事遇到前端加密后端解不开的情况通常有三个排查方向密钥不一致检查前后端密钥是否完全一致包括空格编码问题确保都用UTF-8编码加密模式必须都是ECB模式PKCS7填充5.2 性能优化建议在大并发场景下我有三个优化经验使用连接池HttpClient配置连接池复用缓存AES实例不要每次请求都new新的AES实例异步加解密对于大报文可以用CompletableFuture异步处理5.3 安全增强方案对于高安全要求场景建议动态密钥每次登录生成新密钥通过Token传递请求签名增加时间戳和签名防重放密钥轮换定期更换密钥并兼容老密钥记得在网关层也要做相应的加解密处理保持全链路加密。去年我做的一个政务项目就采用了这种方案顺利通过了等保三级认证。
返回列表