Skip to content

API接口加密 (API Encryption)

API接口加密功能通过Servlet过滤器实现,支持前后端数据传输的全链路加密,采用RSA+AES混合加密方案,既保证了安全性又兼顾了性能。

配置说明

基础配置

application.yml 中配置API加密参数:

yaml
api-decrypt:
  # 功能开关
  enabled: true
  
  # 请求头中加密密钥的标识字段
  header-flag: "encrypt-key"
  
  # RSA公钥(用于加密响应中的AES密钥)
  public-key: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA..."
  
  # RSA私钥(用于解密请求中的AES密钥)
  private-key: "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC..."

配置项说明

配置项说明默认值
enabled是否启用API加密功能false
header-flag请求头中存放加密密钥的字段名encrypt-key
public-keyRSA公钥,用于加密响应中的AES密钥-
private-keyRSA私钥,用于解密请求中的AES密钥-

@ApiEncrypt 注解使用

基础用法

java
@RestController
@RequestMapping("/api/user")
public class UserController {
    
    // 普通接口(不加密)
    @GetMapping("/info")
    public Result<UserInfo> getUserInfo() {
        return Result.success(userService.getCurrentUser());
    }
    
    // 对响应进行加密
    @PostMapping("/login")
    @ApiEncrypt(response = true)
    public Result<LoginResponse> login(@RequestBody LoginRequest request) {
        // 请求自动解密,响应自动加密
        LoginResponse response = userService.login(request);
        return Result.success(response);
    }
    
    // 敏感数据接口
    @GetMapping("/profile")
    @ApiEncrypt(response = true)
    public Result<UserProfile> getUserProfile() {
        // 返回的用户详细信息会被加密
        UserProfile profile = userService.getUserProfile();
        return Result.success(profile);
    }
}

注解参数

参数类型默认值说明
responsebooleanfalse是否对响应进行加密

注意

  • 请求解密是自动的,只要请求头包含加密标识就会解密
  • 响应加密需要显式设置 response = true

加密流程详解

请求加密流程

mermaid
sequenceDiagram
    participant C as 客户端
    participant S as 服务端
    
    C->>C: 1. 生成32位AES密钥
    C->>C: 2. 用AES密钥加密请求体
    C->>C: 3. 对AES密钥进行Base64编码
    C->>C: 4. 用服务端RSA公钥加密Base64编码的AES密钥
    C->>S: 5. 发送请求(加密的请求体 + 请求头中的加密AES密钥)
    S->>S: 6. 用RSA私钥解密获得Base64编码的AES密钥
    S->>S: 7. 对AES密钥进行Base64解码
    S->>S: 8. 用AES密钥解密请求体
    S->>S: 9. 处理业务逻辑

响应加密流程

mermaid
sequenceDiagram
    participant S as 服务端
    participant C as 客户端
    
    S->>S: 1. 处理业务逻辑得到响应数据
    S->>S: 2. 生成新的32位AES密钥
    S->>S: 3. 用AES密钥加密响应体
    S->>S: 4. 对AES密钥进行Base64编码
    S->>S: 5. 用RSA公钥加密Base64编码的AES密钥
    S->>C: 6. 返回响应(加密的响应体 + 响应头中的加密AES密钥)
    C->>C: 7. 用RSA私钥解密获得Base64编码的AES密钥
    C->>C: 8. 对AES密钥进行Base64解码
    C->>C: 9. 用AES密钥解密响应体

前端对接实现

环境变量配置

在前端项目的 .env 文件中配置加密相关参数:

bash
# 接口加密功能开关(如需关闭 后端也必须对应关闭)
VITE_APP_API_ENCRYPT = 'true'

# 接口加密传输 RSA 公钥与后端解密私钥对应 如更换需前后端一同更换
VITE_APP_RSA_PUBLIC_KEY = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAK9s1Pbnn5W+l1hx3ukHLtevayF...'

# 接口响应解密 RSA 私钥与后端加密公钥对应 如更换需前后端一同更换
VITE_APP_RSA_PRIVATE_KEY = 'MIIBOwIBAAJBAIrZxEhzVAHKJm7BJpXIHWGU3sHJYgRiOOTw3Auj...'

⚠️ 安全提醒:

  • 以上为示例密钥,生产环境请使用您自己生成的密钥对
  • 密钥应存储在安全的配置中心,不要直接写在代码中
  • 定期轮换密钥以确保安全性

系统配置

SystemConfig 中配置安全选项:

typescript
export const SystemConfig = {
  security: {
    // 是否启用API加密(从环境变量读取)
    apiEncrypt: process.env.VITE_APP_API_ENCRYPT === 'true',
  },
  // 其他配置...
}

使用示例

1. 加密请求示例

typescript
import { http } from '@/composables/useHttp'

// 发送加密的登录请求
const login = async (loginData: LoginRequest) => {
  const [err, result] = await http.post<LoginResponse>('/api/auth/login', loginData, {
    header: { 
      isEncrypt: true  // 开启请求加密
    }
  })
  
  if (!err) {
    console.log('登录成功', result)
  } else {
    console.error('登录失败', err.message)
  }
}

// 发送加密的用户信息更新请求
const updateProfile = async (profileData: UserProfile) => {
  const [err] = await http.put<void>('/api/user/profile', profileData, {
    header: { 
      isEncrypt: true  // 开启请求加密
    }
  })
  
  if (!err) {
    console.log('更新成功')
  }
}

2. 接收加密响应

typescript
// 响应加密由后端 @ApiEncrypt(response = true) 注解控制
// 前端会自动检测响应头中的加密标识并解密

const getUserInfo = async () => {
  // 后端接口标注了 @ApiEncrypt(response = true),响应会被自动解密
  const [err, userInfo] = await http.get<UserInfo>('/api/user/info')
  
  if (!err) {
    console.log('用户信息', userInfo) // 已自动解密的数据
  }
}

核心实现原理

1. 请求加密处理

typescript
/**
 * 加密请求数据
 */
const encryptRequestData = (data: any, header: Record<string, any>) => {
  if (!SystemConfig.security?.apiEncrypt || !data) return data

  // 1. 生成32位AES密钥
  const aesKey = generateAesKey()
  
  // 2. 用后端RSA公钥加密AES密钥并放入请求头
  header[ENCRYPT_HEADER] = rsaEncrypt(encodeBase64(aesKey))

  // 3. 用AES密钥加密请求数据
  return typeof data === 'object'
    ? encryptWithAes(JSON.stringify(data), aesKey)
    : encryptWithAes(data, aesKey)
}

2. 响应解密处理

typescript
/**
 * 解密响应数据
 */
const decryptResponseData = (data: any, header: Record<string, any>): any => {
  if (!SystemConfig.security?.apiEncrypt) return data

  // 1. 从响应头获取加密的AES密钥
  const encryptKey = header[ENCRYPT_HEADER] || header[ENCRYPT_HEADER.toLowerCase()]
  if (!encryptKey) return data

  try {
    // 2. 用前端RSA私钥解密AES密钥
    const base64Str = rsaDecrypt(encryptKey)
    const aesKey = decodeBase64(base64Str)
    
    // 3. 用AES密钥解密响应数据
    const decryptedData = decryptWithAes(data, aesKey)
    return JSON.parse(decryptedData)
  } catch (error) {
    console.error('[响应解密失败]', error)
    throw new Error('响应数据解密失败')
  }
}

加密流程详解

请求加密流程

mermaid
sequenceDiagram
    participant F as 前端
    participant S as 后端
    
    F->>F: 1. 生成32位随机AES密钥
    F->>F: 2. 用AES密钥加密请求体数据
    F->>F: 3. 对AES密钥进行Base64编码
    F->>F: 4. 用后端RSA公钥加密Base64编码的AES密钥
    F->>S: 5. 发送请求(加密的请求体 + encrypt-key请求头)
    S->>S: 6. 用RSA私钥解密获得Base64编码的AES密钥
    S->>S: 7. Base64解码获得原始AES密钥
    S->>S: 8. 用AES密钥解密请求体
    S->>S: 9. 处理业务逻辑

响应加密流程

mermaid
sequenceDiagram
    participant S as 后端
    participant F as 前端
    
    S->>S: 1. 业务处理完成,准备响应数据
    S->>S: 2. 生成新的32位随机AES密钥
    S->>S: 3. 用AES密钥加密响应体数据
    S->>S: 4. 对AES密钥进行Base64编码
    S->>S: 5. 用RSA公钥加密Base64编码的AES密钥
    S->>F: 6. 返回响应(加密的响应体 + encrypt-key响应头)
    F->>F: 7. 用RSA私钥解密获得Base64编码的AES密钥
    F->>F: 8. Base64解码获得原始AES密钥
    F->>F: 9. 用AES密钥解密响应体
    F->>F: 10. 解析JSON数据供业务使用

密钥配置说明

前后端密钥对应关系

bash
# 后端配置(application.yml)
api-decrypt:
  # 响应加密公钥 - 用于加密响应中的AES密钥
  # 对应前端解密私钥
  public-key: "后端响应加密公钥"
  
  # 请求解密私钥 - 用于解密请求中的AES密钥  
  # 对应前端加密公钥
  private-key: "后端请求解密私钥"

# 前端配置(.env)
# 请求加密公钥 - 用于加密请求中的AES密钥
# 对应后端解密私钥
VITE_APP_RSA_PUBLIC_KEY = "前端请求加密公钥"

# 响应解密私钥 - 用于解密响应中的AES密钥
# 对应后端加密公钥  
VITE_APP_RSA_PRIVATE_KEY = "前端响应解密私钥"

密钥对应关系图

请求加密流程:
前端请求加密公钥 ←→ 后端请求解密私钥 (密钥对A)

响应加密流程:
后端响应加密公钥 ←→ 前端响应解密私钥 (密钥对B)

关键要点

  • 后端配置中的 publicKeyprivateKey 不是一对密钥
  • 需要生成两对独立的RSA密钥
    • 密钥对A:用于请求加密(前端公钥A + 后端私钥A)
    • 密钥对B:用于响应加密(后端公钥B + 前端私钥B)
  • 前端用公钥A加密请求,后端用私钥A解密请求
  • 后端用公钥B加密响应,前端用私钥B解密响应