前后端AES加密解密示例
方案优点缺点JSON 反序列化类型安全结构化数据处理自动类型转换需要创建DTO类需要配置反序列化字符串操作简单直接脆弱易出错丢失数据结构难以维护。
下面是一个完整的Vue3+TypeScript前端AES加密与Spring Boot后端AES解密的示例:
一、前端 Vue3 + TypeScript 代码
-
安装依赖:
bash
复制
下载
npm install crypto-js
-
创建加密工具文件
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
}
}
-
在组件中使用:
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 代码
-
添加依赖到
pom.xml:
xml
复制
下载
运行
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
-
创建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);
}
}
-
创建控制器
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());
}
}
}
-
配置跨域支持(可选)
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("*");
}
}
三、关键配置说明
-
AES参数一致性:
-
密钥:32字节(256位)
1234567890abcdef1234567890abcdef -
IV:16字节(128位)
abcdef1234567890 -
模式:CBC
-
填充:PKCS7(前端)/ PKCS5(后端实际等效)
-
-
数据传输流程:
图表
代码
下载
后端前端后端前端1. JSON序列化数据2. AES-CBC加密3. POST { data: "加密Base64" }4. 检查X-Encrypted头5. Base64解码6. AES解密7. 处理原始数据8. 返回响应
-
安全注意事项:
-
生产环境应将密钥存储在环境变量/配置中心,而非代码中
-
考虑使用HTTPS防止中间人攻击
-
可添加请求签名防止重放攻击
-
Java需确保JCE无限强度权限(JDK8需手动安装)
-
四、测试流程
-
启动Spring Boot应用
-
运行Vue应用(默认端口5173)
-
点击前端"发送加密数据"按钮
-
后端控制台应打印解密后的原始JSON数据
注意:如果遇到Java解密失败,检查:
密钥/IV长度是否正确
是否添加了JCE无限强度策略文件
前端加密后的Base64字符串是否完整传输
确保前后端都使用相同的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);
注意:这种方法非常脆弱,因为:
-
如果字符串本身包含双引号会被破坏
-
无法正确处理嵌套数据结构
-
失去类型安全和结构化优势
推荐方案总结
| 方案 | 优点 | 缺点 |
|---|---|---|
| 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 加密配置文件:
-
添加依赖:
xml:pom.xml
复制
下载
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
-
加密密钥:
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(加密后的字符串)
-
配置
application.yml:
yaml
复制
下载
aes:
key: ENC(密文1) # 替换为加密后的值
iv: ENC(密文2)
jasypt:
encryptor:
password: ${JASYPT_PASSWORD:} # 从环境变量获取主密码
-
启动应用:
bash
复制
下载
export JASYPT_PASSWORD=master_password java -jar your-app.jar
3. 密钥管理服务(高级方案)
适用场景:大型企业级应用,最高安全要求
使用 HashiCorp Vault
图表
代码
下载
应用
Vault Agent
HashiCorp Vault
安全存储
-
添加依赖:
xml:pom.xml
复制
下载
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-vault-config</artifactId>
<version>3.1.0</version>
</dependency>
-
配置
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
-
修改
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;
}
}
-
在Vault中存储密钥:
bash
复制
下载
vault kv put secret/my-app aes.key="prod_key" aes.iv="prod_iv"
云服务商方案
| 云平台 | 服务 | 特点 |
|---|---|---|
| AWS | Secrets Manager | 自动轮换密钥,与IAM集成 |
| Azure | Key Vault | 硬件安全模块(HSM)支持 |
| GCP | Secret Manager | 版本控制,审计日志 |
4. 硬件安全模块(HSM)(最高安全)
适用场景:金融、政府等高安全要求场景
图表
代码
下载
硬件安全模块应用硬件安全模块应用加密请求(明文)密文解密请求(密文)明文
实现方式:
-
使用JCA(Java Cryptography Architecture)接口
-
集成HSM厂商提供的SDK
-
密钥永不离开HSM设备
前端安全特别注意事项
重要:前端无法完全隐藏密钥!所有客户端代码都是公开的。解决方案:
-
使用非对称加密:
图表
代码
下载
公钥加密
私钥解密
前端
后端
-
前端:RSA/ECC加密对称密钥
-
后端:用私钥解密获取对称密钥
-
-
代理层解决方案:
图表
代码
下载
注入密钥
客户端
安全代理
前端
后端
-
通过安全代理动态注入密钥
-
定期轮换密钥
-
-
短期令牌方案:
-
后端生成短期有效的加密令牌
-
前端使用时提交令牌获取临时密钥
-
密钥管理最佳实践
-
密钥轮换:
图表
代码
下载
2023-01-012023-02-012023-03-012023-04-012023-05-012023-06-01生产密钥每日自动生成新密钥主密钥数据密钥密钥轮换周期
-
最小权限原则:
-
应用只能访问必要的密钥
-
不同环境使用不同密钥
-
-
审计与监控:
-
记录所有密钥访问
-
异常访问实时告警
-
-
密钥存储:
-
禁用开发密钥在生产环境
-
使用密钥版本控制
-
定期备份密钥(加密存储)
-
推荐实施路径
图表
代码
下载
是
否
是
否
项目启动
环境变量
安全要求提高?
配置文件加密
保持
企业级需求?
密钥管理服务
HSM
根据项目规模和安全要求:
-
初创项目:环境变量 + 严格访问控制
-
中型项目:Jasypt加密配置 + 密钥轮换
-
大型企业:Vault/AWS KMS + 审计日志
-
金融级:HSM + 硬件隔离
始终记住:安全是一个过程,不是一次性配置。定期审计密钥使用情况,及时更新安全措施至关重要。
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐

所有评论(0)