防重放攻击

This commit is contained in:
cwchen 2025-09-04 18:31:32 +08:00
parent 3320a56495
commit 1ea005424a
7 changed files with 123 additions and 3 deletions

View File

@ -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",

View File

@ -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,

View File

@ -52,5 +52,5 @@ module.exports = {
/**
* 底部版权文本内容
*/
footerContent: 'Copyright © 2018-2025 RuoYi. All Rights Reserved.'
footerContent: ''
}

View File

@ -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()
})
}

View File

@ -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)
}

13
src/utils/crypto-js.js Normal file
View File

@ -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)
}

View File

@ -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)