feat(security): 增加登录频率限制和TOTP二次验证访问控制
- 配置文件中更新数据库密码 - 前端视图中改进TOTP模态框,增加二次验证步骤和状态切换 - 新增前端TOTP验证逻辑,通过Ajax与后端交互验证权限与操作 - 登录控制器中添加每分钟6次的IP登录频率限制,防止暴力尝试 - 修正登录逻辑,阻止频率超限请求,返回友好提示 - 增加TOTP访问权限接口,验证用户访问TOTP信息时需先通过二次验证 - 实现临时10分钟内有效的TOTP访问权限Session管理 - 路由中新增TOTP访问验证路由,支持前端二次验证流程 - 并发安全处理登录频率限制数据,防止竞态条件 - 前端按钮显示与隐藏按验证状态动态变化,提升用户体验
This commit is contained in:
174
views/index.html
174
views/index.html
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user