别再裸奔了!给若依前后端分离项目加上AES接口加密(Vue3 + Spring Boot保姆级配置)
若依框架前后端分离项目AES接口加密实战指南
在当今数据安全日益重要的环境下,企业级应用开发中接口传输的安全性已成为不可忽视的一环。许多开发者在使用若依这类优秀框架时,往往只关注功能实现而忽略了数据传输过程中的安全隐患。本文将带您从零开始,为若依前后端分离项目打造一套完整的AES接口加密方案,涵盖Vue3前端到Spring Boot后端的全链路配置。
1. 为什么需要接口加密?
想象一下,当用户登录时,用户名和密码以明文形式在网络中传输;当查询个人隐私数据时,身份证号、手机号等敏感信息直接暴露在请求响应中——这无异于在互联网上"裸奔"。接口加密的核心价值在于:
- 防止敏感数据泄露:即使请求被拦截,攻击者也无法直接获取有效信息
- 抵御中间人攻击:加密后的数据难以被篡改或伪造
- 满足合规要求:许多行业规范明确要求数据传输必须加密
提示:AES(高级加密标准)作为对称加密算法,在性能和安全性之间取得了良好平衡,是接口加密的理想选择。
2. 前端加密配置(Vue3)
2.1 安装与基础配置
首先在Vue3项目中安装crypto-js库:
npm install crypto-js --save # 如遇网络问题可使用 cnpm install crypto-js --save创建src/utils/aes.js工具类:
import CryptoJS from 'crypto-js' // 生成随机16位AES密钥 export const generateAESKey = () => { const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' return Array(16).fill(0) .map(() => chars.charAt(Math.floor(Math.random() * chars.length))) .join('') } // AES加密 export const encrypt = (data, key) => { const keyBytes = CryptoJS.enc.Utf8.parse(key) const dataBytes = CryptoJS.enc.Utf8.parse( typeof data === 'object' ? JSON.stringify(data) : data ) const encrypted = CryptoJS.AES.encrypt(dataBytes, keyBytes, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 }) return encrypted.toString() } // AES解密 export const decrypt = (ciphertext, key) => { const keyBytes = CryptoJS.enc.Utf8.parse(key) const decrypted = CryptoJS.AES.decrypt(ciphertext, keyBytes, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 }) return CryptoJS.enc.Utf8.stringify(decrypted).toString() }2.2 请求拦截器改造
修改src/utils/request.js,添加加密逻辑:
import { encrypt, decrypt } from './aes' // 从环境变量获取配置 const AES_KEY = import.meta.env.VITE_APP_AES_KEY const ENCRYPT_ENABLED = import.meta.env.VITE_APP_ENCRYPT_ENABLED === 'true' // 请求拦截器 service.interceptors.request.use(config => { if (ENCRYPT_ENABLED && config.data) { config.data = encrypt(config.data, AES_KEY) config.headers['X-Encrypted'] = 'true' } return config }) // 响应拦截器 service.interceptors.response.use(response => { if (ENCRYPT_ENABLED && response.data && response.headers['content-type']?.includes('application/json')) { try { response.data = JSON.parse(decrypt(response.data, AES_KEY)) } catch (e) { console.error('解密失败:', e) } } return response.data })3. 后端加密配置(Spring Boot)
3.1 过滤器实现
创建解密过滤器DecryptionFilter:
import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.List; public class DecryptionFilter implements Filter { private final AES aes; private final boolean enabled; private final List<String> excludeUrls; public DecryptionFilter(String key, boolean enabled, List<String> excludeUrls) { this.aes = new AES(key.getBytes()); this.enabled = enabled; this.excludeUrls = excludeUrls; } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; if (!enabled || shouldExclude(httpRequest.getRequestURI())) { chain.doFilter(request, response); return; } // 包装请求和响应 DecryptionRequestWrapper requestWrapper = new DecryptionRequestWrapper(httpRequest, aes); EncryptionResponseWrapper responseWrapper = new EncryptionResponseWrapper((HttpServletResponse) response); chain.doFilter(requestWrapper, responseWrapper); // 加密响应 if (responseWrapper.getContentType() != null && responseWrapper.getContentType().contains("application/json")) { String encrypted = aes.encryptBase64(responseWrapper.getContent()); response.getWriter().write(encrypted); } } private boolean shouldExclude(String uri) { return excludeUrls.stream().anyMatch(uri::startsWith); } }3.2 请求/响应包装器
实现请求包装器DecryptionRequestWrapper:
public class DecryptionRequestWrapper extends HttpServletRequestWrapper { private final String decryptedBody; public DecryptionRequestWrapper(HttpServletRequest request, AES aes) throws IOException { super(request); String encrypted = IOUtils.toString(request.getReader()); this.decryptedBody = encrypted.isEmpty() ? "" : aes.decryptStr(encrypted); } @Override public BufferedReader getReader() { return new BufferedReader(new StringReader(decryptedBody)); } // 其他需要重写的方法... }实现响应包装器EncryptionResponseWrapper:
public class EncryptionResponseWrapper extends HttpServletResponseWrapper { private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); private PrintWriter writer; public EncryptionResponseWrapper(HttpServletResponse response) { super(response); } @Override public ServletOutputStream getOutputStream() { return new ServletOutputStream() { @Override public void write(int b) { outputStream.write(b); } // 其他必要方法实现... }; } public String getContent() { return outputStream.toString(StandardCharsets.UTF_8); } }3.3 Spring配置
在配置类中注册过滤器:
@Configuration public class FilterConfig { @Value("${encrypt.enabled:false}") private boolean enabled; @Value("${encrypt.key}") private String key; @Value("${encrypt.exclude-urls:/common/**,/profile/**}") private List<String> excludeUrls; @Bean public FilterRegistrationBean<DecryptionFilter> decryptionFilter() { FilterRegistrationBean<DecryptionFilter> registration = new FilterRegistrationBean<>(); registration.setFilter(new DecryptionFilter(key, enabled, excludeUrls)); registration.addUrlPatterns("/*"); registration.setOrder(Ordered.HIGHEST_PRECEDENCE); return registration; } }4. 密钥管理与环境配置
4.1 前端环境变量
在项目根目录创建.env文件:
VITE_APP_AES_KEY=Your16ByteAESKey123 VITE_APP_ENCRYPT_ENABLED=true4.2 后端配置文件
application.yml配置:
encrypt: enabled: true key: Your16ByteAESKey123 exclude-urls: - /common/** - /profile/** - /druid/**4.3 密钥安全最佳实践
- 不要硬编码:始终通过环境变量或配置中心管理密钥
- 区分环境:开发、测试、生产环境使用不同密钥
- 定期轮换:建立密钥轮换机制,但要注意历史数据解密问题
- 最小权限:仅对敏感接口启用加密,静态资源等可排除
5. 常见问题排查
5.1 前端常见问题
问题1:加密后后端无法解密
- 检查前后端密钥是否一致
- 确认加密模式(ECB/CBC)和填充方式(PKCS7)一致
- 查看网络请求原始数据,确认传输过程中是否被修改
问题2:性能明显下降
- 只对必要接口启用加密
- 考虑使用Web Worker处理加密解密
- 检查是否重复加密/解密
5.2 后端常见问题
问题1:过滤器不生效
- 检查过滤器顺序,确保它在安全过滤器之前
- 确认URL模式配置正确
- 检查Spring Boot自动配置是否冲突
问题2:解密失败
- 确认请求Content-Type为application/json
- 检查是否有其他过滤器修改了请求体
- 调试查看原始加密字符串是否完整
6. 进阶优化建议
- 动态密钥协商:实现密钥定期更换机制,避免长期使用同一密钥
- 混合加密方案:结合RSA非对称加密传输AES密钥,提升安全性
- 请求签名验证:添加时间戳和签名,防止重放攻击
- 性能监控:添加加密解密的耗时统计,及时发现性能瓶颈
- 白名单机制:基于用户角色动态决定是否加密
在实际项目中,我们曾遇到一个典型案例:某金融类应用在启用加密后,发现某些安卓设备请求成功率显著下降。经过排查,发现是低端设备加密计算耗时过长导致请求超时。最终通过优化加密策略(仅在WiFi环境下启用强加密)解决了问题。
