😎增加强制修改密码策略

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 sysIcpUrl = await GetConfigValue<string>(ConfigConst.SysWebIcpUrl);
var sysSecondVer = await GetConfigValue<bool>(ConfigConst.SysSecondVer); var sysSecondVer = await GetConfigValue<bool>(ConfigConst.SysSecondVer);
var sysCaptcha = await GetConfigValue<bool>(ConfigConst.SysCaptcha); var sysCaptcha = await GetConfigValue<bool>(ConfigConst.SysCaptcha);
var sysForceChangePassword = await GetConfigValue<bool>(ConfigConst.SysForceChangePassword);
return new return new
{ {
@ -259,7 +260,8 @@ public class SysConfigService : IDynamicApiController, ITransient
SysIcp = sysIcp, SysIcp = sysIcp,
SysIcpUrl = sysIcpUrl, SysIcpUrl = sysIcpUrl,
SysSecondVer = sysSecondVer, 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); 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> /// <summary>
@ -321,7 +322,8 @@ public class SysUserService : IDynamicApiController, ITransient
var user = await _sysUserRep.GetByIdAsync(input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D0009); var user = await _sysUserRep.GetByIdAsync(input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D0009);
var password = await _sysConfigService.GetConfigValue<string>(ConfigConst.SysPassword); var password = await _sysConfigService.GetConfigValue<string>(ConfigConst.SysPassword);
user.Password = CryptogramUtil.Encrypt(password); 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}"; var keyPasswordErrorTimes = $"{CacheConst.KeyPasswordErrorTimes}{user.Account}";

View File

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

View File

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

View File

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

View File

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

View File

@ -100,5 +100,6 @@ declare interface ThemeConfigState {
icpUrl: string; // Icp地址 icpUrl: string; // Icp地址
secondVer: boolean; // 是否开启二级验证 secondVer: boolean; // 是否开启二级验证
captcha: 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>