😎增加强制修改密码策略

This commit is contained in:
zuohuaijun 2024-09-01 13:07:42 +08:00
parent 9da7412017
commit a691937f4e
8 changed files with 139 additions and 19 deletions

View File

@ -247,6 +247,7 @@ public class SysConfigService : IDynamicApiController, ITransient
var sysIcpUrl = await GetConfigValue<string>(ConfigConst.SysWebIcpUrl);
var sysSecondVer = await GetConfigValue<bool>(ConfigConst.SysSecondVer);
var sysCaptcha = await GetConfigValue<bool>(ConfigConst.SysCaptcha);
var sysForceChangePassword = await GetConfigValue<bool>(ConfigConst.SysForceChangePassword);
return new
{
@ -259,7 +260,8 @@ public class SysConfigService : IDynamicApiController, ITransient
SysIcp = sysIcp,
SysIcpUrl = sysIcpUrl,
SysSecondVer = sysSecondVer,
SysCaptcha = sysCaptcha
SysCaptcha = sysCaptcha,
SysForceChangePassword = sysForceChangePassword
};
}

View File

@ -307,7 +307,8 @@ public class SysUserService : IDynamicApiController, ITransient
user.Password = CryptogramUtil.Encrypt(input.PasswordNew);
}
return await _sysUserRep.AsUpdateable(user).UpdateColumns(u => u.Password).ExecuteCommandAsync();
user.LastChangePasswordTime = DateTime.Now;
return await _sysUserRep.AsUpdateable(user).UpdateColumns(u => new { u.Password, u.LastChangePasswordTime }).ExecuteCommandAsync();
}
/// <summary>
@ -321,7 +322,8 @@ public class SysUserService : IDynamicApiController, ITransient
var user = await _sysUserRep.GetByIdAsync(input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D0009);
var password = await _sysConfigService.GetConfigValue<string>(ConfigConst.SysPassword);
user.Password = CryptogramUtil.Encrypt(password);
await _sysUserRep.AsUpdateable(user).UpdateColumns(u => u.Password).ExecuteCommandAsync();
user.LastChangePasswordTime = null;
await _sysUserRep.AsUpdateable(user).UpdateColumns(u => new { u.Password, u.LastChangePasswordTime }).ExecuteCommandAsync();
// 清空密码错误次数
var keyPasswordErrorTimes = $"{CacheConst.KeyPasswordErrorTimes}{user.Account}";

View File

@ -26,7 +26,7 @@
"@wangeditor/editor-for-vue": "^5.1.12",
"animate.css": "^4.1.1",
"async-validator": "^4.2.5",
"axios": "^1.7.6",
"axios": "^1.7.7",
"countup.js": "^2.8.0",
"cropperjs": "^1.6.2",
"echarts": "^5.5.1",

View File

@ -38,7 +38,6 @@ const route = useRoute();
const stores = useTagsViewRoutes();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const needUpdate = ref(false);
//
const setLockScreen = computed(() => {
@ -47,28 +46,16 @@ const setLockScreen = computed(() => {
return themeConfig.value.isLockScreen ? themeConfig.value.lockScreenTime > 1 : themeConfig.value.lockScreenTime >= 0;
});
// //
// const getVersion = computed(() => {
// let isVersion = false;
// if (route.path !== '/login') {
// // @ts-ignore
// if ((Local.get('version') && Local.get('version') !== __NEXT_VERSION__) || !Local.get('version')) isVersion = true;
// }
// return isVersion;
// });
// checkUpdate(() => {
// needUpdate.value = true;
// }, 60000);
//
const getGlobalComponentSize = computed(() => {
return other.globalComponentSize();
});
// i18n
const getGlobalI18n = computed(() => {
return messages.value[locale.value];
});
//
onBeforeMount(() => {
// icon
@ -76,6 +63,7 @@ onBeforeMount(() => {
// js
setIntroduction.jsCdn();
});
//
onMounted(() => {
nextTick(() => {
@ -94,10 +82,12 @@ onMounted(() => {
}
});
});
// /i18n
onUnmounted(() => {
mittBus.off('openSettingsDrawer', () => {});
});
//
watch(
() => route.path,
@ -136,6 +126,8 @@ const loadSysInfo = () => {
//
themeConfig.value.secondVer = data.sysSecondVer;
themeConfig.value.captcha = data.sysCaptcha;
//
themeConfig.value.sysForceChangePassword = data.sysForceChangePassword;
// favicon
updateFavicon(data.sysLogo);

View File

@ -79,8 +79,10 @@
</el-dropdown-menu>
</template>
</el-dropdown>
<Search ref="searchRef" />
<OnlineUser ref="onlineUserRef" />
<ChangePassword ref="changePasswordRef" />
</div>
</template>
@ -107,6 +109,7 @@ import { SysAuthApi, SysNoticeApi } from '/@/api-services/api';
const UserNews = defineAsyncComponent(() => import('/@/layout/navBars/topBar/userNews.vue'));
const Search = defineAsyncComponent(() => import('/@/layout/navBars/topBar/search.vue'));
const OnlineUser = defineAsyncComponent(() => import('/@/views/system/onlineUser/index.vue'));
const ChangePassword = defineAsyncComponent(() => import('/@/views/system/user/component/changePassword.vue'));
//
const { locale, t } = useI18n();
@ -117,6 +120,7 @@ const { userInfos } = storeToRefs(stores);
const { themeConfig } = storeToRefs(storesThemeConfig);
const searchRef = ref();
const onlineUserRef = ref();
const changePasswordRef = ref();
const state = reactive({
isScreenfull: false,
disabledI18n: 'zh-cn',
@ -251,6 +255,9 @@ onMounted(async () => {
// //
// notice.readStatus = 1;
// });
//
forceChangePassword();
});
// //
// onUnmounted(() => {
@ -272,6 +279,16 @@ const receiveNotice = (msg: any) => {
timeout: 4500, //
});
};
//
const forceChangePassword = () => {
var forceChangePasswordEnabled = themeConfig.value.sysForceChangePassword ?? true;
if (!forceChangePasswordEnabled) return;
if (userInfos.value.lastChangePasswordTime == null || userInfos.value.lastChangePasswordTime == undefined) {
changePasswordRef.value?.openDialog();
}
};
</script>
<style scoped lang="scss">

View File

@ -84,6 +84,7 @@ export const useUserInfo = defineStore('userInfo', {
posName: d.posName,
roles: d.roleIds,
authApiList: d.apis,
lastChangePasswordTime: d.lastChangePasswordTime,
time: new Date().getTime(),
};
// vue-next-admin 提交Id225bce7 提交消息admin-23.03.26:发布v2.4.32版本

View File

@ -100,5 +100,6 @@ declare interface ThemeConfigState {
icpUrl: string; // Icp地址
secondVer: boolean; // 是否开启二级验证
captcha: boolean; // 是否开启验证码
sysForceChangePassword: boolean; // 是否开启强制修改密码
};
}

View File

@ -0,0 +1,105 @@
<template>
<div class="changePassword-container">
<el-dialog v-model="state.isShowDialog" draggable :close-on-click-modal="false" width="700px" :show-close="false">
<template #header>
<div style="color: #fff">
<el-icon size="16" style="margin-right: 3px; display: inline; vertical-align: middle"> <ele-View /> </el-icon>
<span> 修改密码 </span>
</div>
</template>
<div style="color: red; padding: 10px 10px; background: #faecd8; margin-bottom: 30px">
<el-icon style="transform: translateY(2px)"><ele-Bell /></el-icon>
<span> 系统安全策略若长时间没用更新密码请定期更新密码 </span>
</div>
<el-form :model="state.ruleForm" ref="ruleFormRef" label-width="auto">
<el-row :gutter="10">
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
<el-form-item label="当前密码" prop="passwordOld" :rules="[{ required: true, message: '当前密码不能为空', trigger: 'blur' }]">
<el-input v-model="state.ruleForm.passwordOld" type="password" autocomplete="off" show-password />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
<el-form-item label="新密码" prop="passwordNew" :rules="[{ required: true, message: '新密码不能为空', trigger: 'blur' }]">
<el-input v-model="state.ruleForm.passwordNew" type="password" autocomplete="off" show-password />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
<el-form-item label="确认密码" prop="passwordNew2" :rules="[{ validator: validatePassword, required: true, trigger: 'blur' }]">
<el-input v-model="state.passwordNew2" type="password" autocomplete="off" show-password />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<!-- <el-button @click="cancel"> </el-button> -->
<el-button type="primary" @click="submit"> </el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue';
import { ElMessageBox } from 'element-plus';
import { sm2 } from 'sm-crypto-v2';
import { clearAccessTokens, getAPI } from '/@/utils/axios-utils';
import { SysUserApi } from '/@/api-services/api';
import { ChangePwdInput } from '/@/api-services/models';
const ruleFormRef = ref();
const state = reactive({
isShowDialog: false,
ruleForm: {} as ChangePwdInput,
passwordNew2: '',
});
//
const openDialog = () => {
state.isShowDialog = true;
ruleFormRef.value?.resetFields();
};
// //
// const cancel = () => {
// state.isShowDialog = false;
// };
//
const submit = () => {
ruleFormRef.value?.validate(async (valid: boolean) => {
if (!valid) return;
// SM2
const cpwd: ChangePwdInput = { passwordOld: '', passwordNew: '' };
const publicKey = window.__env__.VITE_SM_PUBLIC_KEY;
cpwd.passwordOld = sm2.doEncrypt(state.ruleForm.passwordOld, publicKey, 1);
cpwd.passwordNew = sm2.doEncrypt(state.ruleForm.passwordNew, publicKey, 1);
await getAPI(SysUserApi).apiSysUserChangePwdPost(cpwd);
// 退
ElMessageBox.confirm('密码已修改,是否重新登录系统?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(async () => {
state.isShowDialog = false;
clearAccessTokens();
});
});
};
//
const validatePassword = (_rule: any, value: any, callback: any) => {
if (state.passwordNew2 != state.ruleForm.passwordNew) {
callback(new Error('两次密码不一致!'));
} else {
callback();
}
};
//
defineExpose({ openDialog });
</script>