项目安全问题——前端两步完成加密
前言
我之前有写过:项目安全问题-SM4加解密_react前端sm4加密参数-CSDN博客
这个加密当然需要后端配合
此次加密是为了应对银行的安全测试,保证项目不被从接口请求进行攻击
值得借鉴
加密细节描述
一、登录接口请求body进行sm4加密后传输
加密key:65d3ea57d0756fcc4e553e04d96006e3
原body内容:{"loginName":"rrms","passWord":"xmbankUser@123"}
加密后body内容:9Tz/F/02LdIq1y4xNXD/Sn9u/FsdgGwS5iXDrYWnVNCC7jMe4q+zAriXFlMmhKtR1wYEjM/8q28M6HbjY0RKIQ==
请求示例:
二、所有接口调用前,统一增加签名。涉及到的参数有
1、header新增参数:nonce(UUID随机数)、timestamp(13位时间戳)、signature(生成的签名)
2、生成签名signature的规则:将header中的参数以及请求接口最后的路径拼接成下面格式后(nonce=xxx&resource=xxx×tamp=xxx&token=xxx)
签名signature字符串类型,根据请求类型传不同的参数
GET请求:http://101.200.125.197:9000/cbrc/users/userList?pageNo=1&pageSize=20&name=&orgId=&depId=&posId=&roleId=
加密参数(有值的请求参数):nonce、timestamp、resource、token、pageNo、pageSize
最终拼接的加密串:nonce=f40ab8e1113d4074a6fa0bbf7f9616d3&pageNo=1&pageSize=20&resource=/userList×tamp=1755505087425&token=eyJhbGc...
POST请求(非登陆请求和上传文件请求):http://101.200.125.197:9000/cbrc/users/updateUser
加密参数(有值的请求参数):nonce、timestamp、resource、token、body
最终拼接的加密串:body={"id":1226602,"name":"cybfgld","workNo":"cybfgld","orgId":1216,"depId":12574,"positionId":9,"email":"liujinxin@wiseweb.com.cn","roleId":3,"loginName":"cybfgld","userStatus":0,"telephone":"","vacationDatesArray":[],"vacationDates":"","transUser":null,"approvalModuleArray":["1","2","3"],"approvalModule":"1,2,3","sex":"0","positionName":"/","createTime":"2025-06-10 16:48:23","createUserId":1226593,"updateByUser":0,"userFrom":0,"organizationVo":{"id":1216,"name":"總行","parentId":1215,"parentName":null,"type":2,"orgType":1,"createTime":"2025-04-27 15:20:58","updateTime":"2025-04-27 15:20:58"},"departmentVo":{"id":12574,"orgId":1216,"orgName":"总行","name":"AO(总行办公室)","type":1,"updateTime":"2025-05-21 15:47:07","createTime":"2025-05-21 15:47:03","userId":null,"userName":null,"userNumber":null},"roleVo":{"id":3,"name":"声誉组","isReputation":"1"}}&nonce=f40ab8e1113d4074a6fa0bbf7f9616d3&resource=/updateUser×tamp=1755505340216&token=eyJhbGciOiJIUzU...
3、使用HMAC-SHA256生成签名(签名的key随意定:jianzhenbank@2025)
示例:
我调用接口地址: http://101.200.125.197:9000/cbrc/workOrg/reputation/queryPage?orgType=1&pageNo=1&pageSize=20&orgId=1116&stage=&keyType=1&flag=
其中resource取最后一个/到?之前部分(即resource=/queryPage)
nonce=b307b7c84ce84f078748619ad0c7a57c
timestamp=1752822334952
token=eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJpZCUzRDElMjZuYW1lJTNEJUU3JUFFJUExJUU3JTkwJTg2JUU1JTkxJTk4JTI2cmlkJTNEMSUyNnJuYW1lJTNEJUU4JUI2JTg1JUU3JUJBJUE3JUU3JUFFJUExJUU3JTkwJTg2JUU1JTkxJTk4JTI2b2lkJTNEMTExNiUyNm9uYW1lJTNEJUU2JTgwJUJCJUU4JUExJThDJTI2b3JnVHlwZSUzRDElMjZvcmdQYXJlbnRJZCUzRDExMTUlMjZkZXBJZCUzRDEyMzQzJTI2ZGVwTmFtZSUzRCVFOCVBMSU4QyVFOSVBMiU4NiVFNSVBRiVCQyUyNmRlcFR5cGUlM0QyJTI2cG9zSWQlM0QxJTI2cG9zTmFtZSUzRCVFNyVCQiU4RiVFNSU4QSU5RSUyNndvcmtObyUzRG51bGwlMjZlbWFpbCUzRGxpdWppbnhpbiU0MHdpc2V3ZWIuY29tLmNuJTI2b3JnRGF0YVNvdXJjZSUzRFhJQU1FTiIsImlhdCI6MTc1MjgyMjEwNSwiZXhwIjoxNzUyODI5MzA1fQ.VrZNOlbf4c5aRCa45S386gTOinYwmyttqNxzYlU67a95FfXMnhYkO054FJ5Sbjf0R9H7i13SnECBvt4Wa5Rz3g
最终生成的签名signature=6c48a43d13680a9a0b049d9b65541879553f943b5be981c96574a8113bb198c4
开始加密
第一步安装依赖
安装npm i sm-crypto
随机数,安装 npm i uuid
安装npm install --save crypto-js
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Vue2 UUID生成方法</title> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script> <script src="https://cdn.jsdelivr.net/npm/uuid@8.3.2/dist/uuidv4.min.js"></script> <style> * { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; line-height: 1.6; color: #333; background-color: #f5f7fa; padding: 20px; } .container { max-width: 1000px; margin: 0 auto; padding: 20px; } header { text-align: center; margin-bottom: 30px; padding: 20px; background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%); color: white; border-radius: 10px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); } h1 { margin-bottom: 10px; } .description { max-width: 800px; margin: 0 auto 30px; padding: 20px; background-color: white; border-radius: 10px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05); } .methods { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; margin-bottom: 30px; } .method { background-color: white; padding: 20px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05); transition: transform 0.3s ease; } .method:hover { transform: translateY(-5px); box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1); } .method h3 { color: #2575fc; margin-bottom: 15px; padding-bottom: 10px; border-bottom: 1px solid #eee; } .uuid-display { margin: 15px 0; padding: 12px; background-color: #f8f9fa; border-radius: 5px; font-family: monospace; word-break: break-all; border: 1px solid #e9ecef; } .btn { display: inline-block; padding: 10px 20px; background-color: #2575fc; color: white; border: none; border-radius: 5px; cursor: pointer; font-size: 16px; transition: background-color 0.3s; } .btn:hover { background-color: #1c64e0; } .btn-copy { background-color: #4caf50; margin-left: 10px; } .btn-copy:hover { background-color: #43a047; } .notes { margin-top: 15px; padding: 10px; background-color: #fff9e6; border-left: 4px solid #ffc107; font-size: 14px; } .footer { text-align: center; margin-top: 40px; padding: 20px; color: #6c757d; } @media (max-width: 768px) { .methods { grid-template-columns: 1fr; } } </style> </head> <body> <div id="app" class="container"> <header> <h1>Vue2 UUID生成方法</h1> <p>多种方式生成全局唯一标识符</p> </header> <div class="description"> <p>UUID(通用唯一识别码)是用于信息在系统中唯一标识的128位数值。在Vue2项目中,有多种方法可以生成UUID,各有优缺点,适用于不同场景。</p> </div> <div class="methods"> <div class="method"> <h3>1. 使用uuid库</h3> <p>最常用的方法,通过npm安装uuid库</p> <div class="uuid-display">{{ uuidV4 }}</div> <button class="btn" @click="generateV4">生成UUID</button> <button class="btn btn-copy" @click="copyToClipboard(uuidV4)">复制</button> <div class="notes"> <strong>注意:</strong>需要先安装uuid库:<code>npm install uuid</code> </div> </div> <div class="method"> <h3>2. 使用Crypto API</h3> <p>使用浏览器内置的Crypto API生成UUID</p> <div class="uuid-display">{{ cryptoUUID }}</div> <button class="btn" @click="generateCrypto">生成UUID</button> <button class="btn btn-copy" @click="copyToClipboard(cryptoUUID)">复制</button> <div class="notes"> <strong>注意:</strong>需要现代浏览器支持,不支持IE浏览器 </div> </div> <div class="method"> <h3>3. 自定义算法</h3> <p>使用自定义算法生成符合UUID格式的字符串</p> <div class="uuid-display">{{ customUUID }}</div> <button class="btn" @click="generateCustom">生成UUID</button> <button class="btn btn-copy" @click="copyToClipboard(customUUID)">复制</button> <div class="notes"> <strong>注意:</strong>这不是标准实现,但适用于大多数需要唯一标识符的场景 </div> </div> <div class="method"> <h3>4. 时间戳简易ID</h3> <p>基于时间戳生成的简易唯一ID</p> <div class="uuid-display">{{ simpleId }}</div> <button class="btn" @click="generateSimple">生成ID</button> <button class="btn btn-copy" @click="copyToClipboard(simpleId)">复制</button> <div class="notes"> <strong>注意:</strong>这不是UUID,但在简单场景下可以作为唯一标识符使用 </div> </div> </div> <div class="footer"> <p>© 2023 Vue2 UUID生成示例 | 选择适合您项目需求的UUID生成方法</p> </div> </div> <script> new Vue({ el: '#app', data: { uuidV4: '', cryptoUUID: '', customUUID: '', simpleId: '' }, mounted() { this.generateV4(); this.generateCrypto(); this.generateCustom(); this.generateSimple(); }, methods: { // 方法1: 使用uuid库 generateV4() { this.uuidV4 = uuidv4(); }, // 方法2: 使用Crypto API(现代浏览器) generateCrypto() { if (crypto && crypto.randomUUID) { this.cryptoUUID = crypto.randomUUID(); } else { this.cryptoUUID = '您的浏览器不支持Crypto.randomUUID()'; } }, // 方法3: 自定义UUID生成算法 generateCustom() { function generateUUID() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { const r = Math.random() * 16 | 0; const v = c === 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); } this.customUUID = generateUUID(); }, // 方法4: 基于时间戳的简易ID generateSimple() { this.simpleId = 'id_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); }, // 复制到剪贴板 copyToClipboard(text) { if (text.includes('不支持')) return; const textArea = document.createElement('textarea'); textArea.value = text; document.body.appendChild(textArea); textArea.select(); try { document.execCommand('copy'); alert('已复制到剪贴板: ' + text); } catch (err) { alert('复制失败: ' + err); } document.body.removeChild(textArea); } } }); </script> </body> </html>第二步写独立的加密方法文件
signature.js
import CryptoJS from 'crypto-js'; // import { v4 as uuidv4 } from 'uuid'; // 签名密钥 const SIGN_KEY = 'jianzhenbank@2025'; /** * 从URL中提取resource * @param {string} url 接口地址 * @returns {string} 提取的resource */ export function extractResource(url) { if (typeof url !== 'string') { return ''; } // 移除URL中的查询参数 const pureUrl = url.split('?')[0]; // 找到最后一个/的位置 const lastSlashIndex = pureUrl.lastIndexOf('/'); return pureUrl.substring(lastSlashIndex); } /** * 生成签名 * @param {Object} params 签名参数 * @returns {string} 签名结果 */ // export function generateSignature(params) { // // 按照指定格式拼接参数 // const paramString = `nonce=${params.nonce}&resource=${params.resource}×tamp=${params.timestamp}&token=${params.token || ''}`; // // 使用HMAC-SHA256生成签名 // return CryptoJS.HmacSHA256(paramString, SIGN_KEY).toString(CryptoJS.enc.Hex); // } export function generateSignature(params) { // 按照指定格式拼接参数 // 使用HMAC-SHA256生成签名 console.log("加密前======================>",params); return CryptoJS.HmacSHA256(params, SIGN_KEY).toString(CryptoJS.enc.Hex); } /** * 生成13位时间戳 * @returns {number} 时间戳 */ export function getTimestamp() { return Date.now(); } /** * 生成UUID * @returns {string} UUID */ // export function generateNonce() { // return uuidv4().replace(/-/g, ''); // } // 方法4: 基于时间戳的简易ID export function generateNonce() { return 'id_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); } /** * 生成签名字符串 * @param {Object} params - 签名参数对象 * @param {String} method - 请求方法 * @param {Object} config - axios请求配置 * @returns {String} 拼接后的签名字符串 */ export function generateSignatureString(params, method, config) { let signatureParts = {}; // 通用参数 signatureParts.nonce = params.nonce; signatureParts.resource = params.resource; signatureParts.timestamp = params.timestamp; signatureParts.token = params.token; // console.log("config.params======================>",config.params); // GET请求:拼接所有有值的查询参数 if (method === 'get') { const urlParams = new URLSearchParams(config.params || {}); for (const [key, value] of urlParams.entries()) { if (value !== undefined && value !== 'undefined' && value !== null && value !== 'null' && value !== '') { signatureParts[key] = value; } } } // POST请求(非登录/非文件上传):拼接body else if (method === 'post' && !isLoginOrUpload(config)) { if (config.data && typeof config.data === 'object') { signatureParts.body = JSON.stringify(config.data); } } // 其他请求类型可按需扩展 // // 剔除值为 null 或 'null' 的参数 // Object.keys(signatureParts).forEach(key => { // if (signatureParts[key] === null || signatureParts[key] === 'null') { // delete signatureParts[key]; // } // }); // TreeMap式排序 const sortedParams = Object.keys(signatureParts) .sort() .reduce((acc, key) => { acc[key] = signatureParts[key]; return acc; }, {}); // 按顺序拼接,使用&分隔 const signatureStr = Object.entries(sortedParams) .map(([key, value]) => `${key}=${value}`) .join('&'); return generateSignature(signatureStr); } /** * 判断是否为登录或文件上传请求 * @param {Object} config - axios请求配置 * @returns {Boolean} */ export function isLoginOrUpload(config) { const loginUrls = ['/login']; const uploadUrls = ['/upload', '/file/upload']; const url = config.url || ''; return loginUrls.some(u => url.endsWith(u)) || uploadUrls.some(u => url.endsWith(u)) || (config.headers && config.headers['Content-Type'] && config.headers['Content-Type'].includes('multipart/form-data')); }sm4.js
// utils/sm4.js import { sm4 } from 'sm-crypto'; // SM4 加密密钥(16字节,128位) const secretKey = '65d3ea57d0756fcc4e553e04d96006e3'; // 加密函数 export function encryptData(data) { // 将对象转换为 JSON 字符串 const dataString = typeof data === 'string' ? data : JSON.stringify(data); // 使用 SM4-ECB 模式加密,不使用向量 const encryptedHex = sm4.encrypt(dataString, secretKey); // 将加密后的十六进制字符串转换为 Base64 编码 const encryptedBase64 = btoa(hexToUint8Array(encryptedHex).reduce((data, byte) => { return data + String.fromCharCode(byte); }, '')); return encryptedBase64; } // 辅助函数:将十六进制字符串转换为 Uint8Array function hexToUint8Array(hex) { const arrayBuffer = new Uint8Array(hex.length / 2); for (let i = 0; i < hex.length; i += 2) { const byteValue = parseInt(hex.substr(i, 2), 16); if (isNaN(byteValue)) { throw new Error('Invalid hex string'); } arrayBuffer[i / 2] = byteValue; } return arrayBuffer; }第三步写请求拦截器逻辑
请求拦截器(http.interceptors.request)所在文件
import { encryptData } from '@/js/sm4.js'; import { extractResource, generateSignature, getTimestamp, generateNonce,generateSignatureString,isLoginOrUpload } from '@/js/signature.js'; const API_WHITELIST = [ '/login', // 登录接口 ]; window._axiosPromiseArr = [] // axios中设置放置要取消的对象 Vue.prototype.$http.interceptors.request.use((config) => { const isWhitelisted = API_WHITELIST.some(url => config.url.endsWith(url) ); // 只对登录接口进行加密 if (isWhitelisted) { // 加密请求数据 if (config.data) { config.data = encryptData(config.data) } } const token = localStorage.getItem("header") if (token) { config.headers.Authorization = token } // 1. 生成基础参数 const nonce = generateNonce(); const timestamp = getTimestamp(); // 2. 提取resource(从完整URL中) const fullUrl = config.baseURL ? config.baseURL + config.url : config.url; const urlWithoutParams = fullUrl.includes('?') ? fullUrl.split('?')[0] : fullUrl; const resource = extractResource(urlWithoutParams); // 3. 生成签名字符串(根据请求类型和参数) const signatureStr = generateSignatureString({ nonce, timestamp, resource, token }, config.method.toLowerCase(), config); console.log('注意了,上面打印的是这接口加密前======================>'+urlWithoutParams+"接口加密后======================>",signatureStr); // 4. 添加到请求头 config.headers = { ...config.headers, nonce, timestamp, signature: signatureStr, }; return config })注意事项:如果登录请求接口/login加密后无法正常传参
请指定'Content-Type': 'application/json' // 显式指定
/*接口请求*/ this.$http({ method: "post", url: "/cbrc/login", headers: { code: this.code, imgkey: this.imgkey, 'Content-Type': 'application/json' // 显式指定 }, data: data, })最终效果
请求标头:
POST /cbrc/docinfo/updateDocinfStatus HTTP/1.1 Accept: application/json, text/plain, */* Accept-Encoding: gzip, deflate, br, zstd Accept-Language: zh-CN,zh;q=0.9 Authorization: eyJhbGciOiJIUzUxMiIsInppcCI6IkdaSVAifQ.H4sIAAAAAAAAAB2MSw7CMBBD7zJrImXayXfLBbjCJCGQSkRIaaVSxN0Z2Nl-tt-wrA0ihMyFZquVz9kqcqGqlEkrwzU5ClPRWOEEjVeI6IzxFDzOEowhay6P1gWPLYmbEAOZeNy535YD4-UsiLfyK_aX6Ov-_L9Yh7O8fL7oa3gzhQAAAA.96WjgyzLREkys_hqoj4M7nvmx7rLrmtFcfEUmAhftu4HTL6yvc8nn5JQ6EeDXpT1m0gAMEjOgoxPc82upGhTVw Connection: keep-alive Content-Length: 412 Content-Type: application/json;charset=UTF-8 Cookie: parent_qimo_sid_1129a340-f365-11eb-98e3-dfa9d16ffc4a=f73bd7db-c51b-4013-bc9f-76ce03e86256; href=http%3A%2F%2Flocalhost%3A8989%2F; accessId=1129a340-f365-11eb-98e3-dfa9d16ffc4a; qimo_seosource_0=%E7%AB%99%E5%86%85; qimo_seokeywords_0=; qimo_seosource_1129a340-f365-11eb-98e3-dfa9d16ffc4a=%E7%AB%99%E5%86%85; qimo_seokeywords_1129a340-f365-11eb-98e3-dfa9d16ffc4a=; qimo_xstKeywords_1129a340-f365-11eb-98e3-dfa9d16ffc4a=; header=eyJhbGciOiJIUzUxMiIsInppcCI6IkdaSVAifQ.H4sIAAAAAAAAAB2MSw7CMBBD7zJrImXayXfLBbjCJCGQSkRIaaVSxN0Z2Nl-tt-wrA0ihMyFZquVz9kqcqGqlEkrwzU5ClPRWOEEjVeI6IzxFDzOEowhay6P1gWPLYmbEAOZeNy535YD4-UsiLfyK_aX6Ov-_L9Yh7O8fL7oa3gzhQAAAA.96WjgyzLREkys_hqoj4M7nvmx7rLrmtFcfEUmAhftu4HTL6yvc8nn5JQ6EeDXpT1m0gAMEjOgoxPc82upGhTVw; pageViewNum=6 Host: localhost:8989 Origin: http://localhost:8989 Sec-Fetch-Dest: empty Sec-Fetch-Mode: cors Sec-Fetch-Site: same-origin User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 nonce: id_1755849870531_yrq9wrby5 sec-ch-ua: "Not)A;Brand";v="8", "Chromium";v="138", "Google Chrome";v="138" sec-ch-ua-mobile: ?0 sec-ch-ua-platform: "Windows" signature: f0d943fdd90a7c385bf8e854d7042026952fbfce1a38d76b0c3d314e8bb6a3e2 timestamp: 1755849870531后续出现的问题与解决方案
问题:当前js文件的加密方法不太完美, 如代码A( this.$http({ method: 'get', url: '/cbrc/dropDown/data_dic?typeId=1&pid=2' }) ),代码B( this.$http({ method: 'get', url: '/cbrc/dropDown/data_dic?typeId=&pid=', params:{ typeId:1, pid:2 } }) ),当我在加密拼接参数的时候,如果是代码A就无法正常获取参数值并拼接,如果是代码B这种传参方式就能正常传参,我想无论哪种传参方式都能正常获取到并拼接,修改signature.js文件
/*
优化说明:
1. 新增 parseUrlQueryParams 方法,专门解析URL中的查询参数为对象,保证无论参数在URL还是params字段都能被正确获取和拼接。
2. 在 generateSignatureString 方法中,GET请求时会合并URL和config.params中的参数,优先以config.params为准(覆盖URL中的同名参数),保证两种传参方式都能正常加密。
3. 其他逻辑保持原有注释和结构,增强代码可读性和健壮性。
*/
import CryptoJS from 'crypto-js'; // import { v4 as uuidv4 } from 'uuid'; // 签名密钥 const SIGN_KEY = 'xiamenbank@2025'; /** * 从URL中提取resource * @param {string} url 接口地址 * @returns {string} 提取的resource */ export function extractResource(url) { if (typeof url !== 'string') { return ''; } // 移除URL中的查询参数 const pureUrl = url.split('?')[0]; // 找到最后一个/的位置 const lastSlashIndex = pureUrl.lastIndexOf('/'); return pureUrl.substring(lastSlashIndex); } /** * 生成签名 * @param {Object} params 签名参数 * @returns {string} 签名结果 */ // export function generateSignature(params) { // // 按照指定格式拼接参数 // const paramString = `nonce=${params.nonce}&resource=${params.resource}×tamp=${params.timestamp}&token=${params.token || ''}`; // // 使用HMAC-SHA256生成签名 // return CryptoJS.HmacSHA256(paramString, SIGN_KEY).toString(CryptoJS.enc.Hex); // } export function generateSignature(params) { // 按照指定格式拼接参数 // 使用HMAC-SHA256生成签名 console.log("加密前======================>", params); return CryptoJS.HmacSHA256(params, SIGN_KEY).toString(CryptoJS.enc.Hex); } /** * 生成13位时间戳 * @returns {number} 时间戳 */ export function getTimestamp() { return Date.now(); } /** * 生成UUID * @returns {string} UUID */ // export function generateNonce() { // return uuidv4().replace(/-/g, ''); // } // 方法4: 基于时间戳的简易ID export function generateNonce() { return 'id_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); } /** * 解析URL中的查询参数为对象 * @param {string} url - 完整URL字符串 * @returns {Object} 查询参数对象 */ function parseUrlQueryParams(url) { const paramsObj = {}; if (typeof url !== 'string') return paramsObj; const queryStr = url.split('?')[1]; if (!queryStr) return paramsObj; const urlParams = new URLSearchParams(queryStr); for (const [key, value] of urlParams.entries()) { // 只添加有key的参数 paramsObj[key] = value; } return paramsObj; } /** * 生成签名字符串 * @param {Object} params - 签名参数对象 * @param {String} method - 请求方法 * @param {Object} config - axios请求配置 * @returns {String} 拼接后的签名字符串 */ export function generateSignatureString(params, method, config) { let signatureParts = {}; // 通用参数 signatureParts.nonce = params.nonce; signatureParts.resource = params.resource; signatureParts.timestamp = params.timestamp; signatureParts.token = params.token; // 统一处理GET请求参数,无论参数在URL还是params字段 if (method === 'get') { // 1. 解析URL中的查询参数 const urlQueryParams = parseUrlQueryParams(config.url || ''); // 2. 解析config.params中的参数 const configParams = config.params || {}; // 合并两者,优先以config.params为准(覆盖URL中的同名参数) const mergedParams = { ...urlQueryParams, ...configParams }; // 只拼接有值的参数 Object.keys(mergedParams).forEach(key => { const value = mergedParams[key]; if ( value !== undefined && value !== 'undefined' && value !== null && value !== 'null' && value !== '' ) { signatureParts[key] = value; } }); } // POST请求(非登录/非文件上传):拼接body else if (method === 'post' && !isLoginOrUpload(config)) { if (config.data && typeof config.data === 'object') { signatureParts.body = JSON.stringify(config.data); } } // 其他请求类型可按需扩展 // // 剔除值为 null 或 'null' 的参数 // Object.keys(signatureParts).forEach(key => { // if (signatureParts[key] === null || signatureParts[key] === 'null') { // delete signatureParts[key]; // } // }); // TreeMap式排序 const sortedParams = Object.keys(signatureParts) .sort() .reduce((acc, key) => { acc[key] = signatureParts[key]; return acc; }, {}); // 按顺序拼接,使用&分隔 const signatureStr = Object.entries(sortedParams) .map(([key, value]) => `${key}=${value}`) .join('&'); return generateSignature(signatureStr); } /** * 判断是否为登录或文件上传请求 * @param {Object} config - axios请求配置 * @returns {Boolean} */ export function isLoginOrUpload(config) { const loginUrls = ['/login']; const uploadUrls = ['/upload', '/file/upload']; const url = config.url || ''; return loginUrls.some(u => url.endsWith(u)) || uploadUrls.some(u => url.endsWith(u)) || (config.headers && config.headers['Content-Type'] && config.headers['Content-Type'].includes('multipart/form-data')); } /* 优化说明: 1. 新增 parseUrlQueryParams 方法,专门解析URL中的查询参数为对象,保证无论参数在URL还是params字段都能被正确获取和拼接。 2. 在 generateSignatureString 方法中,GET请求时会合并URL和config.params中的参数,优先以config.params为准(覆盖URL中的同名参数),保证两种传参方式都能正常加密。 3. 其他逻辑保持原有注释和结构,增强代码可读性和健壮性。 */