【RuoYi-SpringBoot3-Pro】: 三级等保安全配置
本文详细介绍 RuoYi-SpringBoot3-Pro 框架中内置的三级等保安全策略,帮助企业快速满足国家信息安全等级保护三级要求。
GitHub:
https://github.com/undsky/RuoYi-SpringBoot3-Pro
一、什么是三级等保?
信息安全等级保护(简称"等保")是我国信息安全保障的基本制度。根据《网络安全法》要求,网络运营者应当按照网络安全等级保护制度的要求,履行安全保护义务。
三级等保适用于:
- 涉及国家安全、社会秩序、公共利益的重要信息系统
- 政府机关、金融、能源、交通等关键行业系统
- 一旦遭到破坏会对社会秩序和公共利益造成严重损害的系统
1.1 等保三级对身份鉴别的要求
| 要求项 | 具体内容 |
|---|---|
| 身份标识唯一性 | 应对登录的用户进行身份标识和鉴别,身份标识具有唯一性 |
| 登录失败处理 | 应具有登录失败处理功能,应配置并启用结束会话、限制非法登录次数和当登录连接超时自动退出等相关措施 |
| 口令复杂度 | 应采用口令、密码技术、生物技术等两种或两种以上组合的鉴别技术对用户进行身份鉴别 |
| 口令更换周期 | 应强制用户首次登录时修改初始口令,并定期更换口令 |
| 会话超时 | 当用户在一段时间内无操作时,应自动结束会话 |
二、RuoYi-SpringBoot3-Pro 等保方案
RuoYi-SpringBoot3-Pro 内置了完善的三级等保安全策略,所有配置通过系统参数表动态管理,无需重启服务即可生效。
2.1 安全策略总览
┌─────────────────────────────────────────────────────────────┐
│ 三级等保安全策略 │
├─────────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 密码更新周期 │ │ 登录失败锁定 │ │ 初始密码修改 │ │
│ │ 90天强制改密 │ │ 5次失败锁定 │ │ 首次登录改密 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ IP 黑名单 │ │ 无操作登出 │ │ Redis 缓存 │ │
│ │ 通配符/网段 │ │ 前端计时器 │ │ 高性能支持 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘
2.2 系统参数配置表
| 参数键名 | 参数说明 | 示例值 | 默认值 |
|---|---|---|---|
sys.account.passwordValidateDays | 密码有效期(天) | 90 | 0(不限制) |
sys.account.tryLoginCount | 登录失败锁定策略 | 5-30 | 0(不限制) |
sys.account.initPasswordModify | 初始密码强制修改 | 1 | 0(关闭) |
sys.login.blackIPList | IP 黑名单 | 192.168.1.*;10.0.0.0/8 | 空 |
三、密码更新周期
3.1 功能说明
强制用户定期更换密码,防止密码长期不变带来的安全风险。
3.2 配置方式
在系统管理 → 参数设置中配置:
参数名称:用户管理-账号密码更新周期
参数键名:sys.account.passwordValidateDays
参数键值:90 (90天强制修改密码,0表示不限制)
3.3 实现原理
数据库字段:
用户表 sys_user 中新增 pwd_update_date 字段,记录密码最后更新时间。
ALTER TABLE sys_user ADD COLUMN pwd_update_date datetime COMMENT '密码最后更新时间';
后端校验逻辑:
// SysLoginController.java
public boolean passwordIsExpiration(Date pwdUpdateDate) {
// 获取密码有效期配置
Integer passwordValidateDays = Convert.toInt(
configService.selectConfigByKey("sys.account.passwordValidateDays")
);
if (passwordValidateDays != null && passwordValidateDays > 0) {
if (StringUtils.isNull(pwdUpdateDate)) {
// 从未修改过初始密码,直接提醒过期
return true;
}
Date nowDate = DateUtils.getNowDate();
// 计算密码使用天数是否超过有效期
return DateUtils.differentDaysByMillisecond(nowDate, pwdUpdateDate) > passwordValidateDays;
}
return false;
}
前端处理:
登录成功后,前端根据 isPasswordExpired 标识判断是否需要强制修改密码:
// 获取用户信息
getInfo().then(res => {
if (res.isPasswordExpired) {
// 跳转到修改密码页面
router.push('/user/profile');
ElMessage.warning('您的密码已过期,请修改密码');
}
});
3.4 效果展示
- 密码超过 90 天未更新 → 登录后强制跳转修改密码页面
- 修改密码后自动更新
pwd_update_date字段 - 配置为 0 时关闭此功能
四、登录失败锁定
4.1 功能说明
防止暴力破解密码,连续多次登录失败后自动锁定账号。
4.2 配置方式
参数名称:用户管理-账号密码尝试登录次数
参数键名:sys.account.tryLoginCount
参数键值:5-30 (5次失败后锁定30分钟,格式:次数-锁定时长)
配置格式说明:
5-30:5 次失败后锁定 30 分钟3-60:3 次失败后锁定 60 分钟0:关闭此功能
4.3 实现原理
数据库字段:
用户表 sys_user 中新增 try_count 字段,记录连续登录失败次数。
ALTER TABLE sys_user ADD COLUMN try_count int DEFAULT 0 COMMENT '尝试登录次数';
后端校验逻辑:
// SysLoginController.java
// 1. 登录前检查是否被锁定
public String ifLockUser(String username) {
Integer[] configs = getTryLoginCountConfig();
if (configs == null || configs[1] == null || configs[1] == 0) {
return null;
}
String tryGtCountUsername = username + "-tryGtCount";
if (redisCache.hasKey(tryGtCountUsername)) {
String datetime = redisCache.getCacheObject(tryGtCountUsername);
long betweenMinute = DateUtil.between(DateUtil.parse(datetime), new Date(), DateUnit.MINUTE);
if (betweenMinute > configs[1]) {
// 锁定时间已过,自动解锁
unLockUser(username);
return null;
} else {
return "连续" + configs[0] + "次登录失败,请" + configs[1] + "分钟后再试";
}
}
return null;
}
// 2. 解析配置
public Integer[] getTryLoginCountConfig() {
String tryLoginCount = configService.selectConfigByKey("sys.account.tryLoginCount");
if (StringUtils.equals("0", tryLoginCount)) {
return null;
}
String[] arr = StringUtils.split(tryLoginCount, "-");
if (arr.length == 2) {
Integer[] configs = new Integer[2];
configs[0] = Convert.toInt(arr[0]); // 失败次数
configs[1] = Convert.toInt(arr[1]); // 锁定时长(分钟)
return configs;
}
return null;
}
// 3. 登录失败处理
@PostMapping("/login")
public AjaxResult login(@RequestBody LoginBody loginBody) {
String username = loginBody.getUsername();
// 检查是否被锁定
String msg = ifLockUser(username);
if (null != msg) {
throw new RuntimeException(msg);
}
try {
String token = loginService.login(username, password, code, uuid);
// 登录成功,重置失败次数
userService.resetTryCount(username, 0);
return AjaxResult.success().put(Constants.TOKEN, token);
} catch (Exception e) {
SysUser user = userService.selectUserByUserName(username);
if (null != user && StringUtils.equals("0", user.getDelFlag())) {
// 累加失败次数
Integer tryCount = null == user.getTryCount() ? 1 : (user.getTryCount() + 1);
msg = ifTryGtCount(tryCount);
if (null != msg) {
// 达到锁定条件,记录锁定时间到 Redis
redisCache.setCacheObject(username + "-tryGtCount", DateUtil.now());
} else {
// 未达到锁定条件,更新失败次数
userService.resetTryCount(username, tryCount);
}
}
throw new RuntimeException(msg != null ? msg : e.getMessage());
}
}
4.4 Redis 缓存设计
使用 Redis 存储锁定状态,提升性能并支持分布式部署:
Key: {username}-tryGtCount
Value: 锁定时间(如 "2024-01-15 10:30:00")
4.5 效果展示
第 1 次失败:用户名或密码错误
第 2 次失败:用户名或密码错误
第 3 次失败:用户名或密码错误
第 4 次失败:用户名或密码错误
第 5 次失败:连续5次登录失败,请30分钟后再试
... 30分钟内无法登录 ...
30分钟后:自动解锁,可以重新登录
五、初始密码强制修改
5.1 功能说明
新用户首次登录时,强制修改初始密码,防止使用默认密码带来的安全风险。
5.2 配置方式
参数名称:用户管理-初始密码修改策略
参数键名:sys.account.initPasswordModify
参数键值:1 (1表示启用,0表示关闭)
5.3 实现原理
// SysLoginController.java
public boolean initPasswordIsModify(Date pwdUpdateDate) {
Integer initPasswordModify = Convert.toInt(
configService.selectConfigByKey("sys.account.initPasswordModify")
);
// 启用策略 且 从未修改过密码(pwdUpdateDate 为空)
return initPasswordModify != null && initPasswordModify == 1 && pwdUpdateDate == null;
}
判断逻辑:
pwdUpdateDate == null:表示用户从未修改过密码(使用的是管理员分配的初始密码)- 用户修改密码后,
pwdUpdateDate会被更新为当前时间
5.4 效果展示
- 管理员创建新用户,设置初始密码
- 新用户首次登录成功
- 系统检测到
pwdUpdateDate为空 - 强制跳转到修改密码页面
- 用户修改密码后,
pwdUpdateDate更新 - 后续登录不再强制修改
六、IP 黑名单
6.1 功能说明
阻止特定 IP 地址访问系统,支持精确 IP、通配符、网段三种匹配方式。
6.2 配置方式
参数名称:用户登录-黑名单列表
参数键名:sys.login.blackIPList
参数键值:192.168.1.100;192.168.2.*;10.0.0.0/8
配置格式说明:
| 格式 | 示例 | 说明 |
|---|---|---|
| 精确 IP | 192.168.1.100 | 匹配单个 IP |
| 通配符 | 192.168.1.* | 匹配 192.168.1.0 - 192.168.1.255 |
| 网段 | 10.0.0.0/8 | 匹配 10.0.0.0 - 10.255.255.255 |
| 多个规则 | 用分号 ; 分隔 | 任一规则匹配即拦截 |
6.3 实现原理
登录前置校验:
// SysLoginService.java
public void loginPreCheck(String username, String password) {
// ... 其他校验 ...
// IP 黑名单校验
String blackStr = configService.selectConfigByKey("sys.login.blackIPList");
if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr())) {
AsyncManager.me().execute(
AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL,
MessageUtils.message("login.blocked"))
);
throw new BlackListException();
}
}
IP 匹配工具类:
// IpUtils.java
public static boolean isMatchedIp(String filter, String ip) {
if (StringUtils.isEmpty(filter) || StringUtils.isEmpty(ip)) {
return false;
}
String[] ips = filter.split(";");
for (String iStr : ips) {
// 精确匹配
if (isIP(iStr) && iStr.equals(ip)) {
return true;
}
// 通配符匹配(如 192.168.1.*)
else if (isIpWildCard(iStr) && ipIsInWildCardNoCheck(iStr, ip)) {
return true;
}
// 网段匹配(如 10.0.0.0/8)
else if (isIPSegment(iStr) && ipIsInNetNoCheck(iStr, ip)) {
return true;
}
}
return false;
}
6.4 效果展示
黑名单中的 IP 尝试登录时:
{
"code": 500,
"msg": "很抱歉,您的IP已被列入系统黑名单"
}
同时记录登录日志,便于安全审计。
七、用户无操作自动登出
7.1 功能说明
当用户在指定时间内无任何操作时,系统自动登出以保护账户安全。
7.2 配置方式
在前端环境变量文件中配置:
# .env.production
VITE_LOGOUT_LIMIT=1800000 # 30分钟(毫秒)
配置说明:
1800000:30 分钟(30 × 60 × 1000 毫秒)3600000:60 分钟0或不配置:关闭此功能
7.3 实现原理
前端实现(App.vue):
onMounted(() => {
const logoutLimit = import.meta.env.VITE_LOGOUT_LIMIT;
if (logoutLimit && logoutLimit > 0) {
// 设置自动登出计时器
let logoutTimer = setTimeout(logout, logoutLimit);
// 用户操作后重置计时器
let userOpDelay = () => {
clearTimeout(logoutTimer);
logoutTimer = setTimeout(logout, logoutLimit);
};
// 监听用户操作事件
document.getElementById('app').addEventListener('keydown', userOpDelay); // 键盘
document.getElementById('app').addEventListener('mousemove', userOpDelay); // 鼠标移动
document.getElementById('app').addEventListener('mousedown', userOpDelay); // 鼠标点击
document.getElementById('app').addEventListener('click', userOpDelay); // 点击
document.getElementById('app').addEventListener('scroll', userOpDelay); // 滚动
}
});
function logout() {
userStore.logOut().then(() => {
location.href = '/admin/index';
});
}
7.4 监控的用户操作
| 事件类型 | 说明 |
|---|---|
keydown | 键盘按键 |
mousemove | 鼠标移动 |
mousedown | 鼠标按下 |
click | 鼠标点击 |
scroll | 页面滚动 |
7.5 效果展示
- 用户登录系统
- 开始 30 分钟倒计时
- 用户进行任何操作 → 重置倒计时
- 30 分钟内无操作 → 自动登出,跳转登录页
八、安全审计日志
所有安全相关操作都会记录到登录日志表,便于安全审计:
// 记录登录失败日志
AsyncManager.me().execute(
AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, "用户名或密码错误")
);
// 记录登录成功日志
AsyncManager.me().execute(
AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, "登录成功")
);
// 记录 IP 黑名单拦截日志
AsyncManager.me().execute(
AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, "IP已被列入黑名单")
);
日志查询:
系统管理 → 日志管理 → 登录日志
| 字段 | 说明 |
|---|---|
| 用户名 | 登录用户名 |
| IP 地址 | 登录 IP |
| 登录状态 | 成功/失败 |
| 提示消息 | 详细信息 |
| 登录时间 | 操作时间 |
九、最佳实践
9.1 推荐配置
# 密码有效期:90天
sys.account.passwordValidateDays = 90
# 登录失败锁定:5次失败锁定30分钟
sys.account.tryLoginCount = 5-30
# 初始密码强制修改:启用
sys.account.initPasswordModify = 1
# IP 黑名单:根据实际情况配置
sys.login.blackIPList =
# 前端无操作超时:30分钟
VITE_LOGOUT_LIMIT = 1800000
9.2 密码复杂度建议
配合密码复杂度校验,建议密码满足:
- 长度至少 8 位
- 包含大写字母、小写字母、数字、特殊字符中的至少 3 种
- 不能与用户名相同
- 不能与最近 N 次使用的密码相同
9.3 安全加固建议
| 建议项 | 说明 |
|---|---|
| 启用 HTTPS | 防止密码在传输过程中被窃取 |
| 密码加密传输 | 前端使用 RSA 加密密码后传输 |
| 验证码 | 启用图形验证码,防止自动化攻击 |
| 定期审计 | 定期检查登录日志,发现异常行为 |
| 最小权限 | 用户只分配必要的权限 |
十、常见问题
10.1 忘记密码被锁定怎么办?
管理员可以通过以下方式解锁:
-- 方式一:清除 Redis 缓存
DEL {username}-tryGtCount
-- 方式二:重置数据库失败次数
UPDATE sys_user SET try_count = 0 WHERE user_name = 'xxx';
10.2 如何临时关闭等保策略?
将对应参数值设置为 0 即可:
sys.account.passwordValidateDays = 0
sys.account.tryLoginCount = 0
sys.account.initPasswordModify = 0
10.3 配置修改后需要重启吗?
不需要。所有配置通过系统参数表管理,修改后立即生效。
10.4 如何查看被锁定的用户?
-- 查询 Redis 中的锁定记录
KEYS *-tryGtCount
-- 查询数据库中失败次数较高的用户
SELECT user_name, try_count FROM sys_user WHERE try_count > 0;
十一、总结
RuoYi-SpringBoot3-Pro 的三级等保安全方案具有以下特点:
- 开箱即用:内置完善的安全策略,无需额外开发
- 灵活配置:所有策略通过系统参数动态管理
- 无需重启:配置修改后立即生效
- 高性能:使用 Redis 缓存,支持分布式部署
- 完整审计:所有安全操作记录日志,便于审计
- 前后端协同:后端校验 + 前端超时登出,全方位保护
1094

被折叠的 条评论
为什么被折叠?



