当前位置: 首页 > news >正文

项目安全问题——前端两步完成加密

前言

我之前有写过:项目安全问题-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&timestamp=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&timestamp=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}&timestamp=${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}&timestamp=${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. 其他逻辑保持原有注释和结构,增强代码可读性和健壮性。 */
http://www.cnnetsun.cn/news/2077433.html

相关文章:

  • HyperAgents框架:构建具备思考-行动循环的LLM智能体实战指南
  • 全同态加密与AI Agent融合:构建隐私优先的去中心化预测系统
  • 打造开箱即用的终端代码编辑器:基于Micro的轻量级开发环境实践
  • Hayase社区参与指南:如何加入讨论、报告问题和提出建议
  • 云原生运维代理TAT Agent:Rust构建的自动化命令执行利器
  • Symfony Inflector 测试用例完全解析:掌握300+单词转换规则
  • Jazz社区生态建设:如何参与开源贡献和推动项目发展
  • 木及简历证件照功能深度评测:打破传统模板约束的创新设计
  • 从瀑布流到无尽隧道:5个用Unity UV动画就能实现的酷炫2D/3D效果(保姆级教程)
  • LeaderF缓冲区管理完全指南:快速切换和搜索打开的文件
  • Lang-Agent:基于LangGraph的可视化AI Agent开发平台实战指南
  • USBbuildingFootprints性能优化:处理大规模地理数据集的10个技巧
  • iOS键盘遮挡终极解决方案:TPKeyboardAvoiding三大组件深度解析
  • SpringBoot+Vue网上订餐系统源码+论文
  • 3步打通Syft跨团队协作:从代码提交到SBOM交付的无缝协作指南
  • 在 SAP Gateway 的 Function Import 里传长字符串,真正容易卡住的地方,不在 Edm.String
  • 仅限本周开放!VSCode日志插件性能调优白皮书(含CPU占用下降62%、内存泄漏修复Patch)
  • 如何将你的项目服务配置托管到Nacos配置中心?一文讲透
  • 手把手教你用Docker Compose配置HedgeDoc,避开数据库连接和端口映射的那些坑
  • 为什么92%的嵌入式工程师在大模型移植中踩坑?:曝光未公开的C语言ABI对齐陷阱、栈溢出隐蔽路径与中断冲突根源
  • 【转载】高性能漏洞扫描器afog介绍及使用
  • OpenOmniBot:构建具备规划、执行与记忆能力的通用AI智能体框架
  • Metso Valmet A413052电路板模块
  • 3步掌握专业手机号码定位工具:高效查询电话号码真实位置
  • 2025_NIPS_UI-Genie: A Self-Improving Approach for Iteratively Boosting MLLM-based Mobile GUI Agents
  • 别让PICkit3.5+吃灰了!手把手教你激活硬件仿真,搞定485通讯调试难题
  • ESP32蓝牙音频终极指南:如何用简单代码实现专业级音乐接收器和发送器
  • SanityHarness:前端应用健康检查与健全性测试实践指南
  • 海康威视访客系统API避坑指南:从权限下发失败到动态二维码生成的5个常见问题
  • 小升初英语衔接轻创业,KISSABC 落地全拆解