mirror of
https://gitee.com/beecue/fastbee.git
synced 2026-11-07 01:18:56 +08:00
fix(规则脚本): 规则引擎 Groovy 脚本注入导致远程代码执行漏洞修复
This commit is contained in:
101
vue/src/components/ForceChangePwd/index.vue
Normal file
101
vue/src/components/ForceChangePwd/index.vue
Normal file
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:title="$t('forceChangePwdTitle')"
|
||||
:visible="visible"
|
||||
width="460px"
|
||||
append-to-body
|
||||
:show-close="false"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
>
|
||||
<el-alert :title="$t('forceChangePwdTip')" type="warning" :closable="false" show-icon style="margin-bottom: 16px" />
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item :label="$t('oldPassword')" prop="oldPassword">
|
||||
<el-input v-model="form.oldPassword" :placeholder="$t('system.user.profile.oldPasswordPlaceholder')" type="password" show-password />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('newPassword')" prop="newPassword">
|
||||
<el-input v-model="form.newPassword" :placeholder="$t('system.user.profile.newPasswordPlaceholder')" type="password" show-password />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('confirmPassword')" prop="confirmPassword">
|
||||
<el-input v-model="form.confirmPassword" :placeholder="$t('system.user.profile.confirmPasswordPlaceholder')" type="password" show-password />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" :loading="loading" @click="submit">{{ $t('forceChangePwdConfirm') }}</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { updateUserPwd } from '@/api/system/user';
|
||||
|
||||
export default {
|
||||
name: 'ForceChangePwd',
|
||||
data() {
|
||||
const equalToPassword = (rule, value, callback) => {
|
||||
if (this.form.newPassword !== value) {
|
||||
callback(new Error(this.$t('system.user.profile.pwdNotMatch')));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
return {
|
||||
loading: false,
|
||||
form: {
|
||||
oldPassword: undefined,
|
||||
newPassword: undefined,
|
||||
confirmPassword: undefined,
|
||||
},
|
||||
rules: {
|
||||
oldPassword: [{ required: true, message: this.$t('system.user.profile.oldPasswordRequired'), trigger: 'blur' }],
|
||||
newPassword: [
|
||||
{ required: true, message: this.$t('system.user.profile.newPasswordRequired'), trigger: 'blur' },
|
||||
{ min: 6, max: 20, message: this.$t('system.user.profile.passwordLength'), trigger: 'blur' },
|
||||
{
|
||||
trigger: 'blur',
|
||||
validator: (rule, value, callback) => {
|
||||
var passwordreg = /(?![A-Z]*$)(?![a-z]*$)(?![0-9]*$)(?![^a-zA-Z0-9]*$)/;
|
||||
if (!passwordreg.test(value)) {
|
||||
callback(new Error(this.$t('system.user.profile.passwordStrength')));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
confirmPassword: [
|
||||
{ required: true, message: this.$t('system.user.profile.confirmPasswordRequired'), trigger: 'blur' },
|
||||
{ required: true, validator: equalToPassword, trigger: 'blur' },
|
||||
],
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
visible() {
|
||||
return this.$store.state.user.needChangePwd;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
submit() {
|
||||
this.$refs.form.validate((valid) => {
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
this.loading = true;
|
||||
updateUserPwd({ oldPassword: this.form.oldPassword, newPassword: this.form.newPassword })
|
||||
.then(() => {
|
||||
this.$modal.msgSuccess(this.$t('updateSuccessReLogin'));
|
||||
// Stay on this isolated page until logout has completed.
|
||||
return this.$store.dispatch('LogOut').then(() => {
|
||||
location.href = '/login';
|
||||
});
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -132,5 +132,8 @@
|
||||
"batchCancelAuth": "Batch Cancel Authorization",
|
||||
"cancelAuth": "Cancel Authorization",
|
||||
"clean": "Clear",
|
||||
"cleanSuccess": "Clear Successful"
|
||||
"cleanSuccess": "Clear Successful",
|
||||
"forceChangePwdTitle": "Change the default password",
|
||||
"forceChangePwdTip": "This account is still using the factory default password, which is public and trivially guessable. To keep the platform and its devices safe, set a new password now. You will need to sign in again afterwards.",
|
||||
"forceChangePwdConfirm": "Change password and sign in again"
|
||||
}
|
||||
|
||||
@@ -25,11 +25,24 @@ const i18n = new VueI18n({
|
||||
locale.use(Element, {
|
||||
i18n: (key, value) => i18n.t(key, value),
|
||||
});
|
||||
listLanguage({ pageNum: 1, pageSize: 1000 }).then((res) => {
|
||||
res.rows.forEach((row) => {
|
||||
if (langs[row.language]) langs[row.language].language = row.langName;
|
||||
});
|
||||
});
|
||||
let languageNamesLoaded = false;
|
||||
let languageNamesRequest;
|
||||
function loadLanguageNames() {
|
||||
if (languageNamesLoaded) return Promise.resolve();
|
||||
if (!languageNamesRequest) {
|
||||
languageNamesRequest = listLanguage({ pageNum: 1, pageSize: 1000 }).then((res) => {
|
||||
res.rows.forEach((row) => {
|
||||
if (langs[row.language]) langs[row.language].language = row.langName;
|
||||
});
|
||||
languageNamesLoaded = true;
|
||||
}).catch(() => {
|
||||
// Built-in translations remain available if optional display names cannot load.
|
||||
}).finally(() => {
|
||||
languageNamesRequest = undefined;
|
||||
});
|
||||
}
|
||||
return languageNamesRequest;
|
||||
}
|
||||
|
||||
function getJson(contexts) {
|
||||
let json = {};
|
||||
@@ -40,4 +53,4 @@ function getJson(contexts) {
|
||||
}
|
||||
|
||||
// 导出langs 给切换语言的时候用
|
||||
export { i18n, langs };
|
||||
export { i18n, langs, loadLanguageNames };
|
||||
|
||||
@@ -132,5 +132,8 @@
|
||||
"batchCancelAuth": "批量取消授权",
|
||||
"cancelAuth": "取消授权",
|
||||
"clean": "清空",
|
||||
"cleanSuccess": "清空成功"
|
||||
"cleanSuccess": "清空成功",
|
||||
"forceChangePwdTitle": "请修改初始密码",
|
||||
"forceChangePwdTip": "检测到当前账号仍在使用出厂默认口令,该口令已公开且极易被爆破。为保障平台与设备安全,请立即设置新密码,修改后需要重新登录。",
|
||||
"forceChangePwdConfirm": "确认修改并重新登录"
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import NProgress from 'nprogress'
|
||||
import 'nprogress/nprogress.css'
|
||||
import { getToken } from '@/utils/auth'
|
||||
import { isRelogin } from '@/utils/request'
|
||||
import { PASSWORD_CHANGE_ROUTE } from '@/utils/passwordChange'
|
||||
import { loadLanguageNames } from '@/lang'
|
||||
|
||||
NProgress.configure({ showSpinner: false })
|
||||
|
||||
@@ -24,10 +26,18 @@ router.beforeEach((to, from, next) => {
|
||||
// 判断当前用户是否已拉取完user_info信息
|
||||
store.dispatch('GetInfo').then(() => {
|
||||
isRelogin.show = false
|
||||
// Resolve the server-owned password state before mounting any business page.
|
||||
if (store.state.user.needChangePwd) {
|
||||
if (to.path === PASSWORD_CHANGE_ROUTE) next()
|
||||
else next({ path: PASSWORD_CHANGE_ROUTE, replace: true })
|
||||
return
|
||||
}
|
||||
store.dispatch('GenerateRoutes').then(accessRoutes => {
|
||||
// 根据roles权限生成可访问的路由表
|
||||
router.addRoutes(accessRoutes) // 动态添加可访问路由表
|
||||
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
|
||||
next(to.path === PASSWORD_CHANGE_ROUTE
|
||||
? { path: '/index', replace: true }
|
||||
: { ...to, replace: true }) // hack方法 确保addRoutes已完成
|
||||
})
|
||||
}).catch(err => {
|
||||
store.dispatch('LogOut').then(() => {
|
||||
@@ -35,6 +45,11 @@ router.beforeEach((to, from, next) => {
|
||||
next({ path: '/' })
|
||||
})
|
||||
})
|
||||
} else if (store.state.user.needChangePwd) {
|
||||
if (to.path === PASSWORD_CHANGE_ROUTE) next()
|
||||
else next({ path: PASSWORD_CHANGE_ROUTE, replace: true })
|
||||
} else if (to.path === PASSWORD_CHANGE_ROUTE) {
|
||||
next({ path: '/index', replace: true })
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
@@ -53,4 +68,6 @@ router.beforeEach((to, from, next) => {
|
||||
|
||||
router.afterEach(() => {
|
||||
NProgress.done()
|
||||
// Avoid the eager language-list request before GetInfo has resolved the login state.
|
||||
if (!getToken() || !store.state.user.needChangePwd) loadLanguageNames()
|
||||
})
|
||||
|
||||
@@ -53,6 +53,11 @@ export const constantRoutes = [
|
||||
component: () => import('@/views/login'),
|
||||
hidden: true,
|
||||
},
|
||||
{
|
||||
path: '/change-password',
|
||||
component: () => import('@/components/ForceChangePwd'),
|
||||
hidden: true,
|
||||
},
|
||||
{
|
||||
path: '/register',
|
||||
component: () => import('@/views/register'),
|
||||
|
||||
@@ -3,6 +3,8 @@ import { getToken, setToken, removeToken } from '@/utils/auth'
|
||||
import { isHttp, isEmpty } from "@/utils/validate"
|
||||
import defAva from '@/assets/images/profile.jpg'
|
||||
|
||||
const NEED_CHANGE_PWD_KEY = 'needChangePwd'
|
||||
|
||||
const user = {
|
||||
state: {
|
||||
token: getToken(),
|
||||
@@ -10,7 +12,9 @@ const user = {
|
||||
name: '',
|
||||
avatar: '',
|
||||
roles: [],
|
||||
permissions: []
|
||||
permissions: [],
|
||||
// 出厂默认口令未修改标记,登录接口下发,持久化以覆盖刷新场景
|
||||
needChangePwd: localStorage.getItem(NEED_CHANGE_PWD_KEY) === 'true'
|
||||
},
|
||||
|
||||
mutations: {
|
||||
@@ -31,6 +35,14 @@ const user = {
|
||||
},
|
||||
SET_PERMISSIONS: (state, permissions) => {
|
||||
state.permissions = permissions
|
||||
},
|
||||
SET_NEED_CHANGE_PWD: (state, needChangePwd) => {
|
||||
state.needChangePwd = needChangePwd
|
||||
if (needChangePwd) {
|
||||
localStorage.setItem(NEED_CHANGE_PWD_KEY, 'true')
|
||||
} else {
|
||||
localStorage.removeItem(NEED_CHANGE_PWD_KEY)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -45,6 +57,7 @@ const user = {
|
||||
login(username, password, code, uuid).then(res => {
|
||||
setToken(res.token)
|
||||
commit('SET_TOKEN', res.token)
|
||||
commit('SET_NEED_CHANGE_PWD', res.needChangePwd === true)
|
||||
resolve()
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
@@ -56,6 +69,7 @@ const user = {
|
||||
GetInfo({ commit, state }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
getInfo().then(res => {
|
||||
commit('SET_NEED_CHANGE_PWD', res.needChangePwd === true)
|
||||
const user = res.user
|
||||
let avatar = user.avatar || ""
|
||||
if (!isHttp(avatar)) {
|
||||
@@ -84,6 +98,7 @@ const user = {
|
||||
commit('SET_TOKEN', '')
|
||||
commit('SET_ROLES', [])
|
||||
commit('SET_PERMISSIONS', [])
|
||||
commit('SET_NEED_CHANGE_PWD', false)
|
||||
removeToken()
|
||||
resolve()
|
||||
}).catch(error => {
|
||||
@@ -96,6 +111,7 @@ const user = {
|
||||
FedLogOut({ commit }) {
|
||||
return new Promise(resolve => {
|
||||
commit('SET_TOKEN', '')
|
||||
commit('SET_NEED_CHANGE_PWD', false)
|
||||
removeToken()
|
||||
resolve()
|
||||
})
|
||||
|
||||
14
vue/src/utils/passwordChange.js
Normal file
14
vue/src/utils/passwordChange.js
Normal file
@@ -0,0 +1,14 @@
|
||||
// UX coordination only. The backend remains authoritative for access control.
|
||||
export const PASSWORD_CHANGE_ROUTE = '/change-password';
|
||||
|
||||
export function isPasswordChangeResponse(data, status) {
|
||||
return !!data && data.needChangePwd === true && (status === 403 || data.code === 403);
|
||||
}
|
||||
|
||||
export function allowedWhileChangingPassword(config) {
|
||||
const method = (config.method || 'get').toUpperCase();
|
||||
const path = (config.url || '').split(/[?#]/)[0];
|
||||
return (method === 'GET' && ['/getInfo', '/getRouters', '/system/user/profile'].includes(path))
|
||||
|| (method === 'PUT' && path === '/system/user/profile/updatePwd')
|
||||
|| (method === 'POST' && path === '/logout');
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
import axios from 'axios';
|
||||
import { Notification, MessageBox, Message, Loading } from 'element-ui';
|
||||
import store from '@/store';
|
||||
import router from '@/router';
|
||||
import { PASSWORD_CHANGE_ROUTE, allowedWhileChangingPassword, isPasswordChangeResponse } from '@/utils/passwordChange';
|
||||
import { getToken } from '@/utils/auth';
|
||||
import errorCode from '@/utils/errorCode';
|
||||
import { tansParams, blobValidate } from '@/utils/ruoyi';
|
||||
@@ -10,6 +12,20 @@ import { saveAs } from 'file-saver';
|
||||
let downloadLoadingInstance;
|
||||
// 是否显示重新登录
|
||||
export let isRelogin = { show: false };
|
||||
let passwordChangeNavigation;
|
||||
|
||||
function requirePasswordChange(message) {
|
||||
if (getToken()) {
|
||||
store.commit('SET_NEED_CHANGE_PWD', true);
|
||||
if (!passwordChangeNavigation && router.currentRoute.path !== PASSWORD_CHANGE_ROUTE) {
|
||||
passwordChangeNavigation = Promise.resolve(router.replace({ path: PASSWORD_CHANGE_ROUTE }))
|
||||
.catch(() => {}).finally(() => { passwordChangeNavigation = undefined; });
|
||||
}
|
||||
}
|
||||
const error = new axios.Cancel(message || '请先修改出厂默认密码');
|
||||
error.needChangePwd = true;
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8';
|
||||
// 创建axios实例
|
||||
@@ -26,6 +42,10 @@ service.interceptors.request.use(
|
||||
config.headers.language = store.state.settings.language;
|
||||
// 是否需要设置 token
|
||||
const isToken = (config.headers || {}).isToken === false;
|
||||
if (getToken() && !isToken && store.state.user.needChangePwd && !allowedWhileChangingPassword(config)) {
|
||||
// Stop background work locally; never disguise a rejected request as success.
|
||||
return requirePasswordChange();
|
||||
}
|
||||
// 是否需要防止数据重复提交
|
||||
const isRepeatSubmit = (config.headers || {}).repeatSubmit === false;
|
||||
if (getToken() && !isToken) {
|
||||
@@ -65,13 +85,14 @@ service.interceptors.request.use(
|
||||
},
|
||||
(error) => {
|
||||
console.log(error);
|
||||
Promise.reject(error);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// 响应拦截器
|
||||
service.interceptors.response.use(
|
||||
(res) => {
|
||||
if (isPasswordChangeResponse(res.data, res.status)) return requirePasswordChange(res.data.msg);
|
||||
// 未设置状态码则默认成功状态
|
||||
const code = res.data.code || 200;
|
||||
// 获取错误信息,优先使用服务端消息
|
||||
@@ -111,6 +132,11 @@ service.interceptors.response.use(
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
if (axios.isCancel(error)) return Promise.reject(error);
|
||||
const response = error.response;
|
||||
if (response && isPasswordChangeResponse(response.data, response.status)) {
|
||||
return requirePasswordChange(response.data.msg);
|
||||
}
|
||||
console.log('err' + error);
|
||||
let { message } = error;
|
||||
if (message == 'Network Error') {
|
||||
@@ -153,6 +179,10 @@ export function download(url, params, filename, config) {
|
||||
downloadLoadingInstance.close();
|
||||
})
|
||||
.catch((r) => {
|
||||
if (axios.isCancel(r)) {
|
||||
downloadLoadingInstance.close();
|
||||
return;
|
||||
}
|
||||
console.error(r);
|
||||
Message.error('下载文件出现错误,请联系管理员!');
|
||||
downloadLoadingInstance.close();
|
||||
|
||||
Reference in New Issue
Block a user