前言
大概2个多月前,帮一个同事一起解决使用AES和RSA非对称加密算法来对数据进行解密,需要写一个切面,然后在controller层的接口上直接添加注解,就可以对加密的数据进行解密。
由于自己之前从未涉及到这块,也一直觉得切面是难啃的硬骨头,而这次经历让我对切面有了更深的理解,忽然直接切面在我面前也如猪蹄一样香,被我分崩离析了。
所以这篇博客,涉及到2个技术点:
- AOP的around通知
- AES+RSA非对称加密
当然里面的切面是从大神那里复制过来,然后自己也写了demo,但是复制过来之后并不能直接用,自己也通过不断地修改,最终实现了对加密数据地解密,解密结果再加密,以及对原数据进行加密。
我把项目详细的内容都粘贴出来了,本来是想只粘贴切面和加解密的工具类,剩下的传到github上,但是中途出了差错,切分支的时候不小心把东西全部整没了,还好还有class文件,否则我得花更多的时间恢复,这也就是为啥我类文件上的注释是2020-11-09的时间。
复制起来确实麻烦,所以我还是把github的地址粘贴出来:
https://github.com/fanhuifangGit/annotation-test.git
看着我这么辛苦的份上希望留下你的足迹和star!!!【此处应该有表情】
(这个小的demo也差不多断断续续的写了一个星期了,也算是小有收获吧!!!)
目录结构
pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.fanhf</groupId>
<artifactId>annotation-test</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>annotation-test</name>
<description>Demo project for Spring Boot</description>
<dependencies>
<!-- spring-boot-starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- springboot web(MVC)-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.70</version>
</dependency>
<!-- springboot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--lombok插件 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- thymeleaf模板 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.2</version>
</dependency>
<!-- Base64编码需要 -->
<dependency>
<groupId>org.apache.directory.studio</groupId>
<artifactId>org.apache.commons.codec</artifactId>
<version>1.8</version>
</dependency>
<!--提供更多的加密、填充方式-->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk16</artifactId>
<version>1.46</version>
</dependency>
<!--aop 面向切面-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>
<!--构建工具-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<finalName>${project.artifactId}</finalName>
<outputDirectory>../package</outputDirectory>
</configuration>
</plugin>
<!-- 跳过启动测试 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
</plugins>
</build>
</project>
配置文件:application.properties
#内置Tomcat容器配置
server.port = 8882
#基础配置
#选择配置分支,先读取系统环境变量,如果没有则默认值为 dev
spring.profiles.active = ${BASE_ADMIN:dev}
spring.application.name = annotation-test
启动类:AnnotationTestApplication
package com.fanhf.annotationtest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class AnnotationTestApplication {
private static final Logger log = LoggerFactory.getLogger(AnnotationTestApplication.class);
public AnnotationTestApplication() {}
public static void main(String[] args) {
SpringApplication.run(AnnotationTestApplication.class, args);
log.info("启动成功!!!!!!");
}
}
解密注解:Decrypt
package com.fanhf.annotationtest.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Decrypt {
}
加密注解:Encrypt
package com.fanhf.annotationtest.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Encrypt {
}
bean:
EncryptBean
package com.fanhf.annotationtest.bean;
/**
* @author fanhf
* @Description 加密后的bean字段
* @date 2020-11-09 14:06
*/
public class EncryptBean {
private String aesData;
private String rsaData;
public String getAesData() {
return aesData;
}
public void setAesData(String aesData) {
this.aesData = aesData;
}
public String getRsaData() {
return rsaData;
}
public void setRsaData(String rsaData) {
this.rsaData = rsaData;
}
}
UserBean
package com.fanhf.annotationtest.bean;
/**
* @author fanhf
* @Description 要加密的bean类
* @date 2020-11-09 12:43
*/
public class UserBean {
private String passwd;
private String name;
private Integer age;
public String getPasswd() {
return passwd;
}
public void setPasswd(String passwd) {
this.passwd = passwd;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
Result
package com.fanhf.annotationtest.bean;
import lombok.Data;
import java.io.Serializable;
/**
* @author fanhf
* @Description 结果返回的统一对象
* @date 2020-11-09 12:45
*/
@Data
public class Result<T> implements Serializable {
/**
* 通信数据
*/
private T data;
/**
* 通过静态方法获取实例
*/
public static <T> Result<T> of(T data) {
return new Result<>(data);
}
@Deprecated
public Result() {
}
private Result(T data) {
this.data = data;
}
}
controller控制层
package com.fanhf.annotationtest.controller;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fanhf.anotation.Decrypt;
import com.fanhf.anotation.Encrypt;
import com.fanhf.result.Result;
import com.fanhf.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
/**
* 我写了4个接口,前2个接口是对应切面SafetyAspect
* 后2个接口是对应SafetyAspectPlus
*/
/**
* 1、对密码加密
*/
@PostMapping(value = "/encrypt")
@Encrypt
public JSONObject encrypt(EncryptBean encryptBean) {
return (JSONObject) JSONObject.toJSON(encryptBean);
}
/**POST
* 2、对密码解密
* 解密之后是用userBean来接
*/
@PostMapping(value = "/decrypt")
@Decrypt
public JSONObject decrypt(UserBean userBean) {
return (JSONObject) JSONObject.toJSON(userBean);
}
/**POST
* 3、对密码解密后处理后再加密
* 解密之后是用userBean来接
*/
@PostMapping(value = "/ende")
@Decrypt
@Encrypt
public Result<UserBean> ende(UserBean userBean) {
UserBean userBeanNew = userService.updatePasswd(userBean);
return Result.of(userBeanNew);
}
/**
* 4、加解密
* @date 2020/11/6 17:58
* @return JSONObject
* 说明:这个接口是无法正常放回加密后的数据,原因是参数和返回值不一致
**/
@PostMapping(value = "/endeplus")
@Decrypt
@Encrypt
public JSONObject endeplus(UserBean userBean) {
UserBean userBeanNew = userService.updatePasswd(userBean);
return (JSONObject) JSONObject.toJSON(userBeanNew);
}
}
UserService业务层
package com.fanhf.annotationtest.service;
import com.fanhf.controller.UserBean;
import org.springframework.stereotype.Service;
@Service
public class UserService {
public UserBean updatePasswd(UserBean userBean) {
userBean.setPasswd("@"+userBean.getPasswd()+"@");
userBean.setAge(userBean.getAge()+1);
userBean.setName(userBean.getName()+"AOP");
return userBean;
}
}
说明:我的这个service不涉及到数据库,但在真实地业务场景下应该是需要将解密后的数据进行处理,处理完之后再对数据库进行修改,之后再将处理的数据二次加密再返回给前端。我这里就简化了。
再来看看AES的加密工具类:AesUtil
package com.fanhf.annotationtest.utils;
import org.apache.tomcat.util.codec.binary.Base64;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Random;
/**
* AES加、解密算法工具类
*/
public class AesUtil {
/**
* 加密算法AES
*/
private static final String KEY_ALGORITHM = "AES";
/**
* key的长度,Wrong key size: must be equal to 128, 192 or 256
* 传入时需要16、24、36
*/
private static final Integer KEY_LENGTH = 16 * 8;
/**
* 算法名称/加密模式/数据填充方式
* 默认:AES/ECB/PKCS5Padding
*/
private static final String ALGORITHMS = "AES/ECB/PKCS5Padding";
/**
* 后端AES的key,由静态代码块赋值
*/
public static String key;
/**
* 不能在代码中创建
* JceSecurity.getVerificationResult 会将其put进 private static final Map<Provider,Object>中,导致内存缓便被耗尽
*/
private static final BouncyCastleProvider PROVIDER = new BouncyCastleProvider();
static {
key = getKey();
}
/**
* 获取key
*/
public static String getKey() {
StringBuilder uid = new StringBuilder();
//产生16位的强随机数
Random rd = new SecureRandom();
for (int i = 0; i < KEY_LENGTH / 8; i++) {
//产生0-2的3位随机数
int type = rd.nextInt(3);
switch (type) {
case 0:
//0-9的随机数
uid.append(rd.nextInt(10));
break;
case 1:
//ASCII在65-90之间为大写,获取大写随机
uid.append((char) (rd.nextInt(25) + 65));
break;
case 2:
//ASCII在97-122之间为小写,获取小写随机
uid.append((char) (rd.nextInt(25) + 97));
break;
default:
break;
}
}
return uid.toString();
}
/**
* 加密
*
* @param content 加密的字符串
* @param encryptKey key值
*/
public static String encrypt(String content, String encryptKey) throws Exception {
//设置Cipher对象
Cipher cipher = Cipher.getInstance(ALGORITHMS, PROVIDER);
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(encryptKey.getBytes(), KEY_ALGORITHM));
//调用doFinal
// 转base64
return Base64.encodeBase64String(cipher.doFinal(content.getBytes(StandardCharsets.UTF_8)));
}
/**
* 解密
*
* @param encryptStr 解密的字符串
* @param decryptKey 解密的key值
*/
public static String decrypt(String encryptStr, String decryptKey) throws Exception {
//base64格式的key字符串转byte
byte[] decodeBase64 = Base64.decodeBase64(encryptStr);
//设置Cipher对象
Cipher cipher = Cipher.getInstance(ALGORITHMS,PROVIDER);
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(decryptKey.getBytes(), KEY_ALGORITHM));
//调用doFinal解密
return new String(cipher.doFinal(decodeBase64));
}
public static void main(String[] args) throws Exception {
String key = getKey();
System.out.println("key:"+key);
String en = encrypt("asdf",key);
System.out.println("加密后的结果:"+en);
System.out.println(decrypt(en,key));
}
}
再来看看RSA的加密工具类:RsaUtil
package com.fanhf.annotationtest.utils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.tomcat.util.http.fileupload.ByteArrayOutputStream;
import javax.crypto.Cipher;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* RSA加、解密算法工具类
*/
@Slf4j
public class RsaUtil {
/**
* 加密算法AES
*/
private static final String KEY_ALGORITHM = "RSA";
/**
* 算法名称/加密模式/数据填充方式
* 默认:RSA/ECB/PKCS1Padding
*/
private static final String ALGORITHMS = "RSA/ECB/PKCS1Padding";
/**
* Map获取公钥的key
*/
private static final String PUBLIC_KEY = "publicKey";
/**
* Map获取私钥的key
*/
private static final String PRIVATE_KEY = "privateKey";
/**
* RSA最大加密明文大小
*/
private static final int MAX_ENCRYPT_BLOCK = 117;
/**
* RSA最大解密密文大小
*/
private static final int MAX_DECRYPT_BLOCK = 128;
/**
* RSA 位数 如果采用2048 上面最大加密和最大解密则须填写: 245 256
*/
private static final int INITIALIZE_LENGTH = 1024;
/**
* 后端RSA的密钥对(公钥和私钥)Map,由静态代码块赋值
*/
private static Map<String, Object> genKeyPair = new LinkedHashMap<>();
static {
try {
genKeyPair.putAll(genKeyPair());
} catch (Exception e) {
//输出到日志文件中
e.printStackTrace();
}
}
/**
* 生成密钥对(公钥和私钥)
*/
private static Map<String, Object> genKeyPair() throws Exception {
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
keyPairGen.initialize(INITIALIZE_LENGTH);
KeyPair keyPair = keyPairGen.generateKeyPair();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
Map<String, Object> keyMap = new HashMap<>(2);
//公钥
keyMap.put(PUBLIC_KEY, publicKey);
//私钥
keyMap.put(PRIVATE_KEY, privateKey);
return keyMap;
}
/**
* 私钥解密
*
* @param encryptedData 已加密数据
* @param privateKey 私钥(BASE64编码)
*/
public static byte[] decryptByPrivateKey(byte[] encryptedData, String privateKey) throws Exception {
//base64格式的key字符串转Key对象
Key privateK = KeyFactory.getInstance(KEY_ALGORITHM).generatePrivate(new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey)));
//设置加密、填充方式
/*
如需使用更多加密、填充方式,引入
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk16</artifactId>
<version>1.46</version>
</dependency>
并改成
Cipher cipher = Cipher.getInstance(ALGORITHMS ,new BouncyCastleProvider());
*/
Cipher cipher = Cipher.getInstance(ALGORITHMS);
cipher.init(Cipher.DECRYPT_MODE, privateK);
//分段进行解密操作
return encryptAndDecryptOfSubsection(encryptedData, cipher, MAX_DECRYPT_BLOCK);
}
/**
* 公钥加密
*
* @param data 源数据
* @param publicKey 公钥(BASE64编码)
*/
public static byte[] encryptByPublicKey(byte[] data, String publicKey) throws Exception {
//base64格式的key字符串转Key对象
Key publicK = KeyFactory.getInstance(KEY_ALGORITHM).generatePublic(new X509EncodedKeySpec(Base64.decodeBase64(publicKey)));
//设置加密、填充方式
/*
如需使用更多加密、填充方式,引入
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk16</artifactId>
<version>1.46</version>
</dependency>
并改成
Cipher cipher = Cipher.getInstance(ALGORITHMS ,new BouncyCastleProvider());
*/
Cipher cipher = Cipher.getInstance(ALGORITHMS);
cipher.init(Cipher.ENCRYPT_MODE, publicK);
//分段进行加密操作
return encryptAndDecryptOfSubsection(data, cipher, MAX_ENCRYPT_BLOCK);
}
/**
* 获取私钥
*/
public static String getPrivateKey() {
Key key = (Key) genKeyPair.get(PRIVATE_KEY);
return Base64.encodeBase64String(key.getEncoded());
}
/**
* 获取公钥
*/
public static String getPublicKey() {
Key key = (Key) genKeyPair.get(PUBLIC_KEY);
return Base64.encodeBase64String(key.getEncoded());
}
/**
* 分段进行加密、解密操作
*/
private static byte[] encryptAndDecryptOfSubsection(byte[] data, Cipher cipher, int encryptBlock) throws Exception {
int inputLen = data.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
byte[] cache;
int i = 0;
// 对数据分段加密
while (inputLen - offSet > 0) {
if (inputLen - offSet > encryptBlock) {
cache = cipher.doFinal(data, offSet, encryptBlock);
} else {
cache = cipher.doFinal(data, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * encryptBlock;
}
out.close();
return out.toByteArray();
}
}
AES和RSA的加密算法不是我写的,而是从同事那里copy的,网上也有一些关于AES和RAS算法的博客,大家可自行学习,我就不赘述了,这里只展示具体的实现。
切面1:SafetyAspect
package com.fanhf.annotationtest.aspectj;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fanhf.annotationtest.annotation.Decrypt;
import com.fanhf.annotationtest.annotation.Encrypt;
import com.fanhf.annotationtest.bean.EncryptBean;
import com.fanhf.annotationtest.utils.AesUtil;
import com.fanhf.annotationtest.utils.RsaUtil;
import org.apache.commons.codec.binary.Base64;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
/**
* @author fanhf
* @Description AES + RSA 加解密AOP处理
* @date 2020-11-09 13:47
*/
@Aspect
@Component
public class SafetyAspect {
private static final Logger log = LoggerFactory.getLogger(SafetyAspectPlus.class);
/**
*这里的私钥和公钥可以替换,只要执行RsaUtils的main方法就可以获取到。
* RSA加解密的时候需要根据公钥和私钥来进行加解密,真实的业务场景一般是从前端传过来公钥,私钥是随机获取的,我这里为了方便,就写死了。
**/
private static final String PRIVATEKEY = "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBALwT6AsfbEV/d5Mv+fbsaxjAKDPFmJu2JnV3b03T6RX1Oailg1tawbI3boR2PC1lOjRBHoqR8GPJGNu79NmnaZ9Fq5gqUZkO/K88/BdRgiqropEqLginYiiDJm22jqdFUb2VafwLj2ou5sevtbIR6fVou881k/YRsAImsA3WH7ITAgMBAAECgYAndmnOw6YdIvS8/mkNZWfHRrJowoIV0e9Z4FiLVPZoNA8IEspwBaf0s+rNgl14DPBcfHljC+ILnetIV7S1Yoonk5epsq7BjmrB7AqXazdXLv2Cmyw8CyGjs7ShhBZPn/oAmCWIQgObptDBGrdB9bZk1s7mSr96Z4fIw9/mLXuqsQJBAPI3mnjfVcr5A5KysZezqjljYGgf5cboP/t956H+LqMLkh2DC8nbiSoOy5etWOvoarRb6t8bWc4/crCXQvvTrIkCQQDGx6ZMoLURLz8KLCRKjpUt5Dx0jmu3utjPlmfLxpiAI0VvokWpFZrc4mlib/T4zDtIDFGzGBkSC7UhJ4339Nq7AkAGv6ncKEzZpOqGkdgE5AqgIray8ACU9C+kMDPd/ZkLDe16SQZxD17Y/ySJC1lo6Ubf05fNs5Ni/b2SUgSZw6IRAkAVbDjg80TwWC4sE3vJyToMmxdk3GCBiZKKNMR08q9GyAZYtJ1bTqfE/GWtJTG6ipAtAJ7hdUxmZHqd2xxyx6G3AkASaSgCdDxd7N6O6qZs0T5V2z9PzXj1exsUwIPUtVC1EnZ+OmklUBVqRHtqw+xSwomfcKvb7zx8T9L3oCgx6okj";
private static final String PUBLICKEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8E+gLH2xFf3eTL/n27GsYwCgzxZibtiZ1d29N0+kV9TmopYNbWsGyN26EdjwtZTo0QR6KkfBjyRjbu/TZp2mfRauYKlGZDvyvPPwXUYIqq6KRKi4Ip2IogyZtto6nRVG9lWn8C49qLubHr7WyEen1aLvPNZP2EbACJrAN1h+yEwIDAQAB";
/**
* Pointcut 切入点
*/
@Pointcut("execution(public * com.fanhf.annotationtest.controller.UserController.encrypt(..))||" +
"execution(public * com.fanhf.annotationtest.controller.UserController.decrypt(..))")
public void safetyAspect() {}
/**
* 环绕通知
*/
@Around(value = "safetyAspect()")
public Object around(ProceedingJoinPoint pjp) {
try {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
assert attributes != null;
//request对象
HttpServletRequest request = attributes.getRequest();
//http请求方法 post get
String httpMethod = request.getMethod().toLowerCase();
//method方法
Method method = ((MethodSignature) pjp.getSignature()).getMethod();
//method方法上面的注解
Annotation[] annotations = method.getAnnotations();
//方法的形参参数
Object[] args = pjp.getArgs();
//是否有@Decrypt
boolean hasDecrypt = false;
//是否有@Encrypt
boolean hasEncrypt = false;
for (Annotation annotation : annotations) {
if (annotation.annotationType() == Decrypt.class) {
hasDecrypt = true;
}
if (annotation.annotationType() == Encrypt.class) {
hasEncrypt = true;
}
}
InputStreamReader inputStreamReader = new InputStreamReader(request.getInputStream(), "UTF-8");
StringBuilder stringBuilder;
try (BufferedReader br = new BufferedReader(inputStreamReader)) {
String line;
stringBuilder = new StringBuilder();
while ((line = br.readLine()) != null) {
stringBuilder.append(line);
}
}
String oridata = stringBuilder.toString();
JSONObject jsonObject = JSONObject.parseObject(oridata);
String line = prettyJson(oridata);
log.info("入参的切面json串:\r\n{}", line);
if ("post".equals(httpMethod) && hasDecrypt) {
String data = null;
if (oridata.indexOf("data") != -1) {
data = jsonObject.getString("data");
jsonObject = JSONObject.parseObject(data);
}
//AES加密后的数据
String aesKey = jsonObject.getString("aesData");
//后端RSA公钥加密后的AES数据
String dataString = jsonObject.getString("rsaData");
//后端私钥解密的到AES的key
byte[] plaintext = RsaUtil.decryptByPrivateKey(Base64.decodeBase64(dataString), PRIVATEKEY);
String rsaData = new String(plaintext);
log.info("解密出来的AES生成的key为:{}", rsaData);
String originData = AesUtil.decrypt(aesKey, rsaData);
log.info("解密出来的data数据:\r\n{}", prettyJson(originData));
if (args.length > 0) {
args[0] = JSONObject.parseObject(originData, args[0].getClass());
}
}
if (hasEncrypt) {
String aesKey = AesUtil.getKey();
log.info("AES的key:{}", aesKey);
String dataString = jsonObject.toJSONString();
String aesData = AesUtil.encrypt(dataString, aesKey);
log.info("AesUtil加密后的数据:{}", aesData);
String rsaData = Base64.encodeBase64String(RsaUtil.encryptByPublicKey(aesKey.getBytes(), PUBLICKEY));
log.info("RsaUtil加密后的data数据:{}", rsaData);
EncryptBean encryptBean = new EncryptBean();
encryptBean.setAesData(aesData);
encryptBean.setRsaData(rsaData);
log.info("加密后的数据为:{}", JSONObject.toJSONString(encryptBean));
if (args.length > 0) {
args[0] = JSONObject.parseObject(JSONObject.toJSONString(encryptBean), args[0].getClass());
}
}
return pjp.proceed(args);
} catch (Throwable e) {
log.error(e.getMessage());
return JSONObject.parseObject("{ \"加解密异常\": \"" +e.getMessage() + "\"}");
}
}
public static String prettyJson(String reqJson) {
JSONObject object = JSONObject.parseObject(reqJson);
return JSON.toJSONString(object, new SerializerFeature[]{SerializerFeature.PrettyFormat, SerializerFeature.WriteMapNullValue, SerializerFeature.WriteDateUseDateFormat});
}
}
切面2:SafetyAspectPlus
package com.fanhf.annotationtest.aspectj;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fanhf.annotationtest.annotation.Decrypt;
import com.fanhf.annotationtest.annotation.Encrypt;
import com.fanhf.annotationtest.bean.Result;
import com.fanhf.annotationtest.utils.AesUtil;
import com.fanhf.annotationtest.utils.RsaUtil;
import org.apache.commons.codec.binary.Base64;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
/**
* @author fanhf
* @Description AES + RSA 加解密AOP处理
* @date 2020-11-09 13:47
*/
@Aspect
@Component
public class SafetyAspectPlus {
private static final Logger log = LoggerFactory.getLogger(SafetyAspectPlus.class);
/**
*这里的私钥和公钥可以替换,只要执行RsaUtils的main方法就可以获取到。
* RSA加解密的时候需要根据公钥和私钥来进行加解密,真实的业务场景一般是从前端传过来公钥,私钥是随机获取的,我这里为了方便,就写死了。
**/
private static final String PRIVATEKEY = "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBALwT6AsfbEV/d5Mv+fbsaxjAKDPFmJu2JnV3b03T6RX1Oailg1tawbI3boR2PC1lOjRBHoqR8GPJGNu79NmnaZ9Fq5gqUZkO/K88/BdRgiqropEqLginYiiDJm22jqdFUb2VafwLj2ou5sevtbIR6fVou881k/YRsAImsA3WH7ITAgMBAAECgYAndmnOw6YdIvS8/mkNZWfHRrJowoIV0e9Z4FiLVPZoNA8IEspwBaf0s+rNgl14DPBcfHljC+ILnetIV7S1Yoonk5epsq7BjmrB7AqXazdXLv2Cmyw8CyGjs7ShhBZPn/oAmCWIQgObptDBGrdB9bZk1s7mSr96Z4fIw9/mLXuqsQJBAPI3mnjfVcr5A5KysZezqjljYGgf5cboP/t956H+LqMLkh2DC8nbiSoOy5etWOvoarRb6t8bWc4/crCXQvvTrIkCQQDGx6ZMoLURLz8KLCRKjpUt5Dx0jmu3utjPlmfLxpiAI0VvokWpFZrc4mlib/T4zDtIDFGzGBkSC7UhJ4339Nq7AkAGv6ncKEzZpOqGkdgE5AqgIray8ACU9C+kMDPd/ZkLDe16SQZxD17Y/ySJC1lo6Ubf05fNs5Ni/b2SUgSZw6IRAkAVbDjg80TwWC4sE3vJyToMmxdk3GCBiZKKNMR08q9GyAZYtJ1bTqfE/GWtJTG6ipAtAJ7hdUxmZHqd2xxyx6G3AkASaSgCdDxd7N6O6qZs0T5V2z9PzXj1exsUwIPUtVC1EnZ+OmklUBVqRHtqw+xSwomfcKvb7zx8T9L3oCgx6okj";
private static final String PUBLICKEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8E+gLH2xFf3eTL/n27GsYwCgzxZibtiZ1d29N0+kV9TmopYNbWsGyN26EdjwtZTo0QR6KkfBjyRjbu/TZp2mfRauYKlGZDvyvPPwXUYIqq6KRKi4Ip2IogyZtto6nRVG9lWn8C49qLubHr7WyEen1aLvPNZP2EbACJrAN1h+yEwIDAQAB";
/**
* Pointcut 切入点
*/
@Pointcut("execution(public * com.fanhf.annotationtest.controller.UserController.ende(..))")
public void safetyAspectPlus() {}
/**
* 环绕通知
*/
@Around(value = "safetyAspectPlus()")
public Object around(ProceedingJoinPoint pjp) {
try {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
assert attributes != null;
//request对象
HttpServletRequest request = attributes.getRequest();
//http请求方法 post get
String httpMethod = request.getMethod().toLowerCase();
//method方法
Method method = ((MethodSignature) pjp.getSignature()).getMethod();
//method方法上面的注解
Annotation[] annotations = method.getAnnotations();
//方法的形参参数
Object[] args = pjp.getArgs();
//是否有@Decrypt
boolean hasDecrypt = false;
//是否有@Encrypt
boolean hasEncrypt = false;
for (Annotation annotation : annotations) {
if (annotation.annotationType() == Decrypt.class) {
hasDecrypt = true;
}
if (annotation.annotationType() == Encrypt.class) {
hasEncrypt = true;
}
}
InputStreamReader inputStreamReader = new InputStreamReader(request.getInputStream(), "UTF-8");
StringBuilder stringBuilder;
try (BufferedReader br = new BufferedReader(inputStreamReader)) {
String line;
stringBuilder = new StringBuilder();
while ((line = br.readLine()) != null) {
stringBuilder.append(line);
}
}
String oridata = stringBuilder.toString();
JSONObject jsonObject = JSONObject.parseObject(oridata);
String line = prettyJson(oridata);
log.info("入参的切面json串:\r\n{}", line);
if ("post".equals(httpMethod) && hasDecrypt) {
/*
* 由于通过了Result加密后多了个data,所以再次加密后需要获取data的value后再进行加密
* {
* "data": {
* "rsaData": "FkvZ+vCiyJePoaXbZDfw++Jfgk7gJNAE9piaPOebH0YcaAF70q3GWracDSSiXG0FFKmzV/MGadl5rIyg3DA+Z9/ETDRCr6Z5SYSfL5FDmL0jTUIMxErvPEjYsTIk9MTur7J1/p4wVsTxkZprXUoc3fXdyshi6cM2kC+KuNxwGjA=",
* "aesData": "K8pocSiGFUZYmmFgmFOD6iwcBBi8BBBclSyePKPd+pDUft4B/wmB5nk4sd9CWNCR3F36X2JVrOrqM2XIm+kX8Q=="
* }
* }
**/
String data = null;
if (oridata.indexOf("data") != -1) {
data = jsonObject.getString("data");
jsonObject = JSONObject.parseObject(data);
}
//AES加密后的数据
String aesKey = jsonObject.getString("aesData");
//后端RSA公钥加密后的AES数据
String dataString = jsonObject.getString("rsaData");
//后端私钥解密的到AES的key
byte[] plaintext = RsaUtil.decryptByPrivateKey(Base64.decodeBase64(dataString), PRIVATEKEY);
String rsaData = new String(plaintext);
log.info("解密出来的AES生成的key为:{}", rsaData);
String originData = AesUtil.decrypt(aesKey, rsaData);
log.info("解密出来的data数据:\r\n{}", prettyJson(originData));
if (args.length > 0) {
args[0] = JSONObject.parseObject(originData, args[0].getClass());
}
}
Object object = pjp.proceed(args);
if (hasEncrypt) {
String aesKey = AesUtil.getKey();
log.info("AES的key:{}", aesKey);
String dataString = JSONObject.toJSONString(object);
log.info("需要解密处理后再加密json数据:\r\n{}", prettyJson(dataString));
dataString = JSONObject.parseObject(dataString).getString("data");
log.info("把data对应的value提取出来的originData:\r\n{}", prettyJson(dataString));
String aesData = AesUtil.encrypt(dataString, aesKey);
log.info("AesUtil加密后的数据:{}", aesData);
String rsaData = Base64.encodeBase64String(RsaUtil.encryptByPublicKey(aesKey.getBytes(), PUBLICKEY));
log.info("RsaUtil加密后的data数据:{}", rsaData);
String originData = "{\"rsaData\": \"" + rsaData + "\",\"aesData\": \"" + aesData + "\"}";
log.info("未Result化,返回给前端的加密数据为:\r\n{}", prettyJson(originData));
object = Result.of(JSONObject.parseObject(originData, Object.class));
}
return object;
} catch (Throwable e) {
log.error(e.getMessage());
return Result.of("加解密异常:\n\t" + e.getMessage());
}
}
public static String prettyJson(String reqJson) {
JSONObject object = JSONObject.parseObject(reqJson);
return JSON.toJSONString(object, new SerializerFeature[]{SerializerFeature.PrettyFormat, SerializerFeature.WriteMapNullValue, SerializerFeature.WriteDateUseDateFormat});
}
}
这两个切面的区别比较少,不同的在返回值和对data的处理,同学们可以自己copy到开发工具中debug模式看看具体的区别。
测试
我是用的postman来调用的:
加密(调用的是切面1:SafetyAspect)
拿着加密的数据去解密(调用的是切面1:SafetyAspect)
解密后的数据处理后再加密(调用的是切面2:SafetyAspectPlus)
再对SafetyAspectPlus加密的结果用切面SafetyAspect来解密
和第一次加密后用切面SafetyAspect解密的结果是一样的。
对应的日志:
这3张日志的截图是刚才在postman执行的日志纪录,是完整的日志记录。
到这里这篇博客就基本完成了,如果有更好的办法或建议欢迎留下足迹。
---------------------------------与君共勉-------------------------------------