前后端AES加密解密示例

下面是一个完整的Vue3+TypeScript前端AES加密与Spring Boot后端AES解密的示例:

一、前端 Vue3 + TypeScript 代码

  1. 安装依赖:

bash

复制

下载

npm install crypto-js
  1. 创建加密工具文件 src/utils/crypto.ts

typescript

复制

下载

import CryptoJS from 'crypto-js'

// AES加密配置
const AES_KEY = CryptoJS.enc.Utf8.parse('1234567890abcdef1234567890abcdef') // 32位密钥
const AES_IV = CryptoJS.enc.Utf8.parse('abcdef1234567890') // 16位偏移量

/**
 * AES加密
 * @param data 待加密数据
 * @returns 加密后的Base64字符串
 */
export function encryptAES(data: any): string {
  const dataStr = JSON.stringify(data)
  const encrypted = CryptoJS.AES.encrypt(dataStr, AES_KEY, {
    iv: AES_IV,
    mode: CryptoJS.mode.CBC,
    padding: CryptoJS.pad.Pkcs7
  })
  return encrypted.toString()
}

/**
 * 发送加密数据
 * @param url 请求地址
 * @param data 请求数据
 */
export async function sendEncryptedData(url: string, data: any) {
  const encrypted = encryptAES(data)
  
  try {
    const response = await fetch(url, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-Encrypted': 'true' // 添加加密标识头
      },
      body: JSON.stringify({ data: encrypted })
    })
    
    if (!response.ok) throw new Error('Network response was not ok')
    return await response.json()
  } catch (error) {
    console.error('Encrypted request failed:', error)
    throw error
  }
}
  1. 在组件中使用:

vue

复制

下载

<script setup lang="ts">
import { ref } from 'vue'
import { sendEncryptedData } from '@/utils/crypto'

const formData = ref({
  username: 'testUser',
  password: 'secret123',
  sensitiveInfo: 'This is confidential'
})

async function submitData() {
  try {
    const response = await sendEncryptedData(
      'http://your-backend-api.com/encrypted-endpoint',
      formData.value
    )
    console.log('Server response:', response)
  } catch (e) {
    console.error('Submission error:', e)
  }
}
</script>

<template>
  <div>
    <button @click="submitData">发送加密数据</button>
  </div>
</template>

二、后端 Spring Boot 代码

  1. 添加依赖到 pom.xml

xml

复制

下载

运行

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
  1. 创建AES工具类 src/main/java/com/example/demo/util/AesUtil.java

java

复制

下载

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

public class AesUtil {

    private static final String KEY = "1234567890abcdef1234567890abcdef"; // 32字符
    private static final String IV = "abcdef1234567890"; // 16字符
    private static final String ALGORITHM = "AES/CBC/PKCS5Padding";

    public static String decrypt(String encryptedData) throws Exception {
        byte[] encryptedBytes = Base64.getDecoder().decode(encryptedData);
        
        SecretKeySpec secretKey = new SecretKeySpec(KEY.getBytes(), "AES");
        IvParameterSpec ivParameterSpec = new IvParameterSpec(IV.getBytes());
        
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
        
        byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
        return new String(decryptedBytes);
    }
}
  1. 创建控制器 src/main/java/com/example/demo/controller/EncryptedController.java

java

复制

下载

import com.example.demo.util.AesUtil;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

@RestController
public class EncryptedController {

    @PostMapping("/encrypted-endpoint")
    public Map<String, Object> handleEncryptedRequest(
            @RequestBody Map<String, String> payload,
            @RequestHeader(value = "X-Encrypted", required = false) String encryptedFlag) {
        
        try {
            if (!"true".equalsIgnoreCase(encryptedFlag)) {
                throw new SecurityException("Unencrypted request not allowed");
            }

            String encryptedData = payload.get("data");
            String decryptedJson = AesUtil.decrypt(encryptedData);
            
            // 将解密后的JSON转为Map
            // 实际项目中应使用ObjectMapper解析为具体对象
            System.out.println("Decrypted data: " + decryptedJson);
            
            return Map.of(
                "status", "success",
                "decryptedData", decryptedJson
            );
        } catch (Exception e) {
            throw new SecurityException("Decryption failed: " + e.getMessage());
        }
    }
}
  1. 配置跨域支持(可选)src/main/java/com/example/demo/config/WebConfig.java

java

复制

下载

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("http://localhost:5173") // 前端地址
                .allowedMethods("*");
    }
}

三、关键配置说明

  1. AES参数一致性

    • 密钥:32字节(256位)1234567890abcdef1234567890abcdef

    • IV:16字节(128位)abcdef1234567890

    • 模式:CBC

    • 填充:PKCS7(前端)/ PKCS5(后端实际等效)

  2. 数据传输流程

    图表

    代码

    下载

    后端前端后端前端1. JSON序列化数据2. AES-CBC加密3. POST { data: "加密Base64" }4. 检查X-Encrypted头5. Base64解码6. AES解密7. 处理原始数据8. 返回响应

  3. 安全注意事项

    • 生产环境应将密钥存储在环境变量/配置中心,而非代码中

    • 考虑使用HTTPS防止中间人攻击

    • 可添加请求签名防止重放攻击

    • Java需确保JCE无限强度权限(JDK8需手动安装)

四、测试流程

  1. 启动Spring Boot应用

  2. 运行Vue应用(默认端口5173)

  3. 点击前端"发送加密数据"按钮

  4. 后端控制台应打印解密后的原始JSON数据

注意:如果遇到Java解密失败,检查:

  1. 密钥/IV长度是否正确

  2. 是否添加了JCE无限强度策略文件

  3. 前端加密后的Base64字符串是否完整传输

  4. 确保前后端都使用相同的AES模式(CBC)

这个方案实现了前后端之间的安全数据传输,保护敏感信息在传输过程中的机密性。

在后端解密后出现额外的双引号,是因为前端加密时对数据进行了 JSON 序列化处理。以下是解决方案和修改后的代码:

问题分析

当前流程:

图表

代码

下载

前端对象

JSON.stringify

AES加密

后端接收

AES解密

JSON字符串带双引号

我们需要将解密后的 JSON 字符串解析为 Java 对象

后端修改方案

1. 添加 Jackson 依赖(如果尚未添加)

pom.xml:

xml

复制

下载

运行

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.15.2</version> <!-- 使用最新版本 -->
</dependency>
2. 修改控制器代码

EncryptedController.java:

java

复制

下载

import com.example.demo.util.AesUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

@RestController
public class EncryptedController {

    private final ObjectMapper objectMapper = new ObjectMapper();

    // 创建接收数据的DTO类
    public static class EncryptedDataDTO {
        private String username;
        private String password;
        private String sensitiveInfo;
        
        // 必须有无参构造函数
        public EncryptedDataDTO() {}
        
        // Getters and Setters
        public String getUsername() { return username; }
        public void setUsername(String username) { this.username = username; }
        public String getPassword() { return password; }
        public void setPassword(String password) { this.password = password; }
        public String getSensitiveInfo() { return sensitiveInfo; }
        public void setSensitiveInfo(String sensitiveInfo) { this.sensitiveInfo = sensitiveInfo; }
    }

    @PostMapping("/encrypted-endpoint")
    public Map<String, Object> handleEncryptedRequest(
            @RequestBody Map<String, String> payload,
            @RequestHeader(value = "X-Encrypted", required = false) String encryptedFlag) {
        
        try {
            if (!"true".equalsIgnoreCase(encryptedFlag)) {
                throw new SecurityException("Unencrypted request not allowed");
            }

            String encryptedData = payload.get("data");
            String decryptedJson = AesUtil.decrypt(encryptedData);
            
            // 将JSON字符串解析为Java对象
            EncryptedDataDTO data = objectMapper.readValue(decryptedJson, EncryptedDataDTO.class);
            
            System.out.println("用户名: " + data.getUsername());
            System.out.println("密码: " + data.getPassword());
            System.out.println("敏感信息: " + data.getSensitiveInfo());
            
            return Map.of(
                "status", "success",
                "username", data.getUsername(),
                "received", true
            );
        } catch (Exception e) {
            throw new SecurityException("处理失败: " + e.getMessage());
        }
    }
}

替代方案:直接操作字符串(不推荐)

如果你坚持要获取原始字符串(不推荐,因为会丢失数据结构):

java

复制

下载

// 在解密后添加字符串处理
String decryptedJson = AesUtil.decrypt(encryptedData);

// 移除两端的双引号(脆弱的方法,仅当确定字符串格式时使用)
if (decryptedJson.startsWith("\"") && decryptedJson.endsWith("\"")) {
    decryptedJson = decryptedJson.substring(1, decryptedJson.length() - 1);
}

System.out.println("处理后的字符串: " + decryptedJson);

注意:这种方法非常脆弱,因为:

  1. 如果字符串本身包含双引号会被破坏

  2. 无法正确处理嵌套数据结构

  3. 失去类型安全和结构化优势

推荐方案总结

方案优点缺点
JSON 反序列化类型安全
结构化数据处理
自动类型转换
需要创建DTO类
需要配置反序列化
字符串操作简单直接脆弱易出错
丢失数据结构
难以维护

完整流程说明

图表

代码

下载

后端前端后端前端1. 准备数据对象2. JSON.stringify(对象)3. AES加密字符串4. 发送 { data: "加密数据" }5. AES解密获得JSON字符串6. ObjectMapper解析JSON7. 获得Java对象(无多余引号)8. 返回处理结果

前端配合建议

确保前端发送的是有效的JSON对象:

typescript

复制

下载

// 正确 - 对象会被正确序列化
const data = { 
  username: 'test',
  password: 'secret'
}

// 错误 - 字符串会被双重序列化
const data = JSON.stringify({ ... }) // 不要这样做!

使用推荐方案后,后端将能直接获取到:

java

复制

下载

data.getUsername() // = "test" (字符串,无额外引号)
data.getPassword() // = "secret" (字符串,无额外引号)

这样处理后,你将获得干净的结构化数据,无需手动处理双引号问题。

在生产环境中安全存储加密密钥至关重要。以下是不将密钥硬编码在代码中的推荐方案,按安全性从低到高排序:

1. 环境变量(基础方案)

适用场景:中小型项目,快速实现

前端实现(Vue3)

typescript:src/utils/crypto.ts

复制

下载

// 从环境变量读取密钥(注意:前端环境变量是公开的!)
const AES_KEY = CryptoJS.enc.Utf8.parse(import.meta.env.VITE_AES_KEY || 'default_key_32bytes_xxxxxxxxxxxx');
const AES_IV = CryptoJS.enc.Utf8.parse(import.meta.env.VITE_AES_IV || 'default_iv_16bytes_xx');

.env 文件(添加到 .gitignore):

env:.env.local

复制

下载

VITE_AES_KEY=1234567890abcdef1234567890abcdef
VITE_AES_IV=abcdef1234567890
后端实现(Spring Boot)

application.yml

yaml

复制

下载

aes:
  key: ${AES_KEY:1234567890abcdef1234567890abcdef} # 默认值仅用于开发
  iv: ${AES_IV:abcdef1234567890}

修改 AesUtil.java

java

复制

下载

@Component
public class AesUtil {
    
    private final String key;
    private final String iv;
    
    @Autowired
    public AesUtil(
        @Value("${aes.key}") String key,
        @Value("${aes.iv}") String iv
    ) {
        this.key = key;
        this.iv = iv;
    }
    
    public String decrypt(String encryptedData) throws Exception {
        // 使用 this.key 和 this.iv
    }
}

启动方式

bash

复制

下载

# Linux/macOS
export AES_KEY=prod_key_32bytes_xxxxxxxxxxxx
export AES_IV=prod_iv_16bytes_xxxx
java -jar your-app.jar

# Windows
set AES_KEY=prod_key_32bytes_xxxxxxxxxxxx
set AES_IV=prod_iv_16bytes_xxxx
java -jar your-app.jar

2. 配置文件加密(中级方案)

适用场景:需要更安全的配置存储

使用 jasypt 加密配置文件:

  1. 添加依赖:

xml:pom.xml

复制

下载

<dependency>
    <groupId>com.github.ulisesbocchio</groupId>
    <artifactId>jasypt-spring-boot-starter</artifactId>
    <version>3.0.5</version>
</dependency>
  1. 加密密钥:

bash

复制

下载

# 安装 jasypt (需要 Java)
java -cp jasypt-1.9.3.jar org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI \
  input="real_secret_key" password=master_password algorithm=PBEWithMD5AndDES

# 输出:ENC(加密后的字符串)
  1. 配置 application.yml

yaml

复制

下载

aes:
  key: ENC(密文1)   # 替换为加密后的值
  iv: ENC(密文2)

jasypt:
  encryptor:
    password: ${JASYPT_PASSWORD:} # 从环境变量获取主密码
  1. 启动应用:

bash

复制

下载

export JASYPT_PASSWORD=master_password
java -jar your-app.jar

3. 密钥管理服务(高级方案)

适用场景:大型企业级应用,最高安全要求

使用 HashiCorp Vault

图表

代码

下载

应用

Vault Agent

HashiCorp Vault

安全存储

  1. 添加依赖:

xml:pom.xml

复制

下载

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-vault-config</artifactId>
    <version>3.1.0</version>
</dependency>
  1. 配置 bootstrap.yml

yaml

复制

下载

spring:
  cloud:
    vault:
      uri: https://vault.example.com:8200
      authentication: TOKEN
      token: ${VAULT_TOKEN}
      kv:
        enabled: true
        backend: secret
        application-name: my-app
  1. 修改 AesUtil

java

复制

下载

@Configuration
public class AesUtil {
    
    private final String key;
    private final String iv;
    
    public AesUtil(
        @Value("${aes.key}") String key,
        @Value("${aes.iv}") String iv
    ) {
        this.key = key;
        this.iv = iv;
    }
}
  1. 在Vault中存储密钥:

bash

复制

下载

vault kv put secret/my-app aes.key="prod_key" aes.iv="prod_iv"
云服务商方案
云平台服务特点
AWSSecrets Manager自动轮换密钥,与IAM集成
AzureKey Vault硬件安全模块(HSM)支持
GCPSecret Manager版本控制,审计日志

4. 硬件安全模块(HSM)(最高安全)

适用场景:金融、政府等高安全要求场景

图表

代码

下载

硬件安全模块应用硬件安全模块应用加密请求(明文)密文解密请求(密文)明文

实现方式:

  1. 使用JCA(Java Cryptography Architecture)接口

  2. 集成HSM厂商提供的SDK

  3. 密钥永不离开HSM设备

前端安全特别注意事项

重要:前端无法完全隐藏密钥!所有客户端代码都是公开的。解决方案:

  1. 使用非对称加密

    图表

    代码

    下载

    公钥加密

    私钥解密

    前端

    后端

    • 前端:RSA/ECC加密对称密钥

    • 后端:用私钥解密获取对称密钥

  2. 代理层解决方案

    图表

    代码

    下载

    注入密钥

    客户端

    安全代理

    前端

    后端

    • 通过安全代理动态注入密钥

    • 定期轮换密钥

  3. 短期令牌方案

    • 后端生成短期有效的加密令牌

    • 前端使用时提交令牌获取临时密钥

密钥管理最佳实践

  1. 密钥轮换

    图表

    代码

    下载

    2023-01-012023-02-012023-03-012023-04-012023-05-012023-06-01生产密钥每日自动生成新密钥主密钥数据密钥密钥轮换周期

  2. 最小权限原则

    • 应用只能访问必要的密钥

    • 不同环境使用不同密钥

  3. 审计与监控

    • 记录所有密钥访问

    • 异常访问实时告警

  4. 密钥存储

    • 禁用开发密钥在生产环境

    • 使用密钥版本控制

    • 定期备份密钥(加密存储)

推荐实施路径

图表

代码

下载

项目启动

环境变量

安全要求提高?

配置文件加密

保持

企业级需求?

密钥管理服务

HSM

根据项目规模和安全要求:

  1. 初创项目:环境变量 + 严格访问控制

  2. 中型项目:Jasypt加密配置 + 密钥轮换

  3. 大型企业:Vault/AWS KMS + 审计日志

  4. 金融级:HSM + 硬件隔离

始终记住:安全是一个过程,不是一次性配置。定期审计密钥使用情况,及时更新安全措施至关重要。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值