新增登录页图形验证码功能,支持通过Redis全局配置控制验证码的启用状态,优化登录流程以提升用户体验。新增相关API接口和前端配置项,确保验证码逻辑与后端同步。

This commit is contained in:
geht
2026-04-20 14:21:36 +08:00
parent 7a648b20be
commit 73426a7af3
23 changed files with 450 additions and 103 deletions

View File

@@ -12,6 +12,7 @@ import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.constant.CacheConstant;
@@ -737,6 +738,46 @@ public class LoginController {
}
return Result.ok();
}
/**
* 登录页是否启用图形验证码(未登录可访问)。优先 Redis 全局配置(与侧边栏项目配置同步),否则沿用 jeecg.firewall.enable-login-captcha
*/
@IgnoreAuth
@GetMapping("/loginCaptchaConfig")
@Operation(summary = "登录页是否启用图形验证码")
public Result<Boolean> loginCaptchaConfig() {
return Result.OK(isLoginCaptchaRequired());
}
/**
* 保存登录页图形验证码开关(写入 Redis全站生效需权限 system:project:setting:loginCaptcha
*/
@PostMapping("/setLoginCaptchaConfig")
@Operation(summary = "保存登录页图形验证码开关")
@RequiresPermissions("system:project:setting:loginCaptcha")
public Result<String> setLoginCaptchaConfig(@RequestBody JSONObject body) {
Boolean enabled = body.getBoolean("enabled");
if (enabled == null) {
return Result.error("参数 enabled 不能为空");
}
redisUtil.set(CommonConstant.SYS_LOGIN_CAPTCHA_ENABLED, enabled.toString());
baseCommonService.addLog("保存登录验证码开关: " + enabled, CommonConstant.LOG_TYPE_2, CommonConstant.OPERATE_TYPE_3);
return Result.OK("保存成功");
}
/**
* 是否需要在登录时校验图形验证码Redis 覆盖 &gt; firewall 配置)
*/
private boolean isLoginCaptchaRequired() {
Object v = redisUtil.get(CommonConstant.SYS_LOGIN_CAPTCHA_ENABLED);
if (v != null) {
return Boolean.parseBoolean(v.toString());
}
return jeecgBaseConfig.getFirewall() == null
|| jeecgBaseConfig.getFirewall().getEnableLoginCaptcha() == null
|| Boolean.TRUE.equals(jeecgBaseConfig.getFirewall().getEnableLoginCaptcha());
}
/**
* 登录二维码
*/
@@ -917,9 +958,9 @@ public class LoginController {
* 校验验证码工具方法校验失败直接返回Result校验通过返回realKey
*/
private String validateCaptcha(SysLoginModel sysLoginModel, Result<JSONObject> result) {
// 判断是否启用登录验证码校验
if (jeecgBaseConfig.getFirewall() != null && Boolean.FALSE.equals(jeecgBaseConfig.getFirewall().getEnableLoginCaptcha())) {
log.warn("关闭登录验证码校验,跳过验证码校验!");
// 是否启用登录验证码(含 Redis 全局开关与 firewall 配置)
if (!isLoginCaptchaRequired()) {
log.warn("关闭登录验证码校验Redis 全局或 firewall,跳过验证码校验!");
return "LoginWithoutVerifyCode";
}

View File

@@ -52,6 +52,11 @@ public class SysCheckRuleServiceImpl extends ServiceImpl<SysCheckRuleMapper, Sys
for (int i = 0; i < rules.size(); i++) {
JSONObject result = new JSONObject();
JSONObject rule = rules.getJSONObject(i);
// 全局规则可选「不执行」priority 为 2 时跳过本条(仅保留配置,不参与校验)
String priority = rule.getString("priority");
if ("2".equals(priority)) {
continue;
}
// 位数
String digits = rule.getString("digits");
result.put("digits", digits);

View File

@@ -0,0 +1,21 @@
-- 编码校验规则系统登录密码规则编码 sys_login_password
-- 全局规则拆成多条长度 字母 数字 特殊字符按顺序校验先失败先提示
DELETE FROM sys_check_rule WHERE rule_code = 'sys_login_password';
INSERT INTO sys_check_rule (
id,
rule_name,
rule_code,
rule_json,
rule_description,
create_by,
create_time
) VALUES (
'1990000000000000001',
'系统登录密码',
'sys_login_password',
CONVERT(UNHEX('5b7b22646967697473223a222a222c227061747465726e223a225e2e7b382c7d24222c226d657373616765223a22e5af86e7a081e995bfe5baa6e887b3e5b091e4b8ba203820e4bd8d222c227072696f72697479223a2231227d2c7b22646967697473223a222a222c227061747465726e223a225e283f3d2e2a5b612d7a412d5a5d292e2a24222c226d657373616765223a22e5af86e7a081e5bf85e9a1bbe58c85e590abe5ad97e6af8d222c227072696f72697479223a2231227d2c7b22646967697473223a222a222c227061747465726e223a225e283f3d2e2a5c5c64292e2a24222c226d657373616765223a22e5af86e7a081e5bf85e9a1bbe58c85e590abe695b0e5ad97222c227072696f72697479223a2231227d2c7b22646967697473223a222a222c227061747465726e223a225e283f3d2e2a5b7e21402324255e262a28295f2b5c5c2d3d7b7d5c5c5b5c5c5d3a3b5c225c5c75303032373c3e3f2c2e2f5d292e2a24222c226d657373616765223a22e5af86e7a081e5bf85e9a1bbe58c85e590abe789b9e6ae8ae5ad97e7aca6efbc88e5a682207e214023242520e7ad89efbc89222c227072696f72697479223a2231227d5d') USING utf8mb4),
'供用户管理修改密码等调用全局规则共 4 长度/字母/数字/特殊字符与前端 utils/sys/sysPasswordRules 约定规则编码 sys_login_password',
'admin',
NOW()
);

View File

@@ -0,0 +1,20 @@
-- 已部署环境若已执行过 V3.9.2_1将单条正则升级为多条全局规则拆分 V3.9.2_1 最终内容一致可重复执行
DELETE FROM sys_check_rule WHERE rule_code = 'sys_login_password';
INSERT INTO sys_check_rule (
id,
rule_name,
rule_code,
rule_json,
rule_description,
create_by,
create_time
) VALUES (
'1990000000000000001',
'系统登录密码',
'sys_login_password',
CONVERT(UNHEX('5b7b22646967697473223a222a222c227061747465726e223a225e2e7b382c7d24222c226d657373616765223a22e5af86e7a081e995bfe5baa6e887b3e5b091e4b8ba203820e4bd8d222c227072696f72697479223a2231227d2c7b22646967697473223a222a222c227061747465726e223a225e283f3d2e2a5b612d7a412d5a5d292e2a24222c226d657373616765223a22e5af86e7a081e5bf85e9a1bbe58c85e590abe5ad97e6af8d222c227072696f72697479223a2231227d2c7b22646967697473223a222a222c227061747465726e223a225e283f3d2e2a5c5c64292e2a24222c226d657373616765223a22e5af86e7a081e5bf85e9a1bbe58c85e590abe695b0e5ad97222c227072696f72697479223a2231227d2c7b22646967697473223a222a222c227061747465726e223a225e283f3d2e2a5b7e21402324255e262a28295f2b5c5c2d3d7b7d5c5c5b5c5c5d3a3b5c225c5c75303032373c3e3f2c2e2f5d292e2a24222c226d657373616765223a22e5af86e7a081e5bf85e9a1bbe58c85e590abe789b9e6ae8ae5ad97e7aca6efbc88e5a682207e214023242520e7ad89efbc89222c227072696f72697479223a2231227d5d') USING utf8mb4),
'供用户管理修改密码等调用全局规则共 4 长度/字母/数字/特殊字符与前端 utils/sys/sysPasswordRules 约定规则编码 sys_login_password',
'admin',
NOW()
);

View File

@@ -0,0 +1,5 @@
-- 项目配置抽屉验证码登录开关的按钮权限授权后可在界面设置中看到该开关
INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `url`, `component`, `is_route`, `component_name`, `redirect`, `menu_type`, `perms`, `perms_type`, `sort_no`, `always_show`, `icon`, `is_leaf`, `keep_alive`, `hidden`, `hide_tab`, `description`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `rule_flag`, `status`, `internal_or_external`) VALUES ('2000210000000000001', 'd7d6e2e4e2934f2c9385a623fd98c6f3', '项目配置-验证码登录开关', NULL, NULL, 0, NULL, NULL, 2, 'system:project:setting:loginCaptcha', '1', 99.00, 0, NULL, 1, 0, 0, 0, '控制项目配置抽屉中验证码登录开关是否展示', 'admin', NOW(), NULL, NULL, 0, 0, '1', 0);
-- 默认授权给管理员角色与历史脚本中 admin 角色 id 一致
INSERT INTO `sys_role_permission` (`id`, `role_id`, `permission_id`, `data_rule_ids`, `operate_date`, `operate_ip`) VALUES ('2000210000000000002', 'f6817f48af4fb3af11b9e8bf182f618b', '2000210000000000001', NULL, NOW(), '127.0.0.1');