ts使用CryptoJS.HmacSHA256方法显示undefined的问题

本文介绍了在使用CryptoJS库时遇到的问题及其解决办法。作者最初尝试使用官方文档推荐的方法来实现HMAC-SHA256算法,但在TypeScript环境中遇到了方法未定义的错误。通过调整引入方式,最终成功实现了所需功能。

一开始我是按照官方文档

安装 npm install crypto-js

import CryptoJS from 'crypto-js/crypto-js'

key = "秘钥”

CryptoJS.HmacSHA256("sksffrk111",key)

在ts中就会出错,方法undefined,

后来直接引入 import HmacSHA256 from 'crypto-js/hmac-sha256' 也不行,同样报错

最后使用了另一种方式require("crypto-js");

官方给的另一种加载方式

 var CryptoJS = require("crypto-js");

       console.log(CryptoJS.HmacSHA256("skshsffs122","sksksf22"))

如何实现 post请求通过params传递参数时不参与签名。这是请求方法:export function getTask(data) { return request.post('/earnify-customer/customer/userTask/receive', data) }这是调用:const res = await getTask({ taskId:1 })这是request.ts文件:import type { AxiosInstance } from 'axios' import axios from 'axios' import Cookies from 'js-cookie' import { useUserStore } from '@/store/hooks/userStore' const userStore = useUserStore() import { useOutsideRouter } from '@/store/hooks/useOutsideRouter' import { signRequest } from './sign' export interface RespData<T> { success?: boolean errorCode?: number error?: number | string | null msg?: string data?: T [key: string]: any } // redirect error function errorRedirect(url: string) { const { router } = useOutsideRouter() router.push(url) } // code Message const codeMessage: { [key: number]: string } = { 200: '服务器成功返回请求的数据。', 201: '新建或修改数据成功。', 202: '一个请求已经进入后台排队(异步任务)。', 204: '删除数据成功。', 206: '进行范围请求成功。', 400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。', 401: '用户没有权限(令牌、用户名、密码错误)。', 403: '用户得到授权,但是访问是被禁止的。', 404: '发出的请求针对的是不存在的记录,服务器没有进行操作。', 405: '请求不允许。', 406: '请求的格式不可得。', 410: '请求的资源被永久删除,且不会再得到的。', 422: '当创建一个对象时,发生一个验证错误。', 500: '服务器发生错误,请检查服务器。', 502: '网关错误。', 503: '服务不可用,服务器暂时过载或维护。', 504: '网关超时。' } // 创建axios实例 const service: AxiosInstance = axios.create({ // api 的 base_url baseURL: 'https://tapi.shenyantuling.com:10000', // 请求超时时间 timeout: 6000000 }) // request拦截器 service.interceptors.request.use( request => { const token: string | undefined = userStore.userInfo.token // Conversion of hump nomenclature /** * 让每个请求携带自定义 token * 请根据实际情况自行修改 */ if (request.url === '/login') { return request } request.headers!.token = token as string // request.headers!.extendData = // TODO: 临时使用 // 确保 FormData 请求的 Content-Type 在签名计算之前设置 if (request.data instanceof FormData) { request.headers['Content-Type'] = 'multipart/form-data' } signRequest(request) request.headers['ngrok-skip-browser-warning'] = '69420' return request }, error => { return Promise.reject(error) } ) // respone拦截器 service.interceptors.response.use( response => { /** * response data * { * data: {}, * msg: "", * error: 0 0 success | 1 error | 5000 failed | HTTP code * } */ const data: any = response.data const msg: string = data.msg || '' if (msg.indexOf('user not log in') !== -1 && data.error === -1) { // TODO 写死的 之后要根据语言跳转 errorRedirect('login') return } if (response.config.autoDownLoadFile === undefined || response.config.autoDownLoadFile) { Promise.resolve().then(() => { useResHeadersAPI(response.headers, data) }) } if ( response.request.responseType === 'blob' && /json$/gi.test(response.headers['content-type']) ) { return new Promise(resolve => { const reader = new FileReader() reader.readAsText(<Blob>response.data) reader.onload = () => { if (!reader.result || typeof reader.result !== 'string') return resolve(response.data) response.data = JSON.parse(reader.result) resolve(response.data) } }) } else if (data instanceof Blob) { return { data, msg: '', error: 0 } } if (data.msg === null) { data.msg = 'Unknown error' } return data }, error => { /** * 某些特定的接口 404 500 需要跳转 * 在需要重定向的接口中传入 redirect字段 值为要跳转的路由 * redirect之后 调用接口的地方会继续执行 * 因为此时 response error * 所以需要前端返回一个前端构造好的数据结构 避免前端业务部分逻辑出错 * 不重定向的接口则不需要传 */ if (error.config?.redirect) { errorRedirect(error.config.redirect) } if (error.response) { return { data: {}, error: error.response.status, msg: codeMessage[error.response.status] || error.response.data.message } } else { // 某些特定的接口 failed 需要跳转 return { data: {}, error: 5000, msg: '服务请求不可用,请重试或检查您的网络。' } } } ) export function sleep(time = 0) { return new Promise((resolve) => { setTimeout(() => { resolve({}) }, time) }) } function extractFileNameFromContentDispositionHeader(value: string) { const patterns = [ /filename\*=[^']+'\w*'"([^"]+)";?/i, /filename\*=[^']+'\w*'([^;]+);?/i, /filename="([^;]*);?"/i, /filename=([^;]*);?/i ] let responseFilename: any = null patterns.some(regex => { responseFilename = regex.exec(value) return responseFilename !== null }) if (responseFilename !== null && responseFilename.length > 1) { try { return decodeURIComponent(responseFilename[1]) } catch (e) { console.error(e) } } return null } export function downloadFile(boldData: BlobPart, filename = 'shinewing', type: any) { const blob = boldData instanceof Blob ? boldData : new Blob([boldData], { type }) const url = window.URL.createObjectURL(blob) const link = document.createElement('a') link.style.display = 'none' link.href = url link.download = filename document.body.appendChild(link) link.click() document.body.removeChild(link) } export function useResHeadersAPI(headers: any, resData: any) { const disposition = headers['content-disposition'] if (disposition) { let filename: string | null = '' filename = extractFileNameFromContentDispositionHeader(disposition) if (filename) { downloadFile(resData, filename, headers['content-type']) } } } const requestSuite: IRequestSuite = { get(uri, params, config) { return service.get(uri, { params, ...config }) }, post(uri, data, config) { if (config && config.params) { return service.post(uri, data, config) } return service.post(uri, data, config) }, put(uri, data, config) { return service.put(uri, data, config) }, patch(uri, data, config) { return service.patch(uri, data, config) }, delete(uri, config) { return service.delete(uri, config) } } export default requestSuite 这是sign.ts文件:import CryptoJS from 'crypto-js' // import { localStg } from '@/utils/storage'; // import { useUserStore } from '@/store/hooks/userStore' // const userStore = useUserStore() export function signRequest(config: any) { // const path = config.url; const timestamps = Date.now().toString() const key = 'shDOUArrDhpeAMw9FGY79Zmy3MLWwNWy' // 添加 timestamps 到 headers config.headers.timestamps = timestamps // const test = loadParam(path, config.data); const test = loadParam(config) // const token = userStore.userInfo.token // 添加token // config.headers.token = token || '' config.headers.os = 'web' config.headers.businessType = 'earnify_customer' // AES 加密 timestamps const aesTimestamp = CryptoJS.AES.encrypt(timestamps, CryptoJS.enc.Utf8.parse('4d5bc50346c22dde12be2c3b1b89ada6'), { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.ZeroPadding }) config.headers.envTimestamps = aesTimestamp.toString() // 计算签名 config.headers.sign = CryptoJS.MD5(test + timestamps + key) .toString(CryptoJS.enc.Hex) .toLowerCase() console.log(test) console.log('生成的签名:', config.headers.sign) return config } function loadParam(config: any): string { let str = '' if (config.headers['Content-Type'] !== 'multipart/form-data') { if (config.data || config.params) { if (config.method === 'get') { if (config.params) { const params = config.params const keyList = Object.keys(config.params) // 解析 GET 请求的查询参数 for (const key of keyList) { str += keyList[keyList.length - 1] !== key ? `${ key }=${ encodeURI(<string>params[key]) }&` : `${ key }=${ encodeURI(<string>params[key]) }` } console.log( str, 'gett') } } else if(config.method === 'post') { if(config.data) { const data = config.data const keyList = Object.keys(config.data) // 解析 GET 请求的查询参数 for (const key of keyList) { str += keyList[keyList.length - 1] !== key ? `${ key }=${ encodeURI(<string>data[key]) }&` : `${ key }=${ encodeURI(<string>data[key]) }` } console.log( str, 'post') } } else if(config.data) { str = JSON.stringify(config.data) } } } else { const url = config.url if (url.includes('?')) { str += url.substring(url.indexOf('?') + 1, url.length) } } return str } 指出问题 并解决
08-01
<think>根据用户的问题,用户希望在使用Axios发起POST请求时,让params参数不参与签名计算。签名计算通常是在请求拦截器中进行的,因此我们需要在拦截器中排除params参数。 在Axios中,POST请求可以同时使用`data`和`params`参数: - `params`:这些参数会作为查询字符串附加在URL上。 - `data`:作为请求体被发送。 用户的问题是,当前的签名计算可能同时包含了`data`和`params`,而用户希望只对`data`(请求体)进行签名,或者排除`params`。 解决方案: 1. 在请求拦截器中,我们可以获取到请求的配置对象(config)。 2. 配置对象中包含了`params`(URL参数)和`data`(请求体数据)。 3. 在计算签名的函数中,我们只使用`data`部分,或者从整个配置中删除`params`的影响。 但是,签名的计算方式是由用户自己实现的,因此我们需要在计算签名之前,从用于生成签名的参数对象中移除`params`,或者单独提取`data`。 假设用户当前的签名计算可能是将整个config作为输入,或者将config中的某些字段合并后计算。那么我们需要调整用于签名的数据源,排除`params`。 下面是一个示例,展示如何在请求拦截器中修改签名计算,使其忽略`params`参数: 原代码可能类似于: ```javascript // 请求拦截器 instance.interceptors.request.use(config => { // 计算签名,可能使用了config.params和config.data const signature = calculateSignature(config); config.headers['signature'] = signature; return config; }); ``` 修改后,我们希望签名计算不包含params,那么可以创建一个新的对象,只包含需要签名的部分(例如只包含data,或者排除params后的其他必要信息): ```javascript instance.interceptors.request.use(config => { // 创建一个用于签名的对象,这里我们假设只需要data,或者还可以包含其他除了params以外的信息 // 注意:根据实际签名规则,可能还需要包含其他信息(如url、method等),但排除params const signData = { url: config.url, method: config.method, data: config.data // 可以添加其他需要的字段,但不包括config.params }; const signature = calculateSignature(signData); config.headers['signature'] = signature; return config; }); ``` 或者,如果原来的calculateSignature函数内部已经使用了config,并且我们无法修改该函数的内部结构,那么我们可以临时删除params,计算完签名后再恢复(但这种方法不太优雅,而且如果签名函数内部需要params的话就会出错,所以不推荐)。 另一种方法是修改calculateSignature函数,使其忽略params参数。这取决于签名函数的实现。 因此,关键在于调整用于生成签名的数据源,确保不包含params。 由于用户没有提供具体的签名计算函数,这里只能给出一般性建议。 示例代码: 假设我们有一个签名函数,它接收一个对象,然后将其排序后拼接成字符串,然后进行哈希计算。那么我们可以这样: ```javascript function calculateSignature(obj) { // 将对象转换为字符串(例如按键排序后拼接) const str = Object.keys(obj).sort().map(key => `${key}=${obj[key]}`).join('&'); // 然后使用某种哈希算法,比如SHA256,这里用伪代码表示 return sha256(str); } // 在拦截器中 instance.interceptors.request.use(config => { // 创建一个新的对象,只包含我们想要签名的字段,排除params const signObj = { ...config }; delete signObj.params; // 删除params // 注意:可能还需要删除其他不需要签名的字段,比如headers等,根据实际情况 const signature = calculateSignature(signObj); config.headers['signature'] = signature; return config; }); ``` 但是,这种方法可能会因为删除了params而影响到其他部分的签名逻辑,比如URL中原本应该包含params,但这里我们删除了,所以签名中就没有了。如果服务端验证签名时也不考虑params,那么就可以。 所以,更合理的做法是:在签名函数内部,根据需求选择要签名的字段,而不是直接传入整个config。这样更可控。 因此,建议重构签名计算函数,明确指定需要参与签名的字段。 总结步骤: 1. 确定签名计算需要哪些字段(比如url, method, data等)。 2. 在请求拦截器中,构建一个只包含这些字段的对象(排除params)。 3. 使用这个对象进行签名计算。 这样,params参数就不会参与签名计算了。 下面是一个完整的示例: ```javascript import axios from 'axios'; import md5 from 'crypto-js/md5'; // 使用crypto-js的md5作为示例 // 创建axios实例 const instance = axios.create({ baseURL: 'https://your-api-domain.com', timeout: 5000 }); // 请求拦截器 instance.interceptors.request.use(config => { // 构建签名所需的对象,排除params const signData = { url: config.url, // 注意:这里config.url可能已经包含了baseURL,根据实际情况看是否需要完整URL method: config.method, data: config.data // 如果是对象,可能需要序列化,根据签名函数要求 }; // 计算签名 const signature = calculateSignature(signData); // 将签名放到请求头 config.headers['signature'] = signature; return config; }); // 响应拦截器 instance.interceptors.response.use( response => response, error => { // 错误处理 return Promise.reject(error); } ); // 签名函数 function calculateSignature(obj) { // 将对象转换为字符串:这里简单示例,按字段名排序后拼接 // 注意:如果data是对象,需要先序列化为字符串(如JSON.stringify) const dataStr = obj.data ? JSON.stringify(obj.data) : ''; const str = `url=${obj.url}&method=${obj.method}&data=${dataStr}`; // 使用md5生成签名,实际中可能使用更复杂的算法和密钥 return md5(str).toString(); } // 使用实例发送请求 instance.post('/user', { name: 'John' }, { params: { id: 12345 // 这个params不会参与签名 } }) .then(response => { console.log(response); }) .catch(error => { console.error(error); }); ``` 注意:上述示例中,签名函数比较简单,实际应用中可能需要更严谨的算法(如HMAC-SHA256)和密钥,并且要考虑字段顺序、特殊字符处理等。 通过这种方式,我们确保了在POST请求中,只有url、method和data参与签名,而params参数被排除在外。 最后,根据用户的要求,生成相关问题: §§相关问题§§ 1. 在Axios中,如何确保请求拦截器中的签名计算只针对请求体(data)? 2. 除了排除params,还有哪些常见的签名安全策略? 3. 如果服务端要求URL中的参数(params)也要参与签名,该如何调整?</think>### Axios POST 请求中排除 params 参数参与签名计算的实现方案 在 Axios 中实现 `params` 参数不参与签名计算,需在请求拦截器中精确控制签名数据的范围。以下是分步解决方案: #### 1. **核心原理** Axios 请求配置中: - `params`:URL 查询参数(如 `/endpoint?id=123`) - `data`:请求体数据(POST/PUT 等请求的 body) 签名计算应仅包含 `data` 和必要元数据,需显式排除 `params` 对象。 #### 2. **实现代码示例** ```javascript import axios from "axios"; import crypto from "crypto-js"; const instance = axios.create(); // 请求拦截器 instance.interceptors.request.use(config => { // 仅提取需要签名的字段,排除 params const signData = { method: config.method?.toUpperCase(), // 请求方法 url: config.url, // 请求路径 data: config.data // 仅包含请求体 }; // 计算签名(示例使用 HMAC-SHA256) const signature = calculateSignature(signData); // 添加签名到请求头 config.headers["X-Signature"] = signature; return config; }); // 签名计算函数(排除 params) function calculateSignature({ method, url, data }) { const secret = "YOUR_SECRET_KEY"; // 序列化数据(确保稳定排序) const payload = { method, url, data: data || {} // 处理空 data }; const payloadString = JSON.stringify(payload, Object.keys(payload).sort()); // 返回 HMAC-SHA256 签名 return crypto.HmacSHA256(payloadString, secret).toString(); } // 使用示例:params 不会参与签名 instance.post("/api/user", { name: "Alice" }, { params: { debug: true } // 此参数不被签名 }); ``` #### 3. **关键处理点** - **显式隔离签名数据**:创建 `signData` 对象时仅包含 `method`、`url` 和 `data`,彻底排除 `params` - **稳定序列化**:使用 `JSON.stringify` 时按字段名排序,避免键顺序差异导致签名不一致 - **空数据处理**:`data: data || {}` 确保空请求体时仍有稳定结构 - **签名算法**:实际应用需替换为您的业务签名算法(如 RSA、AES 等) #### 4. **安全增强建议** 1. **时间戳防重放**:在签名数据中添加 `timestamp` 字段 ```javascript signData.timestamp = Date.now(); ``` 2. **Nonce 防重放**:每次请求生成唯一随机数 3. **敏感数据加密**:对 `data` 中的敏感字段额外加密 #### 5. **服务端验证要点** 服务端应使用相同规则重建签名: - **仅验证请求体**:忽略 URL 中的 `params` 参数 - **时间窗口**:检查 `timestamp` 有效性(如 ±5 分钟) - **Nonce 校验**:确保随机数未被复用 > 通过精确控制签名数据的范围,可确保 `params` 参数完全不影响签名结果,同时保持请求的灵活性[^1]。 --- ###
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值