Skip to content

接口加密处理(生产环境)

在实际项目中,对接口请求/返回数据采用RSA加密解密并结合公钥定期更新的方案,需要从加密流程设计、密钥管理、兼容性保障等多维度系统实现。以下从实际应用、拓展方向以及面试应答角度展开说明:

实际项目中的使用方式

实际流程

  • 使用 jsencrypt 库实现 RSA 加密,封装公钥管理和加密方法

  • 公钥管理

    • 公钥通过后端接口获取
    • 包含过期时间管理,避免使用过期公钥
    • 在需要加密时自动检查公钥有效性,无效则重新获取
  • Axios 封装

    • 通过自定义参数encrypt: true控制是否需要加密
    • 只对 POST 请求的请求体进行加密
    • 加密后的数据格式为{ encrypted: true, data: '加密字符串' },便于后端识别
  • 使用方式:

    • 在 API 请求中添加encrypt: true即可自动加密
    • 无需修改组件中的调用方式,保持原有开发体验

创建RSA加密类

js
import JSEncrypt from 'jsencrypt'

/**
 * RSA加密工具类
 */
class RSAEncryptor {
  constructor() {
    this.encryptor = new JSEncrypt()
    this.publicKey = null
    this.keyExpireTime = null // 公钥过期时间
  }

  /**
   * 设置公钥
   * @param {string} publicKey 公钥字符串
   * @param {number} expireSeconds 过期时间(秒)
   */
  setPublicKey(publicKey, expireSeconds = 3600) {
    this.publicKey = publicKey
    this.encryptor.setPublicKey(publicKey)
    // 设置过期时间
    this.keyExpireTime = Date.now() + expireSeconds * 1000
  }

  /**
   * 检查公钥是否有效
   * @returns {boolean} 是否有效
   */
  isPublicKeyValid() {
    return !!this.publicKey && Date.now() < this.keyExpireTime
  }

  /**
   * 加密数据
   * @param {any} data 需要加密的数据
   * @returns {string|null} 加密后的字符串,失败返回null
   */
  encrypt(data) {
    if (!this.isPublicKeyValid()) {
      console.error('公钥无效或已过期')
      return null
    }

    try {
      // 先将数据转为JSON字符串再加密
      const jsonStr = JSON.stringify(data)
      return this.encryptor.encrypt(jsonStr)
    } catch (error) {
      console.error('RSA加密失败:', error)
      return null
    }
  }
}

// 导出单例
export const rsaEncryptor = new RSAEncryptor()

获取公钥

js
import axios from 'axios'

/**
 * 获取后端公钥
 * @returns {Promise<{publicKey: string, expireSeconds: number}>}
 */
export const getPublicKey = async () => {
  try {
    const response = await axios.get('/api/public-key')
    // 假设后端返回格式: { publicKey: '...', expireSeconds: 3600 }
    if (response.data && response.data.publicKey) {
      return response.data
    }
    throw new Error('获取公钥失败,返回格式不正确')
  } catch (error) {
    console.error('获取公钥出错:', error)
    throw error
  }
}

封装axios

js
import axios from 'axios'
import { rsaEncryptor } from './rsaEncrypt'
import { getPublicKey } from '../api/keyService'

// 创建axios实例
const service = axios.create({
  baseURL: import.meta.env.VITE_APP_BASE_API, // 从环境变量获取基础URL
  timeout: 10000
})

/**
 * 确保公钥已准备好
 */
const ensurePublicKeyReady = async () => {
  if (!rsaEncryptor.isPublicKeyValid()) {
    try {
      const { publicKey, expireSeconds } = await getPublicKey()
      rsaEncryptor.setPublicKey(publicKey, expireSeconds)
    } catch (error) {
      console.error('无法获取有效的公钥,加密功能将不可用')
      throw error
    }
  }
}

// 请求拦截器
service.interceptors.request.use(
  async (config) => {
    // 检查是否需要加密
    const needEncrypt = config.encrypt === true
    // 只对POST请求进行加密处理
    if (needEncrypt && config.method?.toLowerCase() === 'post' && config.data) {
      try {
        // 确保公钥可用
        await ensurePublicKeyReady()
        
        // 加密数据
        const encryptedData = rsaEncryptor.encrypt(config.data)
        
        if (encryptedData) {
          // 替换原始数据为加密后的数据
          config.data = {
            encrypted: true,
            data: encryptedData
          }
        } else {
          console.warn('加密失败,将发送原始数据')
        }
      } catch (error) {
        console.error('处理加密时出错:', error)
        // 根据实际需求决定是否继续发送请求
        // throw error; // 若加密失败则阻止请求发送
      }
    }
    
    // 删除自定义参数,避免被发送到服务器
    delete config.encrypt
    
    return config
  },
  (error) => {
    return Promise.reject(error)
  }
)

// 响应拦截器
service.interceptors.response.use(
  (response) => {
    return response.data
  },
  (error) => {
    console.error('请求错误:', error)
    return Promise.reject(error)
  }
)

export default service

请求示例

js
import request from '../utils/request'

/**
 * 用户登录 (需要加密)
 * @param {Object} data 登录信息 {username, password}
 */
export const login = (data) => {
  return request({
    url: '/user/login',
    method: 'post',
    data,
    encrypt: true // 指定需要加密
  })
}

/**
 * 获取用户信息 (不需要加密)
 */
export const getUserInfo = () => {
  return request({
    url: '/user/info',
    method: 'get'
  })
}

/**
 * 更新用户资料 (需要加密)
 * @param {Object} data 用户资料
 */
export const updateUserProfile = (data) => {
  return request({
    url: '/user/profile',
    method: 'post',
    data,
    encrypt: true // 指定需要加密
  })
}

在vue组件使用

vue
<template>
  <div class="login-page">
    <form @submit.prevent="handleLogin">
      <input v-model="username" type="text" placeholder="用户名" />
      <input v-model="password" type="password" placeholder="密码" />
      <button type="submit">登录</button>
    </form>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { login } from '../api/user'

const username = ref('')
const password = ref('')

const handleLogin = async () => {
  try {
    const response = await login({
      username: username.value,
      password: password.value
    })
    console.log('登录成功:', response)
    // 处理登录成功逻辑
  } catch (error) {
    console.error('登录失败:', error)
  }
}
</script>