1. JoinPoint
1.1 介绍
Joinpoint
是面向切面编程(AOP)中的一个重要概念,指的是在应用程序执行过程中可以被拦截的特定点。在AOP中,Joinpoint
代表了程序执行的某个具体位置,比如方法的调用、异常的抛出等。AOP框架通过拦截这些Joinpoint
来插入额外的逻辑,实现横切关注点的功能。
我们可以通过JoinPoint获取到除了异常参数对象和返回值之外的所有信息
返回值截图:
1.2 获取
获取方法调用时,传入的参数
Object[] args = joinPoint.getArgs();
获取被通知的目标对象
Object target = joinPoint.getTarget();
获取代理方法的信息
MethodSignature signature = (MethodSignature) joinPoint.getSignature(); // 获取代理方法的信息
String methodName = signature.getName(); // 获取方法名
Class<?> returnType = signature.getReturnType(); // 获取返回类型
Class<?>[] parameterTypes = signature.getParameterTypes(); // 获取参数类型数组
获取增强方法中返回值
@AfterReturning(value = "point()",returning = "ret")
public void methodAfterReturning(JoinPoint joinPoint, Object ret){
System.out.println("AfterReturning");
}
获取增强方法中的异常对象
@AfterThrowing(value = "point()",throwing = "e")
public void methodAfterThrowing(JoinPoint joinPoint,Throwable e){
System.out.println("AfterThrowing");
}
在环绕通知中,调用proceed()方法会继续执行被通知的方法。
@Around("execution(* com.example.service.*.*(..))")
public Object aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable {
// 执行前置逻辑
Object result = joinPoint.proceed(); // 继续执行被通知的方法
// 执行后置逻辑
return result;
}
2. 数据库加解密
2.1 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.18</version>
</dependency>
2.2 定义需要加解密的注解
2.2.1 解密字段注解
//解密字段注解
@Target(value = {ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DecryptField {
//是否开启敏感数据脱敏处理,默认不开启
boolean open() default false;
//脱敏开始位置索引
int start() default 0;
//脱敏从开始位置向后偏移量
int offset() default 6;
}
2.2.2 加密字段注解
@Target(value = {ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptField {
}
2.2.3 解密处理的方法注解
//作用于对返回值进行解密处理的方法上
@Target(value = {ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface NeedDecrypt {
}
2.2.4 加密处理的方法注解
// 作用于需要对入参数进行加密处理的方法上
@Target(value = {ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface NeedEncrypt {
}
2.3 加解密切入点
2.3.1 解密切入点
@Aspect
@Component
@Slf4j
public class DecryptAop {
/**
* 定义加密切入点
*/
@Pointcut(value = "@annotation(com.example.demo.annotation.NeedDecrypt)")
public void pointcut() {
}
@Around("pointcut()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
log.info("//环绕通知 start");
//执行目标方法
Signature signature = proceedingJoinPoint.getSignature();
Object result = proceedingJoinPoint.proceed();
//判断目标方法的返回值类型
if (result instanceof List) {
for (Object tmp : ((List) result)) {
//数据脱敏处理逻辑
this.deepProcess(tmp);
}
} else {
this.deepProcess(result);
}
log.info("//环绕通知 end");
return result;
}
public void deepProcess(Object obj) throws IllegalAccessException {
if (obj != null) {
//取出输出对象的所有字段属性,并遍历
Field[] declaredFields = obj.getClass().getDeclaredFields();
for (Field declaredField : declaredFields) {
//判断字段属性上是否标记DecryptField注解
if (declaredField.isAnnotationPresent(DecryptField.class)) {
//如果判断结果为真,则取出字段属性数据进行解密处理
declaredField.setAccessible(true);
Object valObj = declaredField.get(obj);
if (valObj != null) {
String value = valObj.toString();
//加密数据的解密处理
value = this.decrypt(value);
DecryptField annotation = declaredField.getAnnotation(DecryptField.class);
boolean open = annotation.open();
if (open) {
//如果开启,则开始进行数据脱敏处理
int start = annotation.start();
int offset = annotation.offset();
value = this.secret(value, start, offset);
}
//把解密后的数据重新赋值
declaredField.set(obj, value);
}
}
}
}
}
private String decrypt(String value) {
//这里特别注意一下,对称加密是根据密钥进行加密和解密的,加密和解密的密钥是相同的,一旦泄漏,就无秘密可言,
//“fanfu-csdn”就是我自定义的密钥,这里仅作演示使用,实际业务中,这个密钥要以安全的方式存储;
byte[] key = SecureUtil.generateKey(SymmetricAlgorithm.AES.getValue(), "shenduxuexizhennan".getBytes()).getEncoded();
SymmetricCrypto aes = new SymmetricCrypto(SymmetricAlgorithm.AES, key);
String decryptStr = aes.decryptStr(value);
return decryptStr;
}
private String secret(String value, Integer start, Integer limit) {
//如果有特殊需要,还可以定义其他用于代替敏感数据的字符,一般情况下,使用的是“*”
char[] chars = value.toCharArray();
for (int i = start; i < start + limit; i++) {
chars[i] = '*';
}
return String.valueOf(chars);
}
}
2.3.2 加密切入点
@Aspect
@Component
@Slf4j
public class EncryptAop {
/**
* 定义加密切入点
*/
@Pointcut(value = "@annotation(com.example.demo.annotation.NeedDecrypt)")
public void pointcut() {
}
@Around("pointcut()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
log.info("//环绕通知 start");
//获取命中目标方法的入参数
Object[] args = proceedingJoinPoint.getArgs();
for (Object arg : args) {
//按参数的类型进行判断,如果业务中还有其他的类型,可酌情增加
if (arg != null) {
if (arg instanceof List) {
for (Object tmp : ((List) arg)) {
//加密处理
this.deepProcess(tmp);
}
} else {
this.deepProcess(arg);
}
}
}
//对敏感数据加密后执行目标方法
Object result = proceedingJoinPoint.proceed();
log.info("//环绕通知 end");
return result;
}
public void deepProcess(Object obj) throws IllegalAccessException {
if (obj != null) {
//获取对象的所有字段属性并遍历
Field[] declaredFields = obj.getClass().getDeclaredFields();
for (Field declaredField : declaredFields) {
//判断字段属性上是否标记了@EncryptField注解
if (declaredField.isAnnotationPresent(EncryptField.class)) {
//如果判断结果为真,则取出字段属性值,进行加密、重新赋值
declaredField.setAccessible(true);
Object valObj = declaredField.get(obj);
if (valObj != null) {
String value = valObj.toString();
//开始敏感字段属性值加密
String decrypt = this.encrypt(value);
//把加密后的字段属性值重新赋值
declaredField.set(obj, decrypt);
}
}
}
}
}
private String encrypt(String value) {
//这里特别注意一下,对称加密是根据密钥进行加密和解密的,加密和解密的密钥是相同的,一旦泄漏,就无秘密可言,
//“fanfu-csdn”就是我自定义的密钥,这里仅作演示使用,实际业务中,这个密钥要以安全的方式存储;
byte[] key = SecureUtil.generateKey(SymmetricAlgorithm.AES.getValue(), "shenduxuexizhennan".getBytes()).getEncoded();
SymmetricCrypto aes = new SymmetricCrypto(SymmetricAlgorithm.AES, key);
String encryptValue = aes.encryptBase64(value);
return encryptValue;
}
}
2.4 使用
- 把自定义的加密字段注解、解密字段注解标记在需要加密或者解密的字段上;
@Slf4j
@Data
public class Person {
private Integer id;
private String userName;
private String loginNo;
@EncryptField
@DecryptField
private String phoneNumber;
private String sex;
@DecryptField
@EncryptField
private String IDCard;
private String address;
@EncryptField
@DecryptField
private String houseNumber;
}
- 把@NeedEncrypt和@NeedDecrypt标记在需要对入参数、返回值中的敏感字段进行加密、解密处理的业务处理方法上;
@RestController
@RequestMapping("/person")
@Slf4j
public class PersonController {
@Autowired
private IPersonService personService;
//添加人员信息
@PostMapping("/add")
@NeedEncrypt
public Person add(@RequestBody Person person, Model model) {
Person result = this.personService.registe(person);
log.info("//增加person执行完成");
return result;
}
//人员信息列表查询
@GetMapping("/list")
@NeedDecrypt
public List<Person> getPerson() {
List<Person> persons = this.personService.getPersonList();
log.info("//查询person列表执行完成");
return persons;
}
//人员信息详情查询
@GetMapping("/{id}")
@NeedDecrypt
public Person get(@PathVariable Integer id) {
Person person= this.personService.get(id);
log.info("//查询person详情执行完成");
return person;
}
}