目录
一、后端实现
二、前端实现(Vue2)
三、补充
1.增强安全措施
四、最后说明
步骤大致如下:
- 后端生成RSA密钥对,提供公钥接口。
- 前端请求公钥,生成随机AES密钥和IV。
- 用RSA公钥加密AES密钥,用AES密钥加密数据。
- 发送包含加密后的AES密钥和数据的请求体。
- 后端用RSA私钥解密AES密钥,再用AES密钥解密数据。
- 使用注解和拦截器自动处理解密过程。
需要确保每个步骤都正确实现,特别是加密模式、填充方式以及编码解码的一致性,避免因配置不同导致解密失败。有什么没加入的在评论区艾特我,我进行补充
一、后端实现
- 新增AES工具类:
public class AesUtils {public static String encrypt(String data, String key, String iv) throws Exception {SecretKeySpec keySpec = new SecretKeySpec(Base64.getDecoder().decode(key), "AES");IvParameterSpec ivSpec = new IvParameterSpec(Base64.getDecoder().decode(iv));Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);byte[] encrypted = cipher.doFinal(data.getBytes());return Base64.getEncoder().encodeToString(encrypted);}public static String decrypt(String data, String key, String iv) throws Exception {byte[] keyBytes = Base64.getDecoder().decode(key);byte[] ivBytes = Base64.getDecoder().decode(iv);byte[] encryptedBytes = Base64.getDecoder().decode(data);Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);return new String(cipher.doFinal(encryptedBytes));}
}
- 修改请求处理切面:
/*** 解密切面*/
@ControllerAdvice
public class DecryptAdvice extends RequestBodyAdviceAdapter {private final RsaKeyManager keyManager;public DecryptAdvice(RsaKeyManager keyManager) {this.keyManager = keyManager;}@Overridepublic boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {return methodParameter.hasMethodAnnotation(NeedDecrypt.class);}@Overridepublic HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage,MethodParameter parameter,Type targetType,Class<? extends HttpMessageConverter<?>> converterType) throws IOException {try {String encryptedBody = new String(inputMessage.getBody().readAllBytes());JSONObject json = JSONObject.parseObject(encryptedBody);// 解密 AES 密钥String encryptedAesKey = json.getString("encryptedKey");String aesKey = RsaUtils.decryptByPrivateKey(encryptedAesKey, keyManager.getPrivateKey());// 解密数据String decryptedData = AesUtils.decrypt(json.getString("encryptedData"),aesKey,json.getString("iv"));return new DecryptedHttpInputMessage(new ByteArrayInputStream(decryptedData.getBytes()),inputMessage.getHeaders());} catch (Exception e) {throw new RuntimeException("解密失败", e);}}
}
- 新增注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NeedDecrypt {
}
- 新增公私钥管理类
/*** 生成RSA*/
public class RsaKeyManager {Logger log = LoggerFactory.getLogger(RsaKeyManager.class);private final RedisService redisService;// Redis 键名private static final String PUBLIC_KEY = "rsa:public";private static final String PRIVATE_KEY = "rsa:private";public RsaKeyManager(RedisService redisService) {this.redisService = redisService;}/*** 初始化密钥(全局唯一)*/@PostConstructpublic void initKeyPair() throws Exception {// 使用 SETNX 原子操作确保只有一个服务生成密钥Boolean isAbsent = redisService.setIfAbsent(PUBLIC_KEY, "");if (Boolean.TRUE.equals(isAbsent)) {KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");generator.initialize(2048);KeyPair keyPair = generator.generateKeyPair();// 存储密钥redisService.set(PUBLIC_KEY,Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded()));redisService.set(PRIVATE_KEY,Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded()));log.info("---------------------------初始化RSA秘钥---------------------------");}}/*** 获取公钥*/public String getPublicKey() {Object publicKey = redisService.get(PUBLIC_KEY);return Objects.isNull(publicKey)?null:publicKey.toString();}/*** 获取私钥*/public String getPrivateKey() {Object privateKey = redisService.get(PRIVATE_KEY);return Objects.isNull(privateKey)?null:privateKey.toString();}
}
- 新增DecryptedHttpInputMessage
public class DecryptedHttpInputMessage implements HttpInputMessage {private final InputStream body;private final HttpHeaders headers;public DecryptedHttpInputMessage(InputStream body, HttpHeaders headers) {this.body = body;this.headers = headers;}@Overridepublic InputStream getBody() throws IOException {return this.body;}@Overridepublic HttpHeaders getHeaders() {return this.headers;}
}
- 新增获取公钥接口
@RestController
@RequestMapping("/rsa")
public class RSAController {@Autowiredprivate RsaKeyManager rsaKeyManager;/*** 获取公钥* @return 结果*/@GetMapping("/publicKey")public R<String> getPublicKey() {String publicKey = rsaKeyManager.getPublicKey();return R.ok(publicKey);}}
二、前端实现(Vue2)
- 安装新依赖:
npm install crypto-js
- 加密工具(src/utils/crypto.js):
getPublicKey 为请求公钥的接口,需要按照自己请求方式去获取
import JSEncrypt from 'jsencrypt'
import CryptoJS from 'crypto-js'
import { getPublicKey } from '../request/api/auth'// 初始化公钥
export async function initPublicKey() { try {const res = await getPublicKey()return formatPublicKey(res.data)} catch (error) {console.error('公钥获取失败:', error)throw new Error('安全模块初始化失败')}
}// 生成AES密钥
export function generateAesKey() {const key = CryptoJS.lib.WordArray.random(32)const iv = CryptoJS.lib.WordArray.random(16)return {key: CryptoJS.enc.Base64.stringify(key),iv: CryptoJS.enc.Base64.stringify(iv)}
}// AES加密
export function aesEncrypt(data, key, iv) {const encrypted = CryptoJS.AES.encrypt(JSON.stringify(data),CryptoJS.enc.Base64.parse(key), { iv: CryptoJS.enc.Base64.parse(iv),mode: CryptoJS.mode.CBC,padding: CryptoJS.pad.Pkcs7})return encrypted.toString()
}// 格式化公钥
function formatPublicKey(rawKey) {return `-----BEGIN PUBLIC KEY-----\n${wrapKey(rawKey)}\n-----END PUBLIC KEY-----`
}// 每64字符换行
function wrapKey(key) {return key.match(/.{1,64}/g).join('\n')
}
- 修改请求拦截器:
service.interceptors.request.use(async config => {if (config.needEncrypt) {await initPublicKey(service)// 生成AES密钥const aes = generateAesKey()// 加密数据const encryptedData = aesEncrypt(config.data, aes.key, aes.iv)// 加密AES密钥const encryptor = new JSEncrypt()encryptor.setPublicKey(publicKey)const encryptedKey = encryptor.encrypt(aes.key)// 构造请求体config.data = {encryptedKey: encryptedKey,encryptedData: encryptedData,iv: aes.iv}}return config
})
三、补充
- 后端需要加密的接口示例
@PostMapping("/secure-data")
@NeedDecrypt
public String handleSecureData(@RequestBody Map<String, Object> decryptedData) {return "Decrypted data: " + decryptedData.toString();
}
- 请求结构体
{"encryptedKey": "RSA加密后的AES密钥","encryptedData": "AES加密后的数据","iv": "Base64编码的IV"
}
1.增强安全措施
-
密钥时效性:
// 前端每次请求生成新密钥
const aes = generateAesKey()
-
完整性校验:
// 后端解密后可添加HMAC校验
String hmac = json.getString("hmac");
if(!verifyHMAC(decryptedData, hmac, aesKey)) {throw new SecurityException("Data tampered");
}
-
防御重放攻击:
// 前端添加时间戳和随机数
config.data.timestamp = Date.now()
config.data.nonce = Math.random().toString(36).substr(2)
四、最后说明
该方案相比纯RSA加密有以下优势:
-
性能提升:AES加密大数据效率比RSA高1000倍以上
-
前向安全性:每次请求使用不同AES密钥
-
安全性增强:CBC模式+随机IV避免模式分析攻击
实际部署时需注意:
-
使用HTTPS传输加密后的数据
-
定期轮换RSA密钥对
-
对敏感接口添加频率限制
-
在网关层实现解密拦截器(而非应用层)