【RuoYi-SpringBoot3-Pro】: 三级等保安全配置

本文详细介绍 RuoYi-SpringBoot3-Pro[1] 框架中内置的三级等保安全策略,帮助企业快速满足国家信息安全等级保护三级要求。

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密码有效期(天)900(不限制)
sys.account.tryLoginCount登录失败锁定策略5-300(不限制)
sys.account.initPasswordModify初始密码强制修改10(关闭)
sys.login.blackIPListIP 黑名单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 效果展示

四、登录失败锁定

4.1 功能说明

防止暴力破解密码,连续多次登录失败后自动锁定账号。

4.2 配置方式


    
    
    
  参数名称:用户管理-账号密码尝试登录次数
参数键名:sys.account.tryLoginCount
参数键值:5-30  (5次失败后锁定30分钟,格式:次数-锁定时长)

配置格式说明:

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;
}

判断逻辑:

5.4 效果展示

  1. 1. 管理员创建新用户,设置初始密码
  2. 2. 新用户首次登录成功
  3. 3. 系统检测到 pwdUpdateDate 为空
  4. 4. 强制跳转到修改密码页面
  5. 5. 用户修改密码后,pwdUpdateDate 更新
  6. 6. 后续登录不再强制修改

六、IP 黑名单

6.1 功能说明

阻止特定 IP 地址访问系统,支持精确 IP、通配符、网段三种匹配方式。

6.2 配置方式


    
    
    
  参数名称:用户登录-黑名单列表
参数键名:sys.login.blackIPList
参数键值:192.168.1.100;192.168.2.*;10.0.0.0/8

配置格式说明:

格式示例说明
精确 IP192.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分钟(毫秒)

配置说明:

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 效果展示

  1. 1. 用户登录系统
  2. 2. 开始 30 分钟倒计时
  3. 3. 用户进行任何操作 → 重置倒计时
  4. 4. 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 密码复杂度建议

配合密码复杂度校验,建议密码满足:

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 的三级等保安全方案具有以下特点:


引用链接

[1] RuoYi-SpringBoot3-Pro: https://github.com/undsky/RuoYi-SpringBoot3-Pro
[2] RuoYi-SpringBoot3-Pro 文档: https://www.undsky.com/blog/?category=RuoYi-SpringBoot3-Pro#