首页
/ RuoYi-Vue数据安全实战:从合规到防护的全链路解决方案

RuoYi-Vue数据安全实战:从合规到防护的全链路解决方案

2026-04-28 11:35:28作者:宣海椒Queenly

在数字化时代,数据已成为企业核心资产,其安全防护直接关系到业务连续性与用户信任。本文基于RuoYi-Vue框架,围绕敏感数据全生命周期防护,构建从风险识别到实施验证的完整解决方案,帮助企业建立符合等保三级要求的数据安全体系。通过数据分级分类、脱敏、加密、审计等技术手段,实现从数据产生、传输、存储到销毁的全程可控,有效防范数据泄露、篡改等安全风险。

数据安全能力评估矩阵

能力等级 数据防护状态 风险控制水平 合规程度
Level 1 无防护措施 高风险 不满足基本要求
Level 2 基础访问控制 中风险 部分满足等保要求
Level 3 全生命周期防护 低风险 完全满足等保三级
Level 4 智能化安全运营 极低风险 超越基本合规要求

现状诊断:RuoYi-Vue数据安全痛点

当前框架在数据安全方面存在以下关键风险点:

  • 分级缺失:未建立数据分类分级标准,敏感数据与普通数据混同管理
  • 传输暴露:部分接口采用HTTP明文传输,存在中间人攻击风险
  • 存储隐患:敏感字段未加密,数据库泄露直接导致信息泄露
  • 审计不足:操作日志未覆盖全数据生命周期,追溯能力弱

数据分级分类体系构建

风险点:数据资产不清导致防护资源错配

企业数据缺乏系统分类,重要数据未得到重点保护,造成"过度防护"与"防护不足"并存的局面。例如:用户身份证号与普通配置信息采用相同安全策略,既增加不必要开销,又可能遗漏关键保护措施。

防护方案:四层级数据分类模型

  1. 公开数据:可对外公开的信息,如产品介绍、公开文档
  2. 内部数据:企业内部流转信息,如部门通知、非敏感报表
  3. 敏感数据:需授权访问的信息,如用户手机号、邮箱地址
  4. 绝密数据:核心敏感信息,如身份证号、银行账户、密码

代码实现:数据分级注解与校验

在ruoyi-common模块中创建数据分级注解:

// ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataLevel.java
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataLevel {
    Level value();
    
    enum Level {
        PUBLIC, INTERNAL, SENSITIVE, TOP_SECRET
    }
}

在实体类中使用注解标记敏感字段:

// ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java
public class SysUser extends BaseEntity {
    private static final long serialVersionUID = 1L;
    
    private Long userId;
    
    private String userName;
    
    @DataLevel(Level.TOP_SECRET)
    private String idCard;  // 身份证号→绝密数据
    
    @DataLevel(Level.SENSITIVE)
    private String phone;   // 手机号→敏感数据
    
    @DataLevel(Level.INTERNAL)
    private String email;   // 邮箱→内部数据
}

验证方法:分级数据扫描工具

开发数据分级扫描工具,定期检查未标记数据字段:

// ruoyi-common/src/main/java/com/ruoyi/common/utils/DataLevelScanner.java
public class DataLevelScanner {
    public void scanPackages(String... packages) {
        // 扫描指定包下所有实体类
        // 检查字段是否添加DataLevel注解
        // 生成未分级数据报告
    }
}

数据脱敏技术实现

风险点:日志与界面展示导致敏感信息泄露

系统运维日志、调试信息及前端界面中直接展示完整敏感数据,如打印包含身份证号的日志、管理界面显示完整手机号,易造成信息泄露。

防护方案:基于分级的动态脱敏策略

根据数据敏感级别应用不同脱敏规则:

数据级别 脱敏规则 示例
公开数据 不脱敏 产品名称:RuoYi-Vue
内部数据 部分掩码 邮箱:ru***@example.com
敏感数据 大部分掩码 手机号:138****5678
绝密数据 完全掩码 身份证号:****************X

代码实现:脱敏工具类与注解处理器

创建脱敏工具类:

// ruoyi-common/src/main/java/com/ruoyi/common/utils/DesensitizedUtil.java
public class DesensitizedUtil {
    /**
     * 手机号脱敏
     * @param phone 手机号
     * @return 脱敏后手机号
     */
    public static String maskPhone(String phone) {
        if (StringUtils.isEmpty(phone)) return "";
        return phone.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
    }
    
    /**
     * 身份证号脱敏
     * @param idCard 身份证号
     * @return 脱敏后身份证号
     */
    public static String maskIdCard(String idCard) {
        if (StringUtils.isEmpty(idCard)) return "";
        return idCard.replaceAll("(\\d{1})\\d{16}(\\d{1})", "$1****************$2");
    }
}

实现脱敏注解与JSON序列化处理器:

// ruoyi-common/src/main/java/com/ruoyi/common/annotation/Desensitize.java
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Desensitize {
    DesensitizeType type();
}

// 在WebConfig中配置Jackson脱敏序列化器
// ruoyi-framework/src/main/java/com/ruoyi/framework/config/ResourcesConfig.java
@Configuration
public class ResourcesConfig implements WebMvcConfigurer {
    @Bean
    public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.registerModule(new SimpleModule().addSerializer(
            new SensitiveDataSerializer()
        ));
        converter.setObjectMapper(objectMapper);
        return converter;
    }
}

验证方法:脱敏效果测试用例

// 脱敏功能测试
@Test
public void testDesensitize() {
    SysUser user = new SysUser();
    user.setPhone("13812345678");
    user.setIdCard("110101199001011234");
    
    // 序列化对象
    String json = new ObjectMapper().writeValueAsString(user);
    
    // 验证脱敏结果
    Assert.assertTrue(json.contains("138****5678"));
    Assert.assertTrue(json.contains("1****************4"));
}

数据加密存储方案

风险点:数据库泄露导致敏感数据直接暴露

数据库备份文件泄露或数据库服务器被入侵时,未加密的敏感数据将直接被攻击者获取,造成严重数据泄露事件。

防护方案:透明数据加密(TDE)+ 字段级加密

采用双层加密策略:

  1. 数据库级加密:使用MySQL TDE功能对整个数据库加密
  2. 应用级加密:对绝密数据字段采用AES-256算法加密存储

代码实现:字段加密工具与MyBatis拦截器

扩展SecurityUtils工具类,添加加密方法:

// ruoyi-common/src/main/java/com/ruoyi/common/utils/SecurityUtils.java
public class SecurityUtils {
    // 加密密钥,实际项目中应从环境变量或密钥管理服务获取
    private static final String ENCRYPT_KEY = System.getenv("ENCRYPT_KEY");
    
    /**
     * AES加密
     */
    public static String encrypt(String content) {
        if (StringUtils.isEmpty(content)) {
            return null;
        }
        try {
            SecretKeySpec key = new SecretKeySpec(ENCRYPT_KEY.getBytes(), "AES");
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, key);
            return Base64.encodeBase64String(cipher.doFinal(content.getBytes(StandardCharsets.UTF_8)));
        } catch (Exception e) {
            log.error("加密失败", e);
            throw new ServiceException("数据加密失败");
        }
    }
    
    /**
     * AES解密
     */
    public static String decrypt(String encryptedContent) {
        if (StringUtils.isEmpty(encryptedContent)) {
            return null;
        }
        try {
            SecretKeySpec key = new SecretKeySpec(ENCRYPT_KEY.getBytes(), "AES");
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
            cipher.init(Cipher.DECRYPT_MODE, key);
            return new String(cipher.doFinal(Base64.decodeBase64(encryptedContent)), StandardCharsets.UTF_8);
        } catch (Exception e) {
            log.error("解密失败", e);
            throw new ServiceException("数据解密失败");
        }
    }
}

创建MyBatis拦截器实现自动加解密:

// ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/EncryptInterceptor.java
@Component
@Intercepts({
    @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class EncryptInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 获取SQL语句
        // 识别包含敏感字段的SQL
        // 对插入/更新操作进行加密处理
        // 对查询操作添加解密函数
        return invocation.proceed();
    }
}

验证方法:加密性能测试脚本

-- 加密性能测试SQL
-- 测试1000条记录加密插入性能
DROP PROCEDURE IF EXISTS test_encrypt_performance;
DELIMITER $$
CREATE PROCEDURE test_encrypt_performance()
BEGIN
    DECLARE i INT DEFAULT 0;
    DECLARE start_time DATETIME DEFAULT NOW();
    
    WHILE i < 1000 DO
        INSERT INTO sys_user (user_name, id_card, phone, email)
        VALUES (CONCAT('test_user_', i), 
                AES_ENCRYPT(CONCAT('11010119900101', LPAD(i, 4, '0')), '${ENCRYPT_KEY}'),
                AES_ENCRYPT(CONCAT('138', LPAD(i, 8, '0')), '${ENCRYPT_KEY}'),
                CONCAT('test_', i, '@example.com'));
        SET i = i + 1;
    END WHILE;
    
    SELECT TIMESTAMPDIFF(MILLISECOND, start_time, NOW()) AS execution_time;
END$$
DELIMITER ;

CALL test_encrypt_performance();

数据传输安全加固

风险点:HTTP传输导致数据在途被窃听篡改

采用HTTP协议传输数据,易遭受中间人攻击,导致数据被窃听、篡改或伪造,尤其在公共网络环境下风险更高。

防护方案:全站HTTPS + API签名验证

  1. 强制HTTPS:所有通信采用TLS 1.3加密
  2. 证书管理:使用Let's Encrypt免费证书,配置自动续期
  3. API签名:对关键接口添加请求签名,防止数据篡改

代码实现:HTTPS配置与签名拦截器

配置服务器HTTPS:

# ruoyi-admin/src/main/resources/application.yml
server:
  port: 443
  ssl:
    enabled: true
    key-store: classpath:keystore.p12
    key-store-password: ${SSL_KEYSTORE_PASSWORD}
    key-store-type: PKCS12
    key-alias: ruoyi
    protocol: TLSv1.3
    ciphers: TLS_AES_256_GCM_SHA384,TLS_CHACHA20_POLY1305_SHA256,TLS_AES_128_GCM_SHA256

实现API签名验证拦截器:

// ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/SignInterceptor.java
@Component
public class SignInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // 获取请求参数
        String timestamp = request.getHeader("X-Timestamp");
        String nonce = request.getHeader("X-Nonce");
        String signature = request.getHeader("X-Signature");
        
        // 验证时间戳有效性(防止重放攻击)
        long now = System.currentTimeMillis() / 1000;
        if (Math.abs(now - Long.parseLong(timestamp)) > 300) {
            throw new ServiceException("请求已过期");
        }
        
        // 验证签名
        String data = timestamp + nonce + request.getRequestURI() + getRequestParams(request);
        String localSign = SignatureUtils.sign(data, API_SECRET);
        if (!localSign.equals(signature)) {
            throw new ServiceException("签名验证失败");
        }
        
        return true;
    }
}

验证方法:HTTPS配置检查工具

# 检查SSL配置安全性
openssl s_client -connect localhost:443 -tls1_3
# 测试API签名功能
curl -X POST https://localhost/api/system/user/list \
  -H "X-Timestamp: $(date +%s)" \
  -H "X-Nonce: $(uuidgen)" \
  -H "X-Signature: $(echo -n "$(date +%s)$(uuidgen)/api/system/user/list" | openssl dgst -sha256 -hmac "your_api_secret")"

数据泄露检测与响应

风险点:数据泄露后难以及时发现和处置

缺乏有效的数据泄露检测机制,导致数据泄露事件发现不及时,扩大安全损失。

防护方案:异常行为检测 + 实时告警响应

  1. 行为基线:建立用户正常操作行为基线
  2. 异常检测:识别异常数据访问模式(如大量下载、非工作时间访问)
  3. 实时告警:发现异常时触发多渠道告警
  4. 应急响应:制定数据泄露应急处置流程

代码实现:数据访问审计与异常检测

扩展操作日志记录敏感数据访问:

// ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/LogAspect.java
@Around("@annotation(log)")
public Object around(ProceedingJoinPoint joinPoint, Log log) throws Throwable {
    // 记录操作日志基本信息
    SysOperLog operLog = new SysOperLog();
    operLog.setOperIp(IpUtils.getIpAddr(ServletUtils.getRequest()));
    operLog.setOperUrl(ServletUtils.getRequest().getRequestURI());
    operLog.setOperTime(new Date());
    
    // 检查是否访问敏感数据
    boolean hasSensitiveData = checkSensitiveDataAccess(joinPoint);
    if (hasSensitiveData) {
        operLog.setBusinessType(BusinessType.SENSITIVE_DATA_ACCESS);
        // 记录访问的敏感数据类型
        operLog.setRemark(getSensitiveDataTypes(joinPoint));
    }
    
    // 执行目标方法
    Object result = joinPoint.proceed();
    
    // 记录结果
    operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
    AsyncManager.me().execute(AsyncFactory.recordOper(operLog));
    return result;
}

实现异常检测服务:

// ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysLogininforServiceImpl.java
@Service
public class SysLogininforServiceImpl implements ISysLogininforService {
    @Override
    public void detectAbnormalLogin() {
        // 获取最近1小时登录记录
        List<SysLogininfor> loginList = logininforMapper.selectRecentLogin(60);
        
        // 按用户分组统计
        Map<String, List<SysLogininfor>> userLoginMap = loginList.stream()
            .collect(Collectors.groupingBy(SysLogininfor::getUserName));
            
        // 检测异常登录模式
        for (Map.Entry<String, List<SysLogininfor>> entry : userLoginMap.entrySet()) {
            List<SysLogininfor> logins = entry.getValue();
            // 1. 短时间内多次失败登录
            long failCount = logins.stream()
                .filter(l -> l.getStatus().equals("失败"))
                .count();
            if (failCount > 5) {
                // 触发账户锁定
                userService.lockUser(entry.getKey());
                // 发送告警
                alarmService.sendAlarm("异常登录", "用户" + entry.getKey() + "短时间内登录失败" + failCount + "次");
            }
            
            // 2. 异地登录检测
            Set<String> regions = logins.stream()
                .map(l -> AddressUtils.getRealAddressByIP(l.getIpaddr()))
                .collect(Collectors.toSet());
            if (regions.size() > 2) {
                alarmService.sendAlarm("异地登录", "用户" + entry.getKey() + "在" + regions + "多地登录");
            }
        }
    }
}

验证方法:数据泄露模拟测试

模拟以下数据泄露场景进行测试:

  1. 大量下载敏感数据(如一次性导出1000条用户记录)
  2. 非工作时间(如凌晨3点)访问敏感数据
  3. 异常IP地址登录系统并访问敏感数据
  4. SQL注入攻击尝试获取敏感数据

数据销毁与残留清除

风险点:数据删除后仍存在存储介质残留

简单删除操作无法彻底清除数据,通过磁盘恢复工具可恢复已删除数据,造成数据泄露风险。

防护方案:安全擦除 + 介质销毁

  1. 逻辑删除:系统内数据标记删除状态
  2. 物理擦除:对删除数据所在磁盘扇区进行多次覆写
  3. 介质销毁:废弃存储介质物理销毁

代码实现:安全删除工具类

// ruoyi-common/src/main/java/com/ruoyi/common/utils/FileSecureDeleteUtils.java
public class FileSecureDeleteUtils {
    /**
     * 安全删除文件
     * @param filePath 文件路径
     * @param times 覆写次数,推荐3-7次
     */
    public static void secureDelete(String filePath, int times) {
        File file = new File(filePath);
        if (!file.exists()) {
            return;
        }
        
        try (RandomAccessFile raf = new RandomAccessFile(file, "rw")) {
            long length = raf.length();
            
            // 多次覆写
            for (int i = 0; i < times; i++) {
                raf.seek(0);
                byte[] data = new byte[4096];
                new Random().nextBytes(data);
                
                long position = 0;
                while (position < length) {
                    int writeLength = (int) Math.min(data.length, length - position);
                    raf.write(data, 0, writeLength);
                    position += writeLength;
                }
                raf.getChannel().force(true);
            }
            
            // 删除文件
            file.delete();
        } catch (IOException e) {
            log.error("安全删除文件失败", e);
        }
    }
}

验证方法:数据恢复测试

使用数据恢复工具测试删除效果:

  1. 存储敏感数据文件
  2. 使用安全删除工具删除
  3. 使用Recuva等数据恢复软件尝试恢复
  4. 验证数据是否完全不可恢复

数据安全能力评估量表

评估项目 改造前 改造后 提升幅度
数据分级覆盖率 0% 100% 100%
敏感数据加密率 0% 100% 100%
传输加密率 30% 100% 70%
安全审计覆盖率 50% 100% 50%
数据泄露检测能力 90%+ -
合规符合度 30% 95% 65%

数据安全合规检查清单

数据分类分级

  • [ ] 已完成数据资产梳理
  • [ ] 已定义数据分类分级标准
  • [ ] 所有敏感字段已标记分级注解
  • [ ] 定期进行数据分级审计

数据脱敏

  • [ ] 日志中敏感数据已脱敏
  • [ ] 前端展示敏感数据已脱敏
  • [ ] 脱敏规则符合业务需求
  • [ ] 脱敏功能通过单元测试

数据加密

  • [ ] 敏感字段已实现存储加密
  • [ ] 加密密钥安全管理
  • [ ] 加密性能满足业务要求
  • [ ] 加密功能通过安全测试

传输安全

  • [ ] 全站已启用HTTPS
  • [ ] 配置TLS 1.3协议
  • [ ] 关键API已添加签名验证
  • [ ] 证书自动续期已配置

审计与响应

  • [ ] 敏感数据访问已记录审计日志
  • [ ] 异常行为检测规则已配置
  • [ ] 告警机制正常工作
  • [ ] 数据泄露应急响应流程已制定

数据销毁

  • [ ] 敏感文件已实现安全删除
  • [ ] 废弃介质已安全销毁
  • [ ] 数据销毁流程已文档化
  • [ ] 销毁效果已验证

总结

通过本文介绍的敏感数据全生命周期防护方案,RuoYi-Vue框架实现了从数据产生、传输、存储到销毁的全程安全管控。通过数据分级分类明确保护重点,结合脱敏、加密、审计等技术手段,构建了多层次数据安全防护体系。企业在实施过程中,应根据自身业务特点调整防护策略,定期进行安全评估与演练,持续提升数据安全能力,确保满足等保三级及相关法规要求,为业务发展提供坚实的数据安全保障。

登录后查看全文
热门项目推荐
相关项目推荐