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

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
+55 -4
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 = '修改密码失败,请稍后重试';
+22 -13
View File
@@ -312,7 +312,7 @@
password: [{ required: true, message: '请输入密码', trigger: 'blur' }]
},
loading: false,
errorMessage: ''
errorMessage: {{ (error or '')|tojson }}
}
},
methods: {
@@ -323,23 +323,32 @@
const formData = new FormData();
formData.append('username', this.loginForm.username);
formData.append('password', this.loginForm.password);
fetch('/login', { method: 'POST', body: formData })
axios.post('/login', formData, {
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Accept': 'application/json'
}
})
.then(response => {
if (response.redirected) {
window.location.href = response.url;
} else {
return response.text();
}
})
.then(html => {
if (html) {
this.errorMessage = '用户名或密码错误';
this.loading = false;
const data = response.data || {};
if (data.success && data.redirect_url) {
window.location.href = data.redirect_url;
return;
}
this.errorMessage = data.error || '登录失败,请稍后重试';
this.loading = false;
})
.catch(error => {
console.error('登录出错:', error);
this.errorMessage = '登录请求失败,请稍后重试';
let errorMsg = '登录请求失败,请稍后重试';
try {
if (error && error.response && error.response.data && error.response.data.error) {
errorMsg = error.response.data.error;
}
} catch (e) {
// 读取错误详情失败时保留默认提示即可。
}
this.errorMessage = errorMsg;
this.loading = false;
});
}