feat(security): 增加登录频率限制和TOTP二次验证访问控制

- 配置文件中更新数据库密码
- 前端视图中改进TOTP模态框,增加二次验证步骤和状态切换
- 新增前端TOTP验证逻辑,通过Ajax与后端交互验证权限与操作
- 登录控制器中添加每分钟6次的IP登录频率限制,防止暴力尝试
- 修正登录逻辑,阻止频率超限请求,返回友好提示
- 增加TOTP访问权限接口,验证用户访问TOTP信息时需先通过二次验证
- 实现临时10分钟内有效的TOTP访问权限Session管理
- 路由中新增TOTP访问验证路由,支持前端二次验证流程
- 并发安全处理登录频率限制数据,防止竞态条件
- 前端按钮显示与隐藏按验证状态动态变化,提升用户体验
This commit is contained in:
danial
2025-11-24 22:39:12 +08:00
parent 4f3cd74fea
commit 74b11c4c70
6 changed files with 484 additions and 30 deletions

View File

@@ -200,32 +200,50 @@
<h4 class="modal-title" id="totpLabel">TOTP二次验证</h4>
</div>
<div class="modal-body">
<div class="totp-regeneration">
<!-- 验证步骤 - 默认显示 -->
<div id="totp-verify-step">
<div class="row">
当前标识:<span id="totp-key"></span>
<h4 class="text-center">查看TOTP信息需要二次验证</h4>
</div>
<div class="row">
<button class="btn btn-warning totp-regeneration-btn" data-toggle="tooltip"
title="重新生成将导致此前的二次验证不可用,请谨慎生成">重新生成
</button>
<div class="row margin-top-20">
<label>请输入二次验证码:
<input id="totp-access-code" type="text" name="" placeholder="请输入6位验证码" maxlength="6">
</label>
</div>
<div class="col-xs-12 color-red totp-access-error">
</div>
</div>
<div class="row margin-top-20 totp-body">
<div id="totp-img">
<img src="" alt="" srcset="">
<input value="" id="totp-secret" hidden>
<!-- 显示TOTP信息 - 验证成功后显示 -->
<div id="totp-display-step" style="display: none;">
<div class="totp-regeneration">
<div class="row">
当前标识:<span id="totp-key"></span>
</div>
<div class="row">
<button class="btn btn-warning totp-regeneration-btn" data-toggle="tooltip"
title="重新生成将导致此前的二次验证不可用,请谨慎生成">重新生成
</button>
</div>
</div>
<div class="row margin-top-20 totp-body">
<div id="totp-img">
<img src="" alt="" srcset="">
<input value="" id="totp-secret" hidden>
</div>
<label>请输入二次验证:
<input id="totp-value" type="text" name="">
</label>
</div>
<div class="col-xs-4 color-red totp-new-error">
</div>
<label>请输入二次验证:
<input id="totp-value" type="text" name="">
</label>
</div>
<div class="col-xs-4 color-red totp-new-error">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default totp-cancal cancal-save" data-dismiss="modal">取消
</button>
<button type="button" class="btn btn-primary totp-save">保存</button>
<button type="button" class="btn btn-primary" id="totp-verify-btn">验证</button>
<button type="button" class="btn btn-primary totp-save" style="display: none;">保存</button>
</div>
</div>
</div>
@@ -687,18 +705,57 @@
});
$("#totpModal").on('show.bs.modal', function () {
// 重置模态框状态
resetTotpModal();
// 先尝试验证用户是否已设置TOTP
$.ajax({
url: "/user/genTotp",
url: "/user/verifyTotpAccess",
method: "post",
data: { code: "check" }, // 特殊代码用于检查状态
success: (res) => {
if (res.Code === 0) {
$("#totp-img img").attr("src", res.Data.qrImage);
$("#totp-secret").attr("value", res.Data.secret);
$("#totp-key").text(res.Data.key);
if (res.Code === 1) {
// 用户未设置TOTP直接显示设置界面
showTotpSetup();
} else if (res.Code === 0) {
// 验证通过显示TOTP信息
loadTotpInfo();
} else {
alert(res.Msg)
// 需要验证,显示验证界面
showTotpVerify();
}
},
error: () => {
// 错误时显示验证界面
showTotpVerify();
}
})
});
// TOTP验证按钮点击事件
$("#totp-verify-btn").click(function () {
const code = $("#totp-access-code").val();
if (code === "") {
setError(".totp-access-error", "请输入二次验证码");
return;
}
$.ajax({
url: "/user/verifyTotpAccess",
method: "post",
data: { code: code },
success: (res) => {
if (res.Code === 0) {
// 验证成功加载TOTP信息
loadTotpInfo();
} else {
setError(".totp-access-error", res.Msg);
}
},
error: (err) => {
setError(".totp-access-error", "验证失败,请重试");
}
})
});
@@ -720,6 +777,79 @@
},
})
})
// 辅助函数:重置模态框状态
function resetTotpModal() {
$("#totp-access-code").val("");
$("#totp-value").val("");
$(".totp-access-error").text("");
$(".totp-new-error").text("");
$("#totp-verify-step").show();
$("#totp-display-step").hide();
$("#totp-verify-btn").show();
$(".totp-save").hide();
}
// 显示验证界面
function showTotpVerify() {
$("#totp-verify-step").show();
$("#totp-display-step").hide();
$("#totp-verify-btn").show();
$(".totp-save").hide();
}
// 显示TOTP设置界面
function showTotpSetup() {
$("#totp-verify-step").hide();
$("#totp-display-step").show();
$("#totp-verify-btn").hide();
$(".totp-save").show();
// 生成新的TOTP
$.ajax({
url: "/user/genTotp",
method: "post",
data: { newTotp: 1 },
success: (res) => {
if (res.Code === 0) {
$("#totp-img img").attr("src", res.Data.qrImage);
$("#totp-secret").attr("value", res.Data.secret);
$("#totp-key").text(res.Data.key);
} else {
alert(res.Msg)
}
},
})
}
// 加载TOTP信息
function loadTotpInfo() {
$("#totp-verify-step").hide();
$("#totp-display-step").show();
$("#totp-verify-btn").hide();
$(".totp-save").show();
$.ajax({
url: "/user/genTotp",
method: "post",
success: (res) => {
if (res.Code === 0) {
$("#totp-img img").attr("src", res.Data.qrImage);
$("#totp-secret").attr("value", res.Data.secret);
$("#totp-key").text(res.Data.key);
} else if (res.Code === -2) {
// 需要重新验证
showTotpVerify();
} else {
alert(res.Msg);
showTotpVerify();
}
},
error: () => {
alert("获取TOTP信息失败");
showTotpVerify();
}
})
}
$(".totp-save").click(function (event) {
const secret = $("#totp-secret").val();
const code = $("#totp-value").val();