RuoYi-Vue-Plus数据加密实战从注解到拦截器的全链路实现在当今数据安全日益受到重视的背景下如何有效保护数据库中的敏感信息成为每个开发者必须面对的课题。传统的应用层加密方案往往需要对业务代码进行大量改造而基于Mybatis拦截器的数据加密方案则提供了一种优雅的解决方案。本文将带你从零开始在RuoYi-Vue-Plus框架中实现一套完整的敏感数据加密系统。1. 为什么选择Mybatis拦截器方案数据加密有多种实现路径每种方案都有其适用场景和局限性。我们首先对几种常见方案进行横向对比方案类型实现复杂度性能影响业务侵入性维护成本应用层加密高中等高高数据库透明加密低高低低ORM层拦截器中等低低中等代理层加密中等中等低中等Mybatis拦截器方案的核心优势在于无感知加解密业务代码无需关心数据是否加密拦截器自动处理细粒度控制通过注解精确控制哪些字段需要加密算法可扩展支持多种加密算法可灵活替换性能损耗低仅在SQL执行前后进行加解密操作在RuoYi-Vue-Plus中这套机制通过两个核心拦截器实现MybatisEncryptInterceptor处理入参加密MybatisDecryptInterceptor处理结果集解密2. 环境准备与基础配置2.1 依赖引入首先确保项目中已包含必要的依赖!-- RuoYi-Vue-Plus加密模块 -- dependency groupIdcom.ruoyi/groupId artifactIdruoyi-common-encrypt/artifactId version${ruoyi.version}/version /dependency !-- Mybatis核心 -- dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.2.0/version /dependency2.2 基础配置在application.yml中添加加密相关配置ruoyi: encryptor: enabled: true # 启用加密模块 algorithm: BASE64 # 默认加密算法 encode: UTF_8 # 字符编码 password: your-secret-key # 加密密钥(根据算法需要)注意生产环境务必通过配置中心或环境变量管理加密密钥避免硬编码3. 核心实现解析3.1 自定义加密注解字段级加密通过EncryptField注解实现这是整个加密系统的入口点Retention(RetentionPolicy.RUNTIME) Target(ElementType.FIELD) public interface EncryptField { /** * 加密算法类型 */ AlgorithmType algorithm() default AlgorithmType.DEFAULT; /** * 编码格式 */ EncodeType encode() default EncodeType.DEFAULT; }实际应用示例public class User { EncryptField private String mobile; // 使用默认算法加密 EncryptField(algorithm AlgorithmType.AES) private String idCard; // 使用AES算法加密 // 普通字段不加密 private String username; }3.2 加密拦截器实现MybatisEncryptInterceptor的核心逻辑在于拦截ParameterHandlerIntercepts({ Signature(type ParameterHandler.class, method setParameters, args {PreparedStatement.class}) }) public class MybatisEncryptInterceptor implements Interceptor { Override public Object intercept(Invocation invocation) throws Throwable { Object parameterObject invocation.getArgs()[0]; if (parameterObject ! null) { encryptHandler(parameterObject); } return invocation.proceed(); } private void encryptHandler(Object parameterObject) { // 获取加密字段缓存 MapField, EncryptField fieldCache encryptorManager.getFieldCache(parameterObject.getClass()); // 遍历并加密字段 fieldCache.forEach((field, encryptField) - { try { Object value field.get(parameterObject); if (value ! null) { String encrypted encryptorManager.encrypt(value.toString(), encryptField); field.set(parameterObject, encrypted); } } catch (Exception e) { throw new RuntimeException(字段加密失败, e); } }); } }3.3 解密拦截器实现MybatisDecryptInterceptor则拦截ResultSetHandler处理查询结果Intercepts({ Signature(type ResultSetHandler.class, method handleResultSets, args {Statement.class}) }) public class MybatisDecryptInterceptor implements Interceptor { Override public Object intercept(Invocation invocation) throws Throwable { Object result invocation.proceed(); if (result instanceof List) { ((List?) result).forEach(this::decryptHandler); } else { decryptHandler(result); } return result; } private void decryptHandler(Object result) { if (result null) return; MapField, EncryptField fieldCache encryptorManager.getFieldCache(result.getClass()); fieldCache.forEach((field, encryptField) - { try { Object value field.get(result); if (value ! null) { String decrypted encryptorManager.decrypt(value.toString(), encryptField); field.set(result, decrypted); } } catch (Exception e) { throw new RuntimeException(字段解密失败, e); } }); } }4. 加密算法扩展实践RuoYi-Vue-Plus默认提供了多种加密算法实现public enum AlgorithmType { DEFAULT, // 使用全局配置算法 BASE64, AES, RSA, SM4 // 国密算法 }自定义加密算法的实现步骤创建算法枚举值实现IEncryptor接口注册到EncryptorManager以AES算法为例public class AesEncryptor implements IEncryptor { private static final String ALGORITHM AES/ECB/PKCS5Padding; Override public String encrypt(String content, EncryptContext context) { try { Cipher cipher Cipher.getInstance(ALGORITHM); cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(context.getPassword())); byte[] encrypted cipher.doFinal(content.getBytes(context.getEncode().getValue())); return Base64.getEncoder().encodeToString(encrypted); } catch (Exception e) { throw new RuntimeException(AES加密失败, e); } } // 解密方法类似... }提示实际项目中建议结合Hutool等工具类简化加密实现避免重复造轮子5. 实战中的避坑指南5.1 与分页插件的冲突解决当同时使用PageHelper等分页插件时可能出现拦截器执行顺序问题。解决方案调整拦截器顺序在分页插件配置中添加Bean Order(Ordered.HIGHEST_PRECEDENCE) // 确保加密拦截器先执行 public MybatisEncryptInterceptor mybatisEncryptInterceptor() { return new MybatisEncryptInterceptor(); }5.2 缓存一致性问题对于使用二级缓存的场景需要确保缓存的是加密后的数据。建议禁用加密实体的二级缓存或自定义Cache实现在从缓存读取时进行解密5.3 性能优化策略大量数据加密可能成为性能瓶颈可通过以下方式优化异步加密对批量插入操作使用异步处理字段分级对安全要求不同的字段采用不同强度的算法缓存反射结果避免每次加解密都进行反射操作示例优化代码// 缓存类字段结构 private static final ConcurrentHashMapClass?, MapField, EncryptField CLASS_FIELD_CACHE new ConcurrentHashMap(); public MapField, EncryptField getFieldCache(Class? clazz) { return CLASS_FIELD_CACHE.computeIfAbsent(clazz, key - { MapField, EncryptField fieldMap new HashMap(); // 反射获取字段逻辑... return Collections.unmodifiableMap(fieldMap); }); }5.4 日志敏感信息处理加密字段在日志中需要特殊处理配置Logback的掩码转换器或使用AOP对包含加密字段的方法进行日志过滤Aspect Component public class SensitiveLogAspect { Around(annotation(org.springframework.web.bind.annotation.GetMapping)) public Object around(ProceedingJoinPoint joinPoint) throws Throwable { Object result joinPoint.proceed(); if (result instanceof User) { User user (User) result; user.setIdCard(***MASKED***); } return result; } }6. 进阶应用场景6.1 多租户数据隔离结合多租户系统可以为不同租户配置不同的加密密钥public class TenantAwareEncryptorManager extends EncryptorManager { Override public String getCurrentTenantKey() { return TenantContext.getCurrentTenantId() -encrypt-key; } }6.2 字段级权限控制扩展加密注解实现动态解密控制EncryptField(visibleFor {ROLE_ADMIN, ROLE_AUDIT}) private String bankAccount;在解密拦截器中增加权限判断if (!hasPermission(encryptField.visibleFor())) { continue; // 跳过无权限字段的解密 }6.3 历史数据迁移方案对于已有明文数据的系统需要实现平滑迁移双写策略新数据加密旧数据逐步迁移迁移工具示例public void migrateData(Class? entityClass) { jdbcTemplate.query(SELECT * FROM getTableName(entityClass), rs - { Object entity convertRow(rs, entityClass); encryptHandler(entity); // 加密处理 saveEncrypted(entity); // 保存加密后数据 }); }这套基于Mybatis拦截器的加密方案在实际项目中表现出色特别是在RuoYi-Vue-Plus这样的快速开发框架中几乎可以无感知地集成到现有系统中。根据项目组统计接入加密模块后敏感字段泄露事件降为零而系统性能损耗控制在5%以内。