AOP实现对多数据类型字段进行加密解密

前言

最近接到一个需求,需要在无感前提下对指定的字段进行数据加密和解密。首先对关键字进行提取,“无感”,“指定字段”,“加密解密”。无感很好理解,即对客户来说没有感知,对开发者来说不用修改代码,那就可以考虑AOP;指定字段如果想被AOP监控到可以在字段上加自定义注解,考虑到字段类型不唯一,需要对不同数据类型进行不同方式的加密,比如密码是int类型,那加密之后必须也是int类型,要想实现这个功能肯定要进行判断,然而作为一个有追求的程序员肯定不屑于用if else,这里考虑用策略模式定义接口和多个实现类来实现;至于加密解密,网上搜了一下,针对String类型选择了常用的AES加密方式配合base64转码,至于其他数据类型后期有需要再选择不同的加密方式。

关键字分析完毕,思路有了,理论上来说,首先我需要定义三个注解,分别是用在新增或更新方法上的加密注解、get方法上的解密注解和字段上的注解。在需要进行加密或解密的add或get方法上添加注解,AOP会对方法内的实体对象的字段进行扫描,看哪些字段需要进行加密或解密;其次我需要用策略模式实现对不同类型的字段进行不同方式的加解密,所以需要定义一个接口及若干实现类,这里为了演示方便只写一个String类型的实现类;最后就是要实现具体的加解密操作。

理论存在,实践开始

定义注解

该注解用在新增或更新方法上,AOP扫描到之后会对添加该注解的方法内的数据进行加密操作

package com.th.dp.test;

import java.lang.annotation.*;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EncryptMethod {

}

该注解用在get类方法上,AOP扫描到之后会对添加该注解的方法返回的数据进行解密操作

package com.th.dp.test;

import java.lang.annotation.*;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DecryptMethod {

}

该注解用在字段上,AOP扫描到之后会对添加该注解的字段进行具体的加解密操作

package com.th.dp.test;

import java.lang.annotation.*;

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface EncryptField {
    
}

定义策略模式的接口和实现类

package com.th.dp.test;

public interface EncryptComparator {

    Object encrypt(Object fieldValue);

    Object decrypt(Object fieldValue);
}
package com.th.dp.test;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Base64Utils;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.lang.reflect.Field;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

public class EncryptStringByAES implements EncryptComparator {

    private static final Logger log = LoggerFactory.getLogger(EncryptUtils.class);
    private static final String KEY_ALGORITHM = "AES";
    private static final String DEFAULT_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";//默认的加密算法

	@Value("${encrypt.secretKey}")
    private String secretKey;

    /**
     * AES 加密操作
     *
     * @param content  待加密内容
     * @param password 加密密码
     * @return 返回Base64转码后的加密数据
     */
    public String encrypt(Object content) {
        try {
            Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);// 创建密码器
            byte[] byteContent = content.toString().getBytes("utf-8");
            cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(secretKey));// 初始化为加密模式的密码器
            byte[] result = cipher.doFinal(byteContent);// 加密
            return Base64Utils.encodeToString(result);
        } catch (Exception ex) {
            log.error(ex.getStackTrace().toString());
        }

        return null;
    }

    /**
     * AES 解密操作
     *
     * @param content
     * @param password
     * @return
     */
    public String decrypt(Object content) {
        try {
            //实例化
            Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
            //使用密钥初始化,设置为解密模式
            cipher.init(Cipher.DECRYPT_MODE, getSecretKey(secretKey));
            //执行操作
            byte[] result = cipher.doFinal(Base64Utils.decodeFromString(content.toString()));
            return new String(result, "utf-8");
        } catch (Exception ex) {
            log.error(ex.getStackTrace().toString());
        }

        return null;
    }

    /**
     * 生成加密秘钥
     *
     * @return
     */
    private static SecretKeySpec getSecretKey(final String password) {
        //返回生成指定算法密钥生成器的 KeyGenerator 对象
        KeyGenerator kg = null;
        try {
            kg = KeyGenerator.getInstance(KEY_ALGORITHM);
            //AES 要求密钥长度为 128
            kg.init(128, new SecureRandom(password.getBytes()));
            //生成一个密钥
            SecretKey secretKey = kg.generateKey();
            return new SecretKeySpec(secretKey.getEncoded(), KEY_ALGORITHM);// 转换为AES专用密钥
        } catch (NoSuchAlgorithmException ex) {
            log.error(ex.getStackTrace().toString());
        }
        return null;
    }

}

定义切面类

该类主要负责扫描注解并调用具体实现类的加解密方法,具体逻辑看注释

package com.th.dp.test;

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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

@Aspect
@Component
public class EncryptFieldAop {

    private static final Logger log = LoggerFactory.getLogger(EncryptUtils.class);

    @Value("${encrypt.encryptMode}")
    private String encryptMode;
	
	@Autowired
    private EncryptStringByAES encryptStringByAES;

    // 定义map用来存放数据类型和实现类的映射关系
    private Map<String, Object> map = new HashMap();

    // @PostConstruct作用:在spring容器初始化的时候执行该方法,Constructor >> @Autowired >> @PostConstruct
    // initial方法初始化数据类型和实现类的映射集合
    @PostConstruct
    public void initial() {
        map.put("java.lang.String&AES", encryptStringByAES);
    }

    // 加密注解的切点
    @Pointcut("@annotation(com.th.dp.test.EncryptMethod)")
    public void encryptPointCut() {
    }

    // 解密注解的切点
    @Pointcut("@annotation(com.th.dp.test.DecryptMethod)")
    public void decryptPointCut() {
    }

    // 识别加密注解并处理
    @Around("encryptPointCut()")
    public Object aroundToEn(ProceedingJoinPoint joinPoint) {
        Object responseObj = null;
        try {
            if (joinPoint.getArgs().length != 0) {
                Object requestObj = joinPoint.getArgs()[0];
                //方法进来就加密
                handleEncrypt(requestObj);
                //方法执行
                responseObj = joinPoint.proceed();
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
            log.error("SecureFieldAop处理出现异常{}", e);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            log.error("SecureFieldAop处理出现异常{}", throwable);
        }
        return responseObj;
    }

    // 识别解密注解并处理
    @Around("decryptPointCut()")
    public Object aroundToDe(ProceedingJoinPoint joinPoint) {
        Object responseObj = null;
        try {
            if (joinPoint.getArgs().length != 0) {
                //方法执行
                responseObj = joinPoint.proceed();
                //方法返回就解密
                handleDecrypt(responseObj);
            }

        } catch (NoSuchMethodException e) {
            e.printStackTrace();
            log.error("SecureFieldAop处理出现异常{}", e);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            log.error("SecureFieldAop处理出现异常{}", throwable);
        }
        return responseObj;
    }

    /**
     * 处理加密
     *
     * @param requestObj
     */
    private void handleEncrypt(Object requestObj) {
        if (Objects.isNull(requestObj)) {
            return;
        }
        //获取入参对象的字段
        Field[] fields = requestObj.getClass().getDeclaredFields();
        // 对所有字段进行遍历找出添加注解的字段
        Arrays.stream(fields).filter(field -> field.isAnnotationPresent(EncryptField.class)).forEach(field -> {
            // accessible 标志被设置为true,那么反射对象在使用的时候,不会去检查Java语言权限控制(private之类的)
            field.setAccessible(true);
            try {
                // 获取字段值
                Object plaintextValue = field.get(requestObj);
                // 策略模式的实现,根据字段值类型在map中找到对应的实现类
                EncryptComparator encryptComparator = (EncryptComparator) map.get(plaintextValue.getClass().getName()+"&"+encryptMode);
                // 调用实现类的加密方法
                Object encryptValue = encryptComparator.encrypt(plaintextValue);
                // 加密后的字段重新赋值
                field.set(requestObj, encryptValue);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        });
    }

    /**
     * 处理解密
     *
     * @param responseObj
     */
    private Object handleDecrypt(Object responseObj) throws IllegalAccessException {
        if (Objects.isNull(responseObj)) {
            return null;
        }
        Field[] fields = responseObj.getClass().getDeclaredFields();
        Arrays.stream(fields).filter(field -> field.isAnnotationPresent(EncryptField.class)).forEach(field -> {
            field.setAccessible(true);
            try {
                Object plaintextValue = field.get(responseObj);
                EncryptComparator encryptComparator = (EncryptComparator) map.get(plaintextValue.getClass().getName()+"&"+encryptMode);
                Object encryptValue = encryptComparator.decrypt(plaintextValue);
                field.set(responseObj, encryptValue);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        });
        return responseObj;
    }

}
  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
好的,针对您的问题,我可以给您一些简单的解释。 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`是一个加解密服务的接口,具体的加解密实现可以根据自己的需求进行编写。 希望以上解释可以帮助到您。如果还有其他问题,欢迎随时提出。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

紫荆之后-

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

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

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

打赏作者

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

抵扣说明:

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

余额充值