密码加密+增加密码重置页
This commit is contained in:
parent
21068b0b60
commit
1e9479a9d6
|
|
@ -105,9 +105,12 @@ export const constantRoutes = [
|
||||||
path: '/qrCode/qrCodePage',
|
path: '/qrCode/qrCodePage',
|
||||||
component: () => import('@/views/qrCode/qrCode'),
|
component: () => import('@/views/qrCode/qrCode'),
|
||||||
hidden: true
|
hidden: true
|
||||||
|
},
|
||||||
|
{
|
||||||
}
|
path: '/resetPassword',
|
||||||
|
component: () => import('@/views/resetPassword'),
|
||||||
|
hidden: true
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
// 动态路由,基于用户权限动态去加载
|
// 动态路由,基于用户权限动态去加载
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,7 @@ const user = {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
login(username, password, code, uuid).then(res => {
|
login(username, password, code, uuid).then(res => {
|
||||||
let data = res.data
|
let data = res.data
|
||||||
|
localStorage.setItem('isCode', data.code)
|
||||||
setToken(data.access_token)
|
setToken(data.access_token)
|
||||||
commit('SET_TOKEN', data.access_token)
|
commit('SET_TOKEN', data.access_token)
|
||||||
localStorage.setItem('token', data.access_token)
|
localStorage.setItem('token', data.access_token)
|
||||||
|
|
|
||||||
|
|
@ -81,3 +81,14 @@ export function isArray(arg) {
|
||||||
}
|
}
|
||||||
return Array.isArray(arg)
|
return Array.isArray(arg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} str
|
||||||
|
* @returns {Boolean}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 密码规则:8-20位,必须包含字母、数字、特殊字符中的两种
|
||||||
|
export function validPassword(str) {
|
||||||
|
const reg = /^((?=.*[A-Za-z])(?=.*\d)|(?=.*[A-Za-z])(?=.*[!@#$%^&*()_+\-\=])|(?=.*\d)(?=.*[!@#$%^&*()_+\-\=]))[A-Za-z\d!@#$%^&*()_+\-\=]{8,20}$/
|
||||||
|
return reg.test(str)
|
||||||
|
}
|
||||||
|
|
@ -302,8 +302,11 @@ export default {
|
||||||
Cookies.remove('rememberMe');
|
Cookies.remove('rememberMe');
|
||||||
}
|
}
|
||||||
this.$store.dispatch("Login", this.loginForm).then(() => {
|
this.$store.dispatch("Login", this.loginForm).then(() => {
|
||||||
this.$router.push({path:"/"}).catch(() => {
|
if (localStorage.getItem('isCode') == 1) {
|
||||||
});
|
this.$router.push({ path: '/resetPassword' }).catch(() => {})
|
||||||
|
} else {
|
||||||
|
this.$router.push({ path: '/' }).catch(() => {})
|
||||||
|
}
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
if (this.captchaEnabled) {
|
if (this.captchaEnabled) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,246 @@
|
||||||
|
<template>
|
||||||
|
<div class="login">
|
||||||
|
<div class="login-bar">
|
||||||
|
<div class="form-bar">
|
||||||
|
<h3 class="title">请重置登录密码</h3>
|
||||||
|
<el-form
|
||||||
|
ref="form"
|
||||||
|
:model="user"
|
||||||
|
:rules="rules"
|
||||||
|
>
|
||||||
|
<el-form-item label="旧密码" prop="oldPassword">
|
||||||
|
<el-input
|
||||||
|
v-model="user.oldPassword"
|
||||||
|
placeholder="请输入旧密码"
|
||||||
|
type="password"
|
||||||
|
show-password
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="新密码" prop="newPassword">
|
||||||
|
<el-input
|
||||||
|
v-model="user.newPassword"
|
||||||
|
placeholder="请输入新密码"
|
||||||
|
type="password"
|
||||||
|
show-password
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="确认密码" prop="confirmPassword">
|
||||||
|
<el-input
|
||||||
|
v-model="user.confirmPassword"
|
||||||
|
placeholder="请确认新密码"
|
||||||
|
type="password"
|
||||||
|
show-password
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" style="width: 48.5%;" @click="submit"
|
||||||
|
>确认修改</el-button
|
||||||
|
>
|
||||||
|
<el-button style="width: 48.5%;" @click="close">返回登录页</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { updateUserPwd } from '@/api/system/user'
|
||||||
|
import { validPassword } from '@/utils/validate'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
const passwordRegex = (rule, value, callback) => {
|
||||||
|
if (value.length < 8 || value.length > 20) {
|
||||||
|
callback(new Error('密码长度在 8 到 20 个字符'))
|
||||||
|
} else if (!validPassword(value)) {
|
||||||
|
callback(
|
||||||
|
new Error('密码须包含数字、字母、特殊符号中的两种以上'),
|
||||||
|
)
|
||||||
|
} else if (this.user.oldPassword === value) {
|
||||||
|
callback(new Error('新密码不能与旧密码相同'))
|
||||||
|
} else {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const equalToPassword = (rule, value, callback) => {
|
||||||
|
if (this.user.newPassword !== value) {
|
||||||
|
callback(new Error('两次输入的密码不一致'))
|
||||||
|
} else {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
user: {
|
||||||
|
oldPassword: undefined,
|
||||||
|
newPassword: undefined,
|
||||||
|
confirmPassword: undefined,
|
||||||
|
},
|
||||||
|
// 表单校验
|
||||||
|
rules: {
|
||||||
|
oldPassword: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '旧密码不能为空',
|
||||||
|
trigger: 'blur',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
newPassword: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '新密码不能为空',
|
||||||
|
trigger: 'blur',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
validator: passwordRegex,
|
||||||
|
trigger: 'blur',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
confirmPassword: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '确认密码不能为空',
|
||||||
|
trigger: 'blur',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
validator: equalToPassword,
|
||||||
|
trigger: 'blur',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
submit() {
|
||||||
|
this.$refs['form'].validate((valid) => {
|
||||||
|
if (valid) {
|
||||||
|
updateUserPwd(
|
||||||
|
this.user.oldPassword,
|
||||||
|
this.user.newPassword,
|
||||||
|
).then((response) => {
|
||||||
|
this.$modal.msgSuccess('修改成功')
|
||||||
|
// 进入首页
|
||||||
|
this.$store.dispatch('LogOut').then(() => {
|
||||||
|
// location.href = '/login';
|
||||||
|
// location.href = '/gl/login';
|
||||||
|
|
||||||
|
location.href =
|
||||||
|
process.env.NODE_ENV === 'production-nw'
|
||||||
|
? '/gl/login'
|
||||||
|
: '/login'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
this.$store.dispatch('LogOut').then(() => {
|
||||||
|
// location.href = '/login';
|
||||||
|
// location.href = '/gl/login';
|
||||||
|
|
||||||
|
location.href =
|
||||||
|
process.env.NODE_ENV === 'production-nw'
|
||||||
|
? '/gl/login'
|
||||||
|
: '/login'
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style rel="stylesheet/scss" lang="scss">
|
||||||
|
.login {
|
||||||
|
//display: flex;
|
||||||
|
//justify-content: center;
|
||||||
|
//align-items: center;
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
background-image: url('../assets/images/login.png');
|
||||||
|
background-size: 100% 100%;
|
||||||
|
//background: #1891FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin: 0px auto 30px auto;
|
||||||
|
//text-align: center;
|
||||||
|
color: #707070;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-bar {
|
||||||
|
position: absolute;
|
||||||
|
height: auto;
|
||||||
|
top: 20%;
|
||||||
|
left: 14%;
|
||||||
|
width: auto;
|
||||||
|
height: 500px;
|
||||||
|
border-radius: 6px;
|
||||||
|
display: flex;
|
||||||
|
background: #fff;
|
||||||
|
text-align: center;
|
||||||
|
padding-top: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-bar {
|
||||||
|
//height: 500px;
|
||||||
|
width: 360px;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form {
|
||||||
|
//border-radius: 6px;
|
||||||
|
//height: 100%;
|
||||||
|
background: #ffffff;
|
||||||
|
width: 400px;
|
||||||
|
padding: 25px 25px 5px 25px;
|
||||||
|
|
||||||
|
.el-input {
|
||||||
|
height: 45px;
|
||||||
|
|
||||||
|
input {
|
||||||
|
height: 38px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-icon {
|
||||||
|
height: 39px;
|
||||||
|
width: 14px;
|
||||||
|
margin-left: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-tip {
|
||||||
|
font-size: 13px;
|
||||||
|
text-align: center;
|
||||||
|
color: #bfbfbf;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-code {
|
||||||
|
width: 33%;
|
||||||
|
height: 38px;
|
||||||
|
float: right;
|
||||||
|
|
||||||
|
img {
|
||||||
|
cursor: pointer;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-login-footer {
|
||||||
|
height: 40px;
|
||||||
|
line-height: 40px;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
color: #fff;
|
||||||
|
font-family: Arial;
|
||||||
|
font-size: 12px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-code-img {
|
||||||
|
height: 38px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -240,6 +240,15 @@ export default {
|
||||||
getList() {
|
getList() {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
listConfig(this.addDateRange(this.queryParams, this.dateRange)).then(response => {
|
listConfig(this.addDateRange(this.queryParams, this.dateRange)).then(response => {
|
||||||
|
response.rows.forEach(row => {
|
||||||
|
if (row.configKey == 'sys.user.initPassword') {
|
||||||
|
this.decryptData(row.configValue, 'CCNXrpassWordKey').then(data => {
|
||||||
|
row.configValue = data;
|
||||||
|
}).catch(err => {
|
||||||
|
console.log('🚀 ~ decryptData ~ err:', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
this.configList = response.rows;
|
this.configList = response.rows;
|
||||||
this.total = response.total;
|
this.total = response.total;
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
|
|
@ -337,6 +346,26 @@ export default {
|
||||||
refreshCache().then(() => {
|
refreshCache().then(() => {
|
||||||
this.$modal.msgSuccess("刷新成功");
|
this.$modal.msgSuccess("刷新成功");
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
async decryptData(encryptedData, keyStr) {
|
||||||
|
const keyUint8 = new TextEncoder().encode(keyStr);
|
||||||
|
const encryptedBytes = Uint8Array.from(atob(encryptedData), c => c.charCodeAt(0));
|
||||||
|
|
||||||
|
const key = await crypto.subtle.importKey(
|
||||||
|
'raw',
|
||||||
|
keyUint8,
|
||||||
|
{ name: 'AES-CBC', length: 256 }, // 假设后端使用了CBC模式,需要调整为实际使用的模式
|
||||||
|
false,
|
||||||
|
['decrypt']
|
||||||
|
);
|
||||||
|
|
||||||
|
const decryptedData = await crypto.subtle.decrypt(
|
||||||
|
{ name: 'AES-CBC', iv: new Uint8Array(16) }, // 实际使用时需要正确的IV,这里仅为示例
|
||||||
|
key,
|
||||||
|
encryptedBytes
|
||||||
|
);
|
||||||
|
|
||||||
|
return new TextDecoder().decode(decryptedData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -345,6 +345,7 @@ import { listUser, getUser, delUser, addUser, updateUser, resetUserPwd, changeUs
|
||||||
import { getToken } from "@/utils/auth";
|
import { getToken } from "@/utils/auth";
|
||||||
import Treeselect from "@riophae/vue-treeselect";
|
import Treeselect from "@riophae/vue-treeselect";
|
||||||
import "@riophae/vue-treeselect/dist/vue-treeselect.css";
|
import "@riophae/vue-treeselect/dist/vue-treeselect.css";
|
||||||
|
import { validPassword } from '@/utils/validate'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "User",
|
name: "User",
|
||||||
|
|
@ -436,7 +437,22 @@ export default {
|
||||||
],
|
],
|
||||||
password: [
|
password: [
|
||||||
{ required: true, message: "用户密码不能为空", trigger: "blur" },
|
{ required: true, message: "用户密码不能为空", trigger: "blur" },
|
||||||
{ min: 5, max: 20, message: '用户密码长度必须介于 5 和 20 之间', trigger: 'blur' }
|
{ min: 8, max: 20, message: '用户密码长度必须介于 8 和 20 之间', trigger: 'blur' },
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
validator: (rule, value, callback) => {
|
||||||
|
if (!validPassword(value)) {
|
||||||
|
callback(
|
||||||
|
new Error(
|
||||||
|
'密码须包含数字、字母、特殊符号中的两种以上',
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
trigger: 'blur',
|
||||||
|
}
|
||||||
],
|
],
|
||||||
email: [
|
email: [
|
||||||
{
|
{
|
||||||
|
|
@ -452,7 +468,8 @@ export default {
|
||||||
trigger: "blur"
|
trigger: "blur"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
|
secretKey: 'CCNXrpassWordKey'
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
|
@ -465,7 +482,12 @@ export default {
|
||||||
this.getList();
|
this.getList();
|
||||||
this.getDeptTree();
|
this.getDeptTree();
|
||||||
this.getConfigKey("sys.user.initPassword").then(response => {
|
this.getConfigKey("sys.user.initPassword").then(response => {
|
||||||
this.initPassword = response.msg;
|
this.decryptData(response.msg, this.secretKey).then((data) => {
|
||||||
|
console.log('🚀 ~ this.getConfigKey ~ data:', data)
|
||||||
|
this.initPassword = data
|
||||||
|
}).catch((error) => {
|
||||||
|
console.log('🚀 ~ this.getConfigKey ~ error:', error)
|
||||||
|
})
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -672,6 +694,27 @@ export default {
|
||||||
// 提交上传文件
|
// 提交上传文件
|
||||||
submitFileForm() {
|
submitFileForm() {
|
||||||
this.$refs.upload.submit();
|
this.$refs.upload.submit();
|
||||||
|
},
|
||||||
|
// 解密
|
||||||
|
async decryptData(encryptedData, keyStr) {
|
||||||
|
const keyUint8 = new TextEncoder().encode(keyStr);
|
||||||
|
const encryptedBytes = Uint8Array.from(atob(encryptedData), c => c.charCodeAt(0));
|
||||||
|
|
||||||
|
const key = await crypto.subtle.importKey(
|
||||||
|
'raw',
|
||||||
|
keyUint8,
|
||||||
|
{ name: 'AES-CBC', length: 256 },
|
||||||
|
false,
|
||||||
|
['decrypt']
|
||||||
|
);
|
||||||
|
|
||||||
|
const decryptedData = await crypto.subtle.decrypt(
|
||||||
|
{ name: 'AES-CBC', iv: new Uint8Array(16) },
|
||||||
|
key,
|
||||||
|
encryptedBytes
|
||||||
|
);
|
||||||
|
|
||||||
|
return new TextDecoder().decode(decryptedData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -18,15 +18,17 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { updateUserPwd } from "@/api/system/user";
|
import { updateUserPwd } from "@/api/system/user";
|
||||||
|
import { validPassword } from '@/utils/validate'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
const passwordRegex = (rule, value, callback) => {
|
const passwordRegex = (rule, value, callback) => {
|
||||||
const reg = /^((?=.*[A-Za-z])(?=.*\d)|(?=.*[A-Za-z])(?=.*[!@#$%^&*()_+\-\=])|(?=.*\d)(?=.*[!@#$%^&*()_+\-\=]))[A-Za-z\d!@#$%^&*()_+\-\=]{8,20}$/
|
|
||||||
if (value.length < 8 || value.length > 20) {
|
if (value.length < 8 || value.length > 20) {
|
||||||
callback(new Error("密码长度在 8 到 20 个字符"));
|
callback(new Error("密码长度在 8 到 20 个字符"));
|
||||||
} else if (!reg.test(value)) {
|
} else if (!validPassword(value)) {
|
||||||
callback(new Error("密码须包含数字、字母、特殊符号中的两种以上"));
|
callback(new Error("密码须包含数字、字母、特殊符号中的两种以上"));
|
||||||
|
} else if (this.user.oldPassword === value) {
|
||||||
|
callback(new Error('新密码不能与旧密码相同'))
|
||||||
} else {
|
} else {
|
||||||
callback()
|
callback()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue