diff --git a/src/App.vue b/src/App.vue index 3cc6374..dc2e417 100644 --- a/src/App.vue +++ b/src/App.vue @@ -8,10 +8,14 @@ + diff --git a/src/api/config.js b/src/api/config.js new file mode 100644 index 0000000..6c834fb --- /dev/null +++ b/src/api/config.js @@ -0,0 +1,9 @@ +import request from '@/utils/request' + +// 获取系统配置 +export const getConfig = () => { + return request({ + url: '/auth/getConfig', + method: 'get' + }) +} diff --git a/src/assets/styles/index.scss b/src/assets/styles/index.scss index 1688580..2ecefbd 100644 --- a/src/assets/styles/index.scss +++ b/src/assets/styles/index.scss @@ -232,3 +232,17 @@ aside { margin-bottom: 10px; } } + +.el-menu { + background-color: #081036; +} + +.el-menu--horizontal .el-menu .el-menu-item, .el-menu--horizontal .el-menu .el-submenu__title { + background-color: #081036; + color: #fff; +} + +.el-menu--horizontal > .el-submenu .el-submenu__title:hover { + background-color: rgba(0, 0, 0, 0.1); + color: #fff; +} diff --git a/src/utils/aescbc.js b/src/utils/aescbc.js index 7cb3fdc..74169a5 100644 --- a/src/utils/aescbc.js +++ b/src/utils/aescbc.js @@ -1,6 +1,7 @@ import * as CryptoJS from 'crypto-js' -const cbc_key = CryptoJS.enc.Utf8.parse('zhgd@bonus@zhgd@bonus@1234567890') -const cbc_iv = CryptoJS.enc.Utf8.parse('1234567812345678') +import { AES_CONFIG } from './configure' +const cbc_key = CryptoJS.enc.Utf8.parse(AES_CONFIG.AES_KEY) +const cbc_iv = CryptoJS.enc.Utf8.parse(AES_CONFIG.AES_IV) /** * 加解密开关 * 默认参数需要加密 @@ -18,9 +19,6 @@ const encryptEnabled= false; * @returns {string} */ export const encryptCBC = function(word) { - if(!encryptEnabled){ - return word; - } const srcs = CryptoJS.enc.Utf8.parse(word) const encrypted = CryptoJS.AES.encrypt(srcs, cbc_key, { iv: cbc_iv, diff --git a/src/utils/config.js b/src/utils/config.js new file mode 100644 index 0000000..27d3fe8 --- /dev/null +++ b/src/utils/config.js @@ -0,0 +1,10 @@ +import { getConfig } from '@/api/config'; +export function get() { + getConfig() + .then(response => { + localStorage.setItem('systemConfig', JSON.stringify(response.data)); + }) + .catch(error => { + console.error('Failed to fetch config:', error); + }); +} diff --git a/src/utils/configure.js b/src/utils/configure.js index 0cff266..4566f0c 100644 --- a/src/utils/configure.js +++ b/src/utils/configure.js @@ -1,17 +1,3 @@ -// 密码强度级别常量 -const STRENGTH_LEVELS = { - WEAK: 'weak', // 弱:一类字符 - MEDIUM: 'medium', // 中:两类字符 - STRONG: 'strong', // 强:三类字符 - VERY_STRONG: 'very-strong' // 非常强:四类字符 -} - -// 数据设置常量 -const DATA_SETTINGS = { - OPEN: true, // 开启 - CLOSE: false // 关闭 -} - // SM 配置 const SM_CONFIG = { SALT: '2cc0c5f9f1749f1632efa9f63e902323', // SM3 盐值(16 字节) @@ -19,67 +5,12 @@ const SM_CONFIG = { SM2_PUBLIC_KEY: 'your-public-key', // SM2 公钥 SM2_PRIVATE_KEY: 'your-private-key' // SM2 私钥 } - // AES 配置 const AES_CONFIG = { AES_KEY: 'zhgd@bonus@zhgd@bonus@1234567890', // AES key值 AES_IV: '1234567812345678' // AES 偏移量 } - -// 登录配置 -const LOGIN_CONFIG = { - CODE_PHONE_LOGIN: DATA_SETTINGS.OPEN, // 手机号验证码登录(true:开启,false:关闭) - CODE_EMAIL_LOGIN: DATA_SETTINGS.OPEN,// 邮箱验证码登录(true:开启,false:关闭) - PHONE_LOGIN: DATA_SETTINGS.OPEN, // 手机号密码登录(true:开启,false:关闭) - EMAIL_LOGIN: DATA_SETTINGS.OPEN // 邮箱密码登录(true:开启,false:关闭) -} -//注册配置 -const REGISTER_CONFIG = { - PHONE_REGISTER: DATA_SETTINGS.OPEN, // 手机号注册(true:开启,false:关闭) - EMAIL_REGISTER: DATA_SETTINGS.OPEN // 邮箱注册(true:开启,false:关闭) -} - -// 配置设置 -const CONFIG = { - STRENGTH: STRENGTH_LEVELS.STRONG,//密码强度配置 - IS_OPEN_REGISTER: REGISTER_CONFIG.PHONE_REGISTER || REGISTER_CONFIG.EMAIL_REGISTER, // 是否开启注册 - IS_CODE_LOGIN: LOGIN_CONFIG.CODE_EMAIL_LOGIN || LOGIN_CONFIG.CODE_PHONE_LOGIN, // 是否开启短信登录 - // 数据设置 - dataSettings: { - integrityCheck: DATA_SETTINGS.CLOSE, // 数据完整性校验(true:开启,false:关闭) - encryptRequest: DATA_SETTINGS.CLOSE, // 数据传输加密(true:开启,false:关闭) - encryptResponse: DATA_SETTINGS.CLOSE, // 数据返回解密(true:开启,false:关闭) - }, - // 增加配置以支持增加根节点公司的添加和删除功能,added by weiweiwang,2024/9/12 - IS_ADD_ROOT_COMPANY: DATA_SETTINGS.OPEN, -} - -// 获取占位符文本的函数 -// 获取占位符文本的函数 -function getPlaceholderText() { - const loginOptions = [] - if (LOGIN_CONFIG.PHONE_LOGIN) loginOptions.push('手机号') - if (LOGIN_CONFIG.EMAIL_LOGIN) loginOptions.push('邮箱') - return `用户名${loginOptions.length ? '/' + loginOptions.join('/') : ''}` -} - -// 获取占位符文本的函数 -// 获取占位符文本的函数 -function getCodePlaceholderText() { - const loginOptions = [] - if (LOGIN_CONFIG.CODE_PHONE_LOGIN) loginOptions.push('手机号') - if (LOGIN_CONFIG.CODE_EMAIL_LOGIN) loginOptions.push('邮箱') - return loginOptions.length ? loginOptions.join('/') : '' -} - module.exports = { - STRENGTH_LEVELS, - DATA_SETTINGS, - CONFIG, SM_CONFIG, AES_CONFIG, - LOGIN_CONFIG, - REGISTER_CONFIG, - getPlaceholderText, - getCodePlaceholderText } diff --git a/src/utils/request.js b/src/utils/request.js index db7a70f..9627fd2 100644 --- a/src/utils/request.js +++ b/src/utils/request.js @@ -7,10 +7,10 @@ import { tansParams, blobValidate } from '@/utils/bonus' import cache from '@/plugins/cache' import { saveAs } from 'file-saver' import { encryptCBC, decryptCBC } from '@/utils/aescbc' -import { CONFIG } from '@/utils/configure' import { hashWithSM3AndSalt } from '@/utils/sm' - -//let token = localStorage.getItem("tokens"); +const systemConfig = JSON.parse(localStorage.getItem('systemConfig')) || { + requestConfig: { encryptRequest: false, checkIntegrity: false, encryptResponse: false } +}; let downloadLoadingInstance // 是否显示重新登录 @@ -40,11 +40,11 @@ service.interceptors.request.use(config => { // 设置请求头 //入参加密 - config.headers['encryptRequest'] = CONFIG.dataSettings.encryptRequest && encryptRequest ? 'true' : 'false' + config.headers['encryptRequest'] = systemConfig.requestConfig.encryptRequest && encryptRequest ? 'true' : 'false' // 数据完整性校验 - config.headers['checkIntegrity'] = CONFIG.dataSettings.integrityCheck && checkIntegrity ? 'true' : 'false' + config.headers['checkIntegrity'] = systemConfig.requestConfig.checkIntegrity && checkIntegrity ? 'true' : 'false' //回参是否加密 - config.headers['encryptResponse'] = CONFIG.dataSettings.encryptResponse && encryptResponse ? 'true' : 'false' + config.headers['encryptResponse'] = systemConfig.requestConfig.encryptResponse && encryptResponse ? 'true' : 'false' const isRepeatSubmit = repeatSubmit // 处理 Token @@ -52,21 +52,6 @@ service.interceptors.request.use(config => { config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义 token } - // // 处理 GET 请求 - // if (config.method === 'get' && config.params) { - // let params = tansParams(config.params).slice(0, -1) - // // 数据完整性校验 - // if (CONFIG.dataSettings.integrityCheck && checkIntegrity) { - // config.headers['Params-Hash'] = hashWithSM3AndSalt(params) - // } - // // 加密参数 - // if (CONFIG.dataSettings.encryptRequest && encryptRequest) { - // params = encryptCBC(params) - // } - // config.url = `${config.url}?${params}` - // config.params = {} - // } - // get请求映射params参数 if (config.method === 'get' && config.params) { let url = config.url + '?' + tansParams(config.params); @@ -80,13 +65,15 @@ service.interceptors.request.use(config => { let contentType = config.headers['Content-Type'] if (contentType.includes('application/json') && typeof data !== 'undefined') { // 数据完整性校验 - if (CONFIG.dataSettings.integrityCheck && checkIntegrity) { + if (systemConfig.requestConfig.checkIntegrity && checkIntegrity) { config.headers['Params-Hash'] = hashWithSM3AndSalt(data) + console.log(hashWithSM3AndSalt(data)) config.data = data } // 加密数据 - if (CONFIG.dataSettings.encryptRequest && encryptRequest) { + if (systemConfig.requestConfig.encryptRequest && encryptRequest) { config.data = encryptCBC(data) + console.log(encryptCBC(data)) } } // 检查请求数据大小 @@ -117,7 +104,6 @@ service.interceptors.request.use(config => { service.interceptors.response.use(res => { if (res.headers.encryptresponse && !res.data.hasOwnProperty('code')) { res.data = JSON.parse(decryptCBC(res.data)) - console.log(res.data) } // 未设置状态码则默认成功状态 const code = res.data.code || 200 @@ -137,7 +123,7 @@ service.interceptors.response.use(res => { }).then(() => { isRelogin.show = false store.dispatch('LogOut').then(() => { - location.href = '/login' + location.href = '/index' }) }).catch(() => { isRelogin.show = false @@ -158,7 +144,6 @@ service.interceptors.response.use(res => { } }, error => { - console.log('err' + error) let { message } = error if (message == 'Network Error') { message = '后端接口连接异常' diff --git a/src/utils/sm.js b/src/utils/sm.js index 2a7e261..a3696bf 100644 --- a/src/utils/sm.js +++ b/src/utils/sm.js @@ -1,51 +1,43 @@ // src/utils/encryption.js -import sm2 from 'sm-crypto/src/sm2' -import sm3 from 'sm-crypto/src/sm3' -import sm4 from 'sm-crypto/src/sm4' +import { sm2, sm3, sm4 } from 'sm-crypto' +// 配置项,例如盐值、SM2 公私钥、SM4 密钥 +import { SM_CONFIG } from './configure' -// 示例密钥对,实际使用中需要从安全来源获取 -const privateKey = 'your-private-key' -const publicKey = 'your-public-key' -// 生成随机盐值(16 字节) -const salt = '2cc0c5f9f1749f1632efa9f63e902323' -const sm4Key = 'your-sm4-key' // SM4 对称密钥,需要为 128 比特 (16 字节) - -// SM2 加密 -export function encryptSM2(data) { - // 使用公钥对数据进行加密 - return sm2.doEncrypt(data, publicKey, 1) // 1 表示 C1C3C2 加密模式 -} - -// SM2 解密 -export function decryptSM2(data) { - // 使用私钥对数据进行解密 - return sm2.doDecrypt(data, privateKey, 1) // 1 表示 C1C3C2 加密模式 -} // SM3 哈希 -export function hashSM3(data) { +export function hashSM3(text) { // 对数据进行哈希计算 - return sm3(data) + return sm3(text) } // 使用 SM3 进行哈希并加入盐值 export function hashWithSM3AndSalt(text) { // 将文本和盐值拼接在一起 - const textWithSalt = salt + text + const textWithSalt = SM_CONFIG.SALT + text // 使用 SM3 进行哈希 return hashSM3(textWithSalt) } +// SM2 加密 +export function encryptWithSM2(text) { + // SM2 公钥加密 + return sm2.doEncrypt(text, SM_CONFIG.SM2_PUBLIC_KEY) +} + +// SM2 解密 +export function decryptWithSM2(encryptedText) { + // SM2 私钥解密 + return sm2.doDecrypt(encryptedText, SM_CONFIG.SM2_PRIVATE_KEY) +} + // SM4 加密 -export function encryptSM4(data) { - // 使用对称密钥对数据进行加密 - const sm4Instance = new sm4() - return sm4Instance.encrypt(data, sm4Key) +export function encryptWithSM4(text) { + // SM4 对称加密,ECB 模式 + return sm4.encrypt(text, SM_CONFIG.SM4_KEY) } // SM4 解密 -export function decryptSM4(data) { - // 使用对称密钥对数据进行解密 - const sm4Instance = new sm4() - return sm4Instance.decrypt(data, sm4Key) +export function decryptWithSM4(encryptedText) { + // SM4 对称解密,ECB 模式 + return sm4.decrypt(encryptedText, SM_CONFIG.SM4_KEY) } diff --git a/src/utils/validate.js b/src/utils/validate.js index 57a568e..1dca102 100644 --- a/src/utils/validate.js +++ b/src/utils/validate.js @@ -1,3 +1,4 @@ +const systemConfig = JSON.parse(localStorage.getItem('systemConfig')); /** * @param {string} path * @returns {Boolean} @@ -65,7 +66,7 @@ export function validEmail(email) { * @returns {Boolean} */ export function isString(str) { - return typeof str === 'string' || str instanceof String; + return typeof str === 'string' || str instanceof String } /** @@ -78,3 +79,139 @@ export function isArray(arg) { } return Array.isArray(arg) } + +/** + * 密码的正则表达式 最少8个字符,最多20个字符,至少一个字母,一个数字和一个特殊字符: + * @param {string} password + * @returns {Boolean} + */ +export function validPwd(value) { + const reg = /^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,20}$/ + return reg.test(value) +} + +export function validateNewPassword(rule, value, callback) { + // 使用配置文件中的策略进行验证 + + // 1. 检查密码长度 + if (value.length < systemConfig.passwordConfig.minLength || value.length > systemConfig.passwordConfig.maxLength) { + callback(new Error('密码长度应为' + systemConfig.passwordConfig.minLength + '至' + systemConfig.passwordConfig.maxLength + '位!')) + return + } + + // 2. 检查密码复杂度 + const hasUpperCase = /[A-Z]/.test(value) + const hasLowerCase = /[a-z]/.test(value) + const hasDigit = /\d/.test(value) + const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(value) + + if (systemConfig.passwordConfig.requireUpperCase && !hasUpperCase) { + callback(new Error('密码必须包含大写字母!')) + return + } + if (systemConfig.passwordConfig.requireLowerCase && !hasLowerCase) { + callback(new Error('密码必须包含小写字母!')) + return + } + if (systemConfig.passwordConfig.requireDigit && !hasDigit) { + callback(new Error('密码必须包含数字!')) + return + } + if (systemConfig.passwordConfig.requireSpecialChar && !hasSpecialChar) { + callback(new Error('密码必须包含特殊字符!')) + return + } + // 3. 检查是否包含弱密码 + for (const weakPwd of systemConfig.passwordConfig.weakPasswords) { + // 将密码和弱密码都转换为小写进行比较 + if (value.toLowerCase().includes(weakPwd.toLowerCase())) { + callback(new Error(`密码包含常见的弱密码片段: ${weakPwd}`)) + return + } + } + // 4. 检查是否包含超过规定数量的连续字符 + if (systemConfig.passwordConfig.restrictConsecutiveChars && containsConsecutiveCharacters(value, systemConfig.passwordConfig.maxConsecutiveChars)) { + callback(new Error(`密码不能包含超过${systemConfig.passwordConfig.maxConsecutiveChars}位连续字符!`)) + return + } + callback() // 验证成功 +} + +/** + * 检查密码中是否包含超过 n 个连续相同字符、连续递增/递减的数字或字母(不区分大小写) + */ +function containsConsecutiveCharacters(password, n) { + // 检查连续相同字符 + n = n + 1; // 允许最多 n 个连续字符 + for (let i = 0; i <= password.length - n; i++) { + let consecutiveSameChar = true; + for (let j = 1; j < n; j++) { + if (password[i + j] !== password[i]) { + consecutiveSameChar = false; + break; + } + } + if (consecutiveSameChar) { + return true; // 包含超过 n 个连续相同字符 + } + } + + // 检查连续递增或递减的数字 + for (let i = 0; i <= password.length - n; i++) { + let consecutiveIncreasing = true; + let consecutiveDecreasing = true; + for (let j = 1; j < n; j++) { + const currentChar = password[i]; + const nextChar = password[i + j]; + + // 检查数字递增或递减 + if (/\d/.test(currentChar) && /\d/.test(nextChar)) { + if (nextChar.charCodeAt(0) !== currentChar.charCodeAt(0) + j) { + consecutiveIncreasing = false; + } + if (nextChar.charCodeAt(0) !== currentChar.charCodeAt(0) - j) { + consecutiveDecreasing = false; + } + } else { + consecutiveIncreasing = false; + consecutiveDecreasing = false; + break; + } + } + if (consecutiveIncreasing || consecutiveDecreasing) { + return true; // 包含超过 n 个递增或递减的连续数字 + } + } + + // 检查连续递增或递减的字母(不区分大小写) + for (let i = 0; i <= password.length - n; i++) { + let consecutiveIncreasing = true; + let consecutiveDecreasing = true; + for (let j = 1; j < n; j++) { + const currentChar = password[i].toLowerCase(); // 转为小写 + const nextChar = password[i + j].toLowerCase(); // 转为小写 + + // 检查字母递增或递减 + if (/[a-zA-Z]/.test(currentChar) && /[a-zA-Z]/.test(nextChar)) { + if (nextChar.charCodeAt(0) !== currentChar.charCodeAt(0) + j) { + consecutiveIncreasing = false; + } + if (nextChar.charCodeAt(0) !== currentChar.charCodeAt(0) - j) { + consecutiveDecreasing = false; + } + } else { + consecutiveIncreasing = false; + consecutiveDecreasing = false; + break; + } + } + if (consecutiveIncreasing || consecutiveDecreasing) { + return true; // 包含超过 n 个递增或递减的连续字母 + } + } + + // 不包含连续相同字符、数字或字母序列 + return false; +} + + diff --git a/src/views/dataSet/dataSetFile/details.vue b/src/views/dataSet/dataSetFile/details.vue index 7d4433a..f210f0f 100644 --- a/src/views/dataSet/dataSetFile/details.vue +++ b/src/views/dataSet/dataSetFile/details.vue @@ -34,7 +34,7 @@
+ style="width: 100%;height: 100%;position: absolute;left: 0; border-radius: 5% 5% 0 0;object-fit: contain;background-color: #000" alt=""/>
@@ -62,7 +62,7 @@