import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios'; import { IEncryptor, ISigner, CryptoConfig, EncryptedRequest, EncryptedResponse } from '../crypto/interface'; import { uint8ArrayToBase64, base64ToUint8Array, stringToUint8Array, uint8ArrayToString } from '../utils/base64'; import { generateUUID } from '../utils/uuid'; /** * 加密Axios实例 */ export class EncryptedAxios { private axiosInstance: AxiosInstance; private encryptor: IEncryptor | null = null; private signer: ISigner | null = null; private config: CryptoConfig; constructor( encryptor?: IEncryptor, signer?: ISigner, config: CryptoConfig = {}, axiosConfig?: AxiosRequestConfig ) { this.encryptor = encryptor || null; this.signer = signer || null; this.config = { timestampWindow: 5 * 60 * 1000, // 默认5分钟 enableTimestamp: true, enableSignature: true, ...config, }; // 创建axios实例 this.axiosInstance = axios.create(axiosConfig); // 添加请求拦截器 this.axiosInstance.interceptors.request.use( this.encryptRequestInterceptor.bind(this), (error) => Promise.reject(error) ); // 添加响应拦截器 this.axiosInstance.interceptors.response.use( this.decryptResponseInterceptor.bind(this), (error) => Promise.reject(error) ); } /** * 请求拦截器 - 加密请求数据 */ private async encryptRequestInterceptor( config: InternalAxiosRequestConfig ): Promise { // 放行GET和OPTIONS请求 if (config.method?.toUpperCase() === 'GET' || config.method?.toUpperCase() === 'OPTIONS') { return config; } // 如果没有配置加密器,直接返回 if (!this.encryptor) { return config; } try { // 将请求数据转换为JSON字符串 const plaintext = JSON.stringify(config.data || {}); const plaintextBytes = stringToUint8Array(plaintext); // 加密数据 const ciphertext = await this.encryptor.encrypt(plaintextBytes); const encryptedData = uint8ArrayToBase64(ciphertext); // 构建加密请求体 const encryptedRequest: EncryptedRequest = { data: encryptedData, timestamp: Date.now(), request_id: generateUUID(), algorithm: this.encryptor.name(), }; // 生成签名 if (this.config.enableSignature && this.signer) { const signature = await this.signer.sign(plaintextBytes); encryptedRequest.signature = uint8ArrayToBase64(signature); } // 替换请求数据 config.data = encryptedRequest; config.headers['Content-Type'] = 'application/json'; // 保存request_id供响应使用 config.headers['X-Request-ID'] = encryptedRequest.request_id; } catch (error) { console.error('加密请求失败:', error); throw error; } return config; } /** * 响应拦截器 - 解密响应数据 */ private async decryptResponseInterceptor( response: AxiosResponse ): Promise { // 如果没有配置加密器或响应不是加密格式,直接返回 if (!this.encryptor || !response.data || typeof response.data !== 'object') { return response; } // 检查是否是加密响应 const encryptedResponse = response.data as EncryptedResponse; if (!encryptedResponse.data || !encryptedResponse.request_id) { // 不是加密响应,直接返回 return response; } try { // 验证时间戳 if (this.config.enableTimestamp) { this.verifyTimestamp(encryptedResponse.timestamp); } // 解密数据 const ciphertext = base64ToUint8Array(encryptedResponse.data); const plaintext = await this.encryptor.decrypt(ciphertext); // 验证签名 if (this.config.enableSignature && this.signer && encryptedResponse.signature) { const signature = base64ToUint8Array(encryptedResponse.signature); const isValid = await this.signer.verify(plaintext, signature); if (!isValid) { throw new Error('签名验证失败'); } } // 将解密后的数据转换为JSON对象 const decryptedData = uint8ArrayToString(plaintext); response.data = JSON.parse(decryptedData); } catch (error) { console.error('解密响应失败:', error); throw error; } return response; } /** * 验证时间戳 */ private verifyTimestamp(timestamp: number): void { const now = Date.now(); const diff = Math.abs(now - timestamp); if (diff > (this.config.timestampWindow || 5 * 60 * 1000)) { throw new Error('请求超时'); } } /** * 获取axios实例 */ getInstance(): AxiosInstance { return this.axiosInstance; } /** * GET 请求 */ get(url: string, config?: AxiosRequestConfig): Promise> { return this.axiosInstance.get(url, config); } /** * POST 请求 */ post(url: string, data?: any, config?: AxiosRequestConfig): Promise> { return this.axiosInstance.post(url, data, config); } /** * PUT 请求 */ put(url: string, data?: any, config?: AxiosRequestConfig): Promise> { return this.axiosInstance.put(url, data, config); } /** * DELETE 请求 */ delete(url: string, config?: AxiosRequestConfig): Promise> { return this.axiosInstance.delete(url, config); } /** * PATCH 请求 */ patch(url: string, data?: any, config?: AxiosRequestConfig): Promise> { return this.axiosInstance.patch(url, data, config); } }