增强后台登录安全与密码策略

This commit is contained in:
liuwei
2026-04-30 15:32:41 +08:00
parent 2d5a5547de
commit cb99e94493
6 changed files with 342 additions and 26 deletions

View File

@@ -929,6 +929,9 @@
:visible.sync="passwordDialogVisible"
width="460px"
:close-on-click-modal="false"
:close-on-press-escape="!passwordDialogLocked"
:show-close="!passwordDialogLocked"
:before-close="handlePasswordDialogBeforeClose"
custom-class="password-dialog">
<el-form
ref="passwordFormRef"
@@ -939,17 +942,17 @@
<el-input v-model="passwordForm.old_password" type="password" show-password autocomplete="off" placeholder="请输入当前密码"></el-input>
</el-form-item>
<el-form-item label="新密码" prop="new_password">
<el-input v-model="passwordForm.new_password" type="password" show-password autocomplete="off" placeholder="至少6位"></el-input>
<el-input v-model="passwordForm.new_password" type="password" show-password autocomplete="off" placeholder="至少8位建议字母+数字/符号组合"></el-input>
</el-form-item>
<el-form-item label="确认新密码" prop="confirm_password">
<el-input v-model="passwordForm.confirm_password" type="password" show-password autocomplete="off" placeholder="请再次输入新密码"></el-input>
</el-form-item>
</el-form>
<div class="password-dialog-tip">
提示:修改成功后将立即生效,建议使用强密码(字母、数字、符号组合)。
提示:修改成功后将立即生效,建议使用强密码(至少 8 位,且包含两类以上字符)。
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="passwordDialogVisible = false">取消</el-button>
<el-button v-if="!passwordDialogLocked" @click="passwordDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="passwordSubmitting" @click="submitPasswordChange">确认修改</el-button>
</span>
</el-dialog>
@@ -1043,6 +1046,7 @@
navGroups: NAV_GROUPS,
// 账号密码修改弹窗状态。
passwordDialogVisible: false,
passwordDialogLocked: false,
passwordSubmitting: false,
passwordForm: {
old_password: '',
@@ -1055,7 +1059,22 @@
],
new_password: [
{ required: true, message: '请输入新密码', trigger: 'blur' },
{ min: 6, message: '新密码长度至少6位', trigger: 'blur' }
{ min: 8, message: '新密码长度至少8位', trigger: 'blur' },
{
validator: function(rule, value, callback) {
const passwordText = String(value || '');
let score = 0;
if (/[A-Za-z]/.test(passwordText)) score += 1;
if (/\d/.test(passwordText)) score += 1;
if (/[^A-Za-z0-9]/.test(passwordText)) score += 1;
if (passwordText && score < 2) {
callback(new Error('新密码需至少包含字母、数字、符号中的两类'));
return;
}
callback();
},
trigger: 'blur'
}
],
confirm_password: [
{ required: true, message: '请再次输入新密码', trigger: 'blur' },
@@ -1094,6 +1113,7 @@
},
mounted() {
document.querySelector('.app-container').classList.add('loaded');
this.loadSecurityStatus();
},
methods: {
normalizePath(path) {
@@ -1149,6 +1169,29 @@
window.location.href = '/logout';
});
},
loadSecurityStatus() {
// 登录后主动拉一次账号安全状态:
// 1. 若仍在使用默认/弱口令,这里会强制打开改密弹窗;
// 2. 这样用户不需要额外摸索入口,首次进入后台就能完成风险收敛。
axios.get('/api/auth/security_status')
.then((response) => {
const data = (response.data || {}).data || {};
if (data.force_password_change) {
this.passwordDialogLocked = true;
this.openPasswordDialog();
this.$alert('当前后台账号仍在使用默认或弱密码,请先修改密码后再继续操作。', '安全提示', {
confirmButtonText: '去修改',
showClose: false,
closeOnClickModal: false,
closeOnPressEscape: false,
type: 'warning'
}).catch(() => {});
}
})
.catch(() => {
// 安全状态获取失败时不阻塞页面使用,避免偶发接口异常影响整体后台。
});
},
openPasswordDialog() {
// 打开弹窗前重置表单,避免上次输入残留。
this.passwordDialogVisible = true;
@@ -1164,6 +1207,13 @@
}
});
},
handlePasswordDialogBeforeClose(done) {
if (this.passwordDialogLocked) {
this.$message.warning('当前账号需要先完成密码修改,暂时不能关闭该弹窗。');
return;
}
done();
},
submitPasswordChange() {
if (!this.$refs.passwordFormRef) {
return;
@@ -1182,6 +1232,7 @@
}
this.$message.success(data.message || '密码修改成功');
this.passwordDialogVisible = false;
this.passwordDialogLocked = false;
})
.catch((error) => {
let errorMsg = '修改密码失败,请稍后重试';