RuoYi-Vue数据安全实战:从合规到防护的全链路解决方案
在数字化时代,数据已成为企业核心资产,其安全防护直接关系到业务连续性与用户信任。本文基于RuoYi-Vue框架,围绕敏感数据全生命周期防护,构建从风险识别到实施验证的完整解决方案,帮助企业建立符合等保三级要求的数据安全体系。通过数据分级分类、脱敏、加密、审计等技术手段,实现从数据产生、传输、存储到销毁的全程可控,有效防范数据泄露、篡改等安全风险。
数据安全能力评估矩阵
| 能力等级 | 数据防护状态 | 风险控制水平 | 合规程度 |
|---|---|---|---|
| Level 1 | 无防护措施 | 高风险 | 不满足基本要求 |
| Level 2 | 基础访问控制 | 中风险 | 部分满足等保要求 |
| Level 3 | 全生命周期防护 | 低风险 | 完全满足等保三级 |
| Level 4 | 智能化安全运营 | 极低风险 | 超越基本合规要求 |
现状诊断:RuoYi-Vue数据安全痛点
当前框架在数据安全方面存在以下关键风险点:
- 分级缺失:未建立数据分类分级标准,敏感数据与普通数据混同管理
- 传输暴露:部分接口采用HTTP明文传输,存在中间人攻击风险
- 存储隐患:敏感字段未加密,数据库泄露直接导致信息泄露
- 审计不足:操作日志未覆盖全数据生命周期,追溯能力弱
数据分级分类体系构建
风险点:数据资产不清导致防护资源错配
企业数据缺乏系统分类,重要数据未得到重点保护,造成"过度防护"与"防护不足"并存的局面。例如:用户身份证号与普通配置信息采用相同安全策略,既增加不必要开销,又可能遗漏关键保护措施。
防护方案:四层级数据分类模型
- 公开数据:可对外公开的信息,如产品介绍、公开文档
- 内部数据:企业内部流转信息,如部门通知、非敏感报表
- 敏感数据:需授权访问的信息,如用户手机号、邮箱地址
- 绝密数据:核心敏感信息,如身份证号、银行账户、密码
代码实现:数据分级注解与校验
在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)+ 字段级加密
采用双层加密策略:
- 数据库级加密:使用MySQL TDE功能对整个数据库加密
- 应用级加密:对绝密数据字段采用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签名验证
- 强制HTTPS:所有通信采用TLS 1.3加密
- 证书管理:使用Let's Encrypt免费证书,配置自动续期
- 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")"
数据泄露检测与响应
风险点:数据泄露后难以及时发现和处置
缺乏有效的数据泄露检测机制,导致数据泄露事件发现不及时,扩大安全损失。
防护方案:异常行为检测 + 实时告警响应
- 行为基线:建立用户正常操作行为基线
- 异常检测:识别异常数据访问模式(如大量下载、非工作时间访问)
- 实时告警:发现异常时触发多渠道告警
- 应急响应:制定数据泄露应急处置流程
代码实现:数据访问审计与异常检测
扩展操作日志记录敏感数据访问:
// 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 + "多地登录");
}
}
}
}
验证方法:数据泄露模拟测试
模拟以下数据泄露场景进行测试:
- 大量下载敏感数据(如一次性导出1000条用户记录)
- 非工作时间(如凌晨3点)访问敏感数据
- 异常IP地址登录系统并访问敏感数据
- SQL注入攻击尝试获取敏感数据
数据销毁与残留清除
风险点:数据删除后仍存在存储介质残留
简单删除操作无法彻底清除数据,通过磁盘恢复工具可恢复已删除数据,造成数据泄露风险。
防护方案:安全擦除 + 介质销毁
- 逻辑删除:系统内数据标记删除状态
- 物理擦除:对删除数据所在磁盘扇区进行多次覆写
- 介质销毁:废弃存储介质物理销毁
代码实现:安全删除工具类
// 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);
}
}
}
验证方法:数据恢复测试
使用数据恢复工具测试删除效果:
- 存储敏感数据文件
- 使用安全删除工具删除
- 使用Recuva等数据恢复软件尝试恢复
- 验证数据是否完全不可恢复
数据安全能力评估量表
| 评估项目 | 改造前 | 改造后 | 提升幅度 |
|---|---|---|---|
| 数据分级覆盖率 | 0% | 100% | 100% |
| 敏感数据加密率 | 0% | 100% | 100% |
| 传输加密率 | 30% | 100% | 70% |
| 安全审计覆盖率 | 50% | 100% | 50% |
| 数据泄露检测能力 | 无 | 90%+ | - |
| 合规符合度 | 30% | 95% | 65% |
数据安全合规检查清单
数据分类分级
- [ ] 已完成数据资产梳理
- [ ] 已定义数据分类分级标准
- [ ] 所有敏感字段已标记分级注解
- [ ] 定期进行数据分级审计
数据脱敏
- [ ] 日志中敏感数据已脱敏
- [ ] 前端展示敏感数据已脱敏
- [ ] 脱敏规则符合业务需求
- [ ] 脱敏功能通过单元测试
数据加密
- [ ] 敏感字段已实现存储加密
- [ ] 加密密钥安全管理
- [ ] 加密性能满足业务要求
- [ ] 加密功能通过安全测试
传输安全
- [ ] 全站已启用HTTPS
- [ ] 配置TLS 1.3协议
- [ ] 关键API已添加签名验证
- [ ] 证书自动续期已配置
审计与响应
- [ ] 敏感数据访问已记录审计日志
- [ ] 异常行为检测规则已配置
- [ ] 告警机制正常工作
- [ ] 数据泄露应急响应流程已制定
数据销毁
- [ ] 敏感文件已实现安全删除
- [ ] 废弃介质已安全销毁
- [ ] 数据销毁流程已文档化
- [ ] 销毁效果已验证
总结
通过本文介绍的敏感数据全生命周期防护方案,RuoYi-Vue框架实现了从数据产生、传输、存储到销毁的全程安全管控。通过数据分级分类明确保护重点,结合脱敏、加密、审计等技术手段,构建了多层次数据安全防护体系。企业在实施过程中,应根据自身业务特点调整防护策略,定期进行安全评估与演练,持续提升数据安全能力,确保满足等保三级及相关法规要求,为业务发展提供坚实的数据安全保障。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust099- DDeepSeek-V4-ProDeepSeek-V4-Pro(总参数 1.6 万亿,激活 49B)面向复杂推理和高级编程任务,在代码竞赛、数学推理、Agent 工作流等场景表现优异,性能接近国际前沿闭源模型。Python00
MiMo-V2.5-ProMiMo-V2.5-Pro作为旗舰模型,擅⻓处理复杂Agent任务,单次任务可完成近千次⼯具调⽤与⼗余轮上 下⽂压缩。Python00
GLM-5.1GLM-5.1是智谱迄今最智能的旗舰模型,也是目前全球最强的开源模型。GLM-5.1大大提高了代码能力,在完成长程任务方面提升尤为显著。和此前分钟级交互的模型不同,它能够在一次任务中独立、持续工作超过8小时,期间自主规划、执行、自我进化,最终交付完整的工程级成果。Jinja00
Kimi-K2.6Kimi K2.6 是一款开源的原生多模态智能体模型,在长程编码、编码驱动设计、主动自主执行以及群体任务编排等实用能力方面实现了显著提升。Python00
MiniMax-M2.7MiniMax-M2.7 是我们首个深度参与自身进化过程的模型。M2.7 具备构建复杂智能体应用框架的能力,能够借助智能体团队、复杂技能以及动态工具搜索,完成高度精细的生产力任务。Python00