springboot aop方式实现敏感数据自动加解密

一、前言
在实际项目开发中,可能会涉及到一些敏感信息,那么我们就需要对这些敏感信息进行加密处理,
也就是脱敏,比如像手机号、身份证号等信息。如果我们只是在接口返回后再去做替换处理,则代码会显得非常冗余,那么实际可以通过注解的方式实现数据脱敏。

二、具体实现
1.定义一个标记于方法上的注解。

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)         //METHOD 说明该注解只能用在方法上
@Retention(RetentionPolicy.RUNTIME) //RUNTIME 说明该注解在运行时生效
public @interface DataEncryption {

}

2.定义一个数据解密的注解。

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)         //METHOD 说明该注解只能用在方法上
@Retention(RetentionPolicy.RUNTIME) //RUNTIME 说明该注解在运行时生效
public @interface DataDecryption {

}

3.定义一个在字段上,且有值的注解。

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)           //FIELD 说明该注解只能用在字段上
@Retention(RetentionPolicy.RUNTIME)  //RUNTIME 说明该注解在运行时生效
public @interface EncryptField {
	//脱敏枚举值
	DesensitizationEnum value();
}

4.定义一个在字段上,且有值的注解。

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)           //FIELD 说明该注解只能用在字段上
@Retention(RetentionPolicy.RUNTIME)  //RUNTIME 说明该注解在运行时生效
public @interface DecryptField {
	//脱敏枚举值
	DesensitizedUtil.DesensitizedType value();
}

5.定义用户返回的实体类。


import com.test.annotation.DecryptField;
import com.test.enums.DesensitizationEnum;
import com.test.utils.AesUtil;

import java.lang.reflect.Field;
import lombok.Data;
// 用户信息返回实体类
@Data
public class UserResVo {
 	@DecryptField(DesensitizedUtil.DesensitizedType.CHINESE_NAME)
    @EncryptField
    private String name;
    @EncryptField
    @EncryptField(DesensitizedUtil.DesensitizedType.ID_CARD)
    private String idCard;
    @EncryptField
	@EncryptField(DesensitizedUtil.DesensitizedType.MOBILE_PHONE)
    private String phone;
    @EncryptField
    @EncryptField(DesensitizedUtil.DesensitizedType.EMAIL)
    private String email;
}

6.定义AOP注解实现

import cn.hutool.crypto.digest.DigestUtil;
import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
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.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Field;
import java.util.Objects;

@Aspect
@Component
@Slf4j
public class SecretAopAspect {

    @Pointcut("@annotation(com.secret.annotation.DataEncryption)")
    public void encryptAopCut() {
    }

    @Pointcut("@annotation(com.secret.annotation.DataDecryption)")
    public void decryptAopCut() {
    }
    /**
     * @method encryptMethodAop
     * @desc 加密方法
     * @Param: joinPoint
     */
    @Around("encryptAopCut()")
    public Object encryptMethodAop(ProceedingJoinPoint joinPoint) {
        Object responseObj = null;
        try {
            responseObj = joinPoint.proceed();
            this.handleEncrypt(responseObj);
            //md5加密,在相应里面设置签名值
            String md5Data = DigestUtil.md5Hex(new Gson().toJson(responseObj));
            HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
            response.setHeader("md5",md5Data);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            this.log.error("encryptMethodAop处理出现异常{}", throwable);
        }
        return responseObj;
    }
   /**
     * @method decryptMethodAop
     * @desc 解密方法
     * @Param: joinPoint
     */
    @Around("decryptAopCut()")
    public Object decryptMethodAop(ProceedingJoinPoint joinPoint) {
        Object responseObj = null;
        try {
            responseObj = joinPoint.getArgs()[0];
            //throw new RuntimeException("md5校验失败");
            this.handleDecrypt(responseObj);
            //生成md5签名
            String md5 = DigestUtil.md5Hex(new Gson().toJson(responseObj));
            System.out.println(md5);
            //从请求头获取前端传过来的签名
            String origianlMd5 = "";
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            origianlMd5 = request.getHeader("md5");
            //比对签名
            if(md5.equals(origianlMd5)){//方便调试,不比对前端签名
                responseObj = joinPoint.proceed();
            }else{
                throw new Exception("参数的md5校验不同,可能存在篡改行为,请检查!");
            }
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            log.error("decryptMethodAop处理出现异常{}", throwable);
        }
        return responseObj;
    }
     /**
     * @method handleEncrypt
     * @desc 对实体么个属性进行加密
     * @Param: requestObj
     * @return void
     */
    private void handleEncrypt(Object requestObj) throws Exception {
        if (!Objects.isNull(requestObj)) {
            Field[] fields = requestObj.getClass().getDeclaredFields();
            Field[] fieldsCopy = fields;
            int fieldLength = fields.length;
            for(int i = 0; i < fieldLength; ++i) {
                Field field = fieldsCopy[i];
                boolean hasSecureField = field.isAnnotationPresent(EncryptField.class);
                if (hasSecureField) {
                    field.setAccessible(true);
                    String plaintextValue = (String)field.get(requestObj);
                    String cipherText = AesUtil.encrypt(plaintextValue);
                    field.set(requestObj, cipherText);
                }
            }
        }
    }
    /**
     * @method handleDecrypt
     * @desc 对实体么个属性进行解密
     * @Param: responseObj
     * @return
     */
    private Object handleDecrypt(Object responseObj) throws Exception {
        if (Objects.isNull(responseObj)) {
            return null;
        } else {
            Field[] fields = responseObj.getClass().getDeclaredFields();
            Field[] fieldsCopy = fields;
            int fieldLength = fields.length;
            for(int i = 0; i < fieldLength; ++i) {
                Field field = fieldsCopy[i];
                boolean hasSecureField = field.isAnnotationPresent(DecryptField.class);
                if (hasSecureField) {
                    DecryptField decryptField = field.getAnnotation(DecryptField.class);
                    field.setAccessible(true);
                    String encryptValue = (String)field.get(responseObj);
                    encryptValue = AesUtil.decrypt(encryptValue,decryptField.getValue());
                    field.set(responseObj, encryptValue);
                }
            }
            return responseObj;
        }
    }
 }

7.加解密工具类

import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.DesensitizedUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.symmetric.AES;

public class AesUtil {
    // 默认16位 或 128 256位
    public static String AES_KEY = "EN#qerdfdshbd110";

    public static AES aes = SecureUtil.aes(AES_KEY.getBytes());

    public static String encrypt(Object obj) {
        return aes.encryptHex((String) obj);
    }

    public static String decrypt(Object obj, DesensitizedUtil.DesensitizedType desensitizedType) {
        // 解密
        String decrypt = decrypt(obj);
        // 脱敏
        return DesensitizedUtil.desensitized(decrypt, desensitizedType);
    }

    public static String decrypt(Object obj) {
        return aes.decryptStr((String) obj, CharsetUtil.CHARSET_UTF_8);
    }
}

8.接着就可以测试了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,针对您的问题,我可以给您一些简单的解释。 SpringBoot中的AOP(面向切面编程)可以帮助我们对方法进行拦截和增强,这样我们就可以在方法执行前后进行一些自己想要的操作,比如接口的请求数据解密和返回数据密。 具体来说,我们可以通过定义一个切面类,在其中定义一个前置通知和一个后置通知。前置通知可以在方法执行前进行解密操作,后置通知可以在方法执行后进行密操作。 下面是一个简单的示例代码: ```java @Aspect @Component public class EncryptAspect { @Autowired private EncryptService encryptService; @Pointcut("execution(public * com.example.controller.*.*(..))") public void encrypt() {} @Before("encrypt()") public void doEncrypt(JoinPoint joinPoint) { Object[] args = joinPoint.getArgs(); for (Object arg : args) { if (arg instanceof String) { String decrypted = encryptService.decrypt((String) arg); // 将解密后的数据重新设置到参数中 // ... } } } @AfterReturning(value = "encrypt()", returning = "result") public void doDecrypt(JoinPoint joinPoint, Object result) { if (result instanceof String) { String encrypted = encryptService.encrypt((String) result); // 将密后的数据返回 // ... } } } ``` 在上面的示例代码中,我们定义了一个切面类`EncryptAspect`,其中通过`@Pointcut`注解定义了需要拦截的方法,即`com.example.controller`包下的所有方法。在`doEncrypt`方法中,我们可以获取到方法的参数,并进行解密操作;在`doDecrypt`方法中,我们可以获取到方法的返回值,并进行密操作。 需要注意的是,上面的示例代码中的`EncryptService`是一个解密服务的接口,具体的解密实现可以根据自己的需求进行编写。 希望以上解释可以帮助到您。如果还有其他问题,欢迎随时提出。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夜空下的星

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值