From 1ea005424a17890b0eb5143bf86cfd6a89cd12ed Mon Sep 17 00:00:00 2001 From: cwchen <1048842385@qq.com> Date: Thu, 4 Sep 2025 18:31:32 +0800 Subject: [PATCH] =?UTF-8?q?=E9=98=B2=E9=87=8D=E6=94=BE=E6=94=BB=E5=87=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + src/main.js | 22 ++++++++++++++++ src/settings.js | 2 +- src/store/modules/user.js | 9 ++++++- src/utils/auth.js | 26 +++++++++++++++++++ src/utils/crypto-js.js | 13 ++++++++++ src/utils/request.js | 53 ++++++++++++++++++++++++++++++++++++++- 7 files changed, 123 insertions(+), 3 deletions(-) create mode 100644 src/utils/crypto-js.js diff --git a/package.json b/package.json index 16cc4e7..4bb107f 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "axios": "0.28.1", "clipboard": "2.0.8", "core-js": "3.37.1", + "crypto-js": "^4.2.0", "echarts": "5.4.0", "element-ui": "2.15.14", "file-saver": "2.0.5", diff --git a/src/main.js b/src/main.js index 897192a..3efaab5 100644 --- a/src/main.js +++ b/src/main.js @@ -75,6 +75,28 @@ Vue.use(Element, { Vue.config.productionTip = false +// 防F12开发者工具 - Vue版本 +/* Vue.mixin({ + mounted() { + this.preventDevTools() + }, + methods: { + preventDevTools() { + const banDevTools = () => { + setInterval(() => { + debugger + }, 50) + } + + try { + banDevTools() + } catch (err) { + console.warn('防开发者工具功能初始化失败:', err) + } + } + } +}) */ + new Vue({ el: '#app', router, diff --git a/src/settings.js b/src/settings.js index bb37ecf..875538a 100644 --- a/src/settings.js +++ b/src/settings.js @@ -52,5 +52,5 @@ module.exports = { /** * 底部版权文本内容 */ - footerContent: 'Copyright © 2018-2025 RuoYi. All Rights Reserved.' + footerContent: '' } diff --git a/src/store/modules/user.js b/src/store/modules/user.js index 6a7b710..960ded3 100644 --- a/src/store/modules/user.js +++ b/src/store/modules/user.js @@ -1,9 +1,10 @@ import router from '@/router' import { MessageBox, } from 'element-ui' import { login, logout, getInfo } from '@/api/login' -import { getToken, setToken, removeToken } from '@/utils/auth' +import { getToken, setToken, removeToken,setUserId,removeUserId,setSecretKey,removeSecretKey } from '@/utils/auth' import { isHttp, isEmpty } from "@/utils/validate" import defAva from '@/assets/images/profile.jpg' +import { encryptWithSM4} from '@/utils/sm' const user = { state: { @@ -63,6 +64,8 @@ const user = { return new Promise((resolve, reject) => { getInfo().then(res => { const user = res.user + setUserId(encryptWithSM4(user.userId + '')) + setSecretKey(encryptWithSM4(user.secret)) let avatar = user.avatar || "" if (!isHttp(avatar)) { avatar = (isEmpty(avatar)) ? defAva : process.env.VUE_APP_BASE_API + avatar @@ -104,6 +107,8 @@ const user = { commit('SET_ROLES', []) commit('SET_PERMISSIONS', []) removeToken() + removeUserId() + removeSecretKey() resolve() }).catch(error => { reject(error) @@ -116,6 +121,8 @@ const user = { return new Promise(resolve => { commit('SET_TOKEN', '') removeToken() + removeUserId() + removeSecretKey() resolve() }) } diff --git a/src/utils/auth.js b/src/utils/auth.js index 08a43d6..eb0f09b 100644 --- a/src/utils/auth.js +++ b/src/utils/auth.js @@ -1,6 +1,8 @@ import Cookies from 'js-cookie' const TokenKey = 'Admin-Token' +const UserIdKey = 'UserId' +const SecretKey = 'Secret' export function getToken() { return Cookies.get(TokenKey) @@ -13,3 +15,27 @@ export function setToken(token) { export function removeToken() { return Cookies.remove(TokenKey) } + +export function getUserId() { + return Cookies.get(UserIdKey) +} + +export function setUserId(token) { + return Cookies.set(UserIdKey, token) +} + +export function removeUserId() { + return Cookies.remove(UserIdKey) +} + +export function getSecretKey() { + return Cookies.get(SecretKey) +} + +export function setSecretKey(token) { + return Cookies.set(SecretKey, token) +} + +export function removeSecretKey() { + return Cookies.remove(SecretKey) +} diff --git a/src/utils/crypto-js.js b/src/utils/crypto-js.js new file mode 100644 index 0000000..3612e80 --- /dev/null +++ b/src/utils/crypto-js.js @@ -0,0 +1,13 @@ +import CryptoJS from 'crypto-js' + +// HMAC-SHA256加密 +export function hmacSHA256(message, secret) { + return CryptoJS.HmacSHA256(message, secret).toString(CryptoJS.enc.Hex) +} + +// 生成请求签名 +export function generateRequestSignature(userId, timestamp, method, url, secret) { + const signString = userId + timestamp + method.toUpperCase() + url + console.log(signString); + return hmacSHA256(signString, secret) +} \ No newline at end of file diff --git a/src/utils/request.js b/src/utils/request.js index 931d3a3..5ca5d37 100644 --- a/src/utils/request.js +++ b/src/utils/request.js @@ -1,12 +1,13 @@ import axios from 'axios' import { Notification, MessageBox, Message, Loading } from 'element-ui' import store from '@/store' -import { getToken } from '@/utils/auth' +import { getToken,getUserId,getSecretKey } from '@/utils/auth' import errorCode from '@/utils/errorCode' import { tansParams, blobValidate } from '@/utils/bonus' import cache from '@/plugins/cache' import { saveAs } from 'file-saver' import { decryptWithSM4, encryptWithSM4, hashWithSM3AndSalt } from '@/utils/sm' +import {generateRequestSignature } from '@/utils/crypto-js' const systemConfig = { requestConfig: { @@ -36,6 +37,7 @@ service.interceptors.request.use( checkIntegrity = true, encryptResponse = true, repeatSubmit = false, + skipReplayProtection = false } = headers // 设置请求头 @@ -50,12 +52,61 @@ service.interceptors.request.use( config.headers['Authorization'] = 'Bearer ' + getToken() } + // 添加防重放签名头(如果不是跳过重放保护的请求) + if (!skipReplayProtection) { + try { + const userId = getUserId() + const userSecret = getSecretKey() + const method = config.method.toUpperCase() + const timestamp = Date.now().toString() + + let requestUrl = config.url + + if (config.params && typeof config.params === 'object') { + // 使用URLSearchParams自动处理嵌套对象 + const searchParams = new URLSearchParams() + + Object.entries(config.params).forEach(([key, value]) => { + if (value === undefined || value === null || value === '' || value === 'undefined') { + return + } + + if (typeof value === 'object' && value !== null) { + // 对于对象参数,后端期望的是 params[beginTime] 格式 + Object.entries(value).forEach(([nestedKey, nestedValue]) => { + if (nestedValue !== undefined && nestedValue !== null && nestedValue !== '') { + searchParams.append(`${key}[${nestedKey}]`, nestedValue.toString()) + } + }) + } else { + searchParams.append(key, value.toString()) + } + }) + + const paramsString = searchParams.toString() + if (paramsString) { + requestUrl += '?' + paramsString + } + } + + const signature = generateRequestSignature(userId, timestamp, method, requestUrl, userSecret) + + config.headers['timestamp'] = timestamp + config.headers['X-Signature'] = signature + + } catch (error) { + console.warn('生成防重放签名失败:', error) + } + } + // GET 请求处理 - 统一处理加密逻辑 if (config.method === 'get' && config.params) { // 如果需要加密 GET 请求 if (systemConfig.requestConfig.encryptRequest && encryptRequest) { // 将参数转换为查询字符串 let paramsString = tansParams(config.params) + console.error(paramsString); + // 移除末尾的 & 字符 if (paramsString.endsWith('&')) { paramsString = paramsString.slice(0, -1)