@Auto-Annotation自定义注解——数据加密篇

@Auto-Annotation自定义注解——数据加密篇

自定义通用注解连更系列—连载中…

首页介绍:点这里

前言

​ 前段时间在做密码测评时,遇到一个这样的业务场景,业务系统中的数据需要进行加密存储,在数据传输过程中进行加密传输。这就麻烦了,这数据可不光要加密,肯定也要解密呀,这再加上个传输加解密以及存储加解密,想想都麻烦,于是便想有什么办法能够写一套通用的代码来处理这种业务场景呢。

​ 力求便捷,最先想到的便是自定义注解了,那么我能不能通过定义一个注解来标识哪些接口需要加解密传输,哪些字段需要加解密存储呢。于是数据加密篇便油然而生。

​ 本章加解密实现,主要精确为字段级的加解密处理。

所需依赖

        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-jdk15to18</artifactId>
            <version>1.69</version>
        </dependency>
         <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
          <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.10</version>
        </dependency>

自定义加解密注解@EncryptionFields

加密方法标记注解,做为一个接口加解密标记的作用

/** 加密方法标记注解 标记需加密的接口
 *  与@EncryptionFields连用
 * @Author: 清峰
 * @Description: May there be no bug in the world!
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EncryptionMethod {
}

数据存储字段加解密注解,做为对字段加解密处理标识符。

  • 标识参数是否加解密存储
  • 标识结果是否加解密返回
/** 数据存储字段加解密注解
 * 与@EncryptionMethod连用
 * @Author: 清峰
 * @Description: May there be no bug in the world!
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EncryptionFields {
    /**
     * 是否将接收到的参数解密存储
     */
    boolean decryptStorage() default false;
    /**
     * 是否将接收到的参数加密存储
     */
    boolean encryptStorage() default false;

    /**
     * 是否将查询到的结果解密返回
     */
    boolean decryptReturn() default false;
    /**
     * 是否将查询到的结果加密返回
     */
    boolean encryptReturn() default false;
}	

加解密配置类

定义一些配置信息,如:采用哪种加密方式,加密密钥等信息

/**
 * @Author: 清峰
 * @Description: May there be no bug in the world!
 */
@Data
@Configuration
@ConfigurationProperties(prefix = "auto.encrypt")
public class EncryptTypeConfig {
    /**
     * 加密方式
     */
    EncryptTypeEnum encryptType = EncryptTypeEnum.PLAIN;
    /**
     * 请求头公共密钥名称(非对称加密使用)
     */
    String pubKeyName;
    /**
     * 私有密钥key
     */
    String priKey;

}	

加解密切面类

​ 该类做为加解密核心类,主要对参数进行加解密处理

主要实现逻辑

​ 方法执行前,对参数进行加解密存储

  • 参数检查,只支持String类型的参数进行加解密处理
  • 是否将接收到的参数解密存储(根据值中{xxx},获取到加密方式进行解密操作)
  • 是否将接收到的参数加密存储(加密数据根据配置的加密规则操作,并把规则{xxx}拼接)
  • 根据{xxx}中提供的加密方式,获取加解密对象,执行其实现类方法对数据进行加解密处理

​ 方法执行后对结果数据加解密传输

  • 参数检查,只支持String类型的参数进行加解密处理
  • 是否将返回的结果解密返回(根据值中{xxx},获取到加密方式进行解密操作)
  • 是否将查询到的结果加密返回(加密数据根据配置的加密规则操作,并把规则{xxx}拼接)
  • 根据{xxx}中提供的加密方式,获取加解密对象,执行其实现类方法对数据进行加解密处理
/**
 * @Author: 清峰
 * @Description: May there be no bug in the world!
 */
@Slf4j
@Aspect
@Configuration
@ConditionalOnProperty(prefix = ConditionalConstants.AUTO_ENABLE,name = ConditionalConstants.ENCRYPTION_DATA,havingValue =ConditionalConstants.ENABLE_TRUE)
public class EncryptionAspect {

    @Resource
    private EncryptTypeConfig encryptTypeConfig;
    @Resource
    private List<IEncryptTypeStrategy> encryptTypeStrategyList;


    @Around(value = "@annotation(encryptionMethod)")
    public Object aroundFields(ProceedingJoinPoint jp, EncryptionMethod encryptionMethod) throws Throwable {

        //1、方法执行前加密
        //获取参数
        Object[] args = jp.getArgs();
        for (Object arg : args) {
            //获取属性
            Field[] declaredFields = arg.getClass().getDeclaredFields();
            for (Field declaredField : declaredFields) {
                //获取属性上的注解
                EncryptionFields encryptionField = declaredField.getAnnotation(EncryptionFields.class);
                if (!ObjectUtils.isEmpty(encryptionField)) {
                    //检查并获取属性值
                    Object value = checkFieldValue(arg, declaredField);
                    if (ObjectUtils.isEmpty(value)){
                        continue;
                    }
                    //是否将接收到的参数解密存储
                    if (encryptionField.decryptStorage()) {
                        decryptData(arg, declaredField, value);
                    }
                    //是否将接收到的参数加密存储
                    if (encryptionField.encryptStorage()) {
                        encryptData(arg, declaredField, value);
                    }
                }
            }
        }
        //2、方法执行
        Object result = jp.proceed();
        //3、方法执行后加解密
        if (!ObjectUtils.isEmpty(result)) {
            Field[] declaredFields = result.getClass().getDeclaredFields();
            for (Field declaredField : declaredFields) {
                //获取属性上的注解
                EncryptionFields encryptionField = declaredField.getAnnotation(EncryptionFields.class);
                if (!ObjectUtils.isEmpty(encryptionField)) {
                    //检查并获取属性值
                    Object value = checkFieldValue(result, declaredField);
                    if (ObjectUtils.isEmpty(value)){
                        continue;
                    }
                    //是否将查询到的结果解密返回
                    if (encryptionField.decryptReturn()) {
                        decryptData(result, declaredField, value);
                    }
                    //是否将查询到的结果加密返回
                    if (encryptionField.encryptReturn()) {
                        encryptData(result, declaredField, value);
                    }
                }
            }
        }

        return result;
    }

    /**
     * 检查并获取字段属性值
     *
     * @param obj           参数对象
     * @param declaredField 字段属性对象
     * @return 结果集
     * @throws ServerException        服务异常
     * @throws IllegalAccessException 非法访问异常
     */
    private Object checkFieldValue(Object obj, Field declaredField) throws IllegalAccessException, ServerException {
        declaredField.setAccessible(true);
        String name = declaredField.getName();
        Object value = declaredField.get(obj);
        if (ObjectUtils.isEmpty(value)){
            return null;
        }
        if (!(value instanceof String)) {
            throw new ServerException("参数类型有误非字符类型:" + name);
        }
        return value;
    }

    /**
     * 加密数据(加密根据配置的加密规则操作,并把规则{xxx}拼接)
     *
     * @param obj           参数对象
     * @param declaredField 字段属性对象
     * @param value         属性值
     * @throws ServerException        服务异常
     * @throws IllegalAccessException 非法访问异常
     */
    private void encryptData(Object obj, Field declaredField, Object value) throws ServerException, IllegalAccessException {
        String key = encryptTypeConfig.getPriKey();
        EncryptTypeEnum encryptType = encryptTypeConfig.getEncryptType();
        IEncryptTypeStrategy encryptTypeStrategy = getEncryptTypeStrategy(encryptType);
        String encryptStr = encryptTypeStrategy.encrypt(String.valueOf(value), key);
        String encryptValue = StrPool.DELIM_START + encryptType + StrPool.DELIM_END + encryptStr;
        declaredField.set(obj, encryptValue);
    }

    /**
     * 解密数据 (根据值中{xxx},获取到加密方式进行解密操作)
     *
     * @param obj           参数对象
     * @param declaredField 字段属性对象
     * @param value         属性值
     * @throws ServerException        服务异常
     * @throws IllegalAccessException 非法访问异常
     */
    private void decryptData(Object obj, Field declaredField, Object value) throws ServerException, IllegalAccessException {
        String key = encryptTypeConfig.getPriKey();
        EncryptTypeDTO encryptTypeDTO = splitEncryptType(String.valueOf(value));
        IEncryptTypeStrategy encryptTypeStrategy = getEncryptTypeStrategy(encryptTypeDTO.getEncryptType());
        String decryptStr = encryptTypeStrategy.decrypt(encryptTypeDTO.getEncryptValue(), key);
        declaredField.set(obj, decryptStr);
    }

    /**
     * 根据加密方式获取加密策略者
     *
     * @param encryptType 加密方式
     * @return 结果集
     * @throws ServerException 服务异常
     */
    private IEncryptTypeStrategy getEncryptTypeStrategy(EncryptTypeEnum encryptType) throws ServerException {
        IEncryptTypeStrategy encryptTypeStrategy = encryptTypeStrategyList.stream()
                .filter(x -> x.isSupport().equals(encryptType))
                .findFirst().orElse(null);
        if (ObjectUtils.isEmpty(encryptTypeStrategy)) {
            throw new ServerException("未找到该加密方式的实现");
        }
        return encryptTypeStrategy;
    }

    /**
     * 获取加密数据中的加密方式({PLAIN}xxssdfsd)
     *
     * @param str 加密数据
     * @return 结果集
     * @throws ServerException 服务异常
     */
    private EncryptTypeDTO splitEncryptType(String str) throws ServerException {
        String[] split = str.split(StrPool.DELIM_END);
        String encryptType = split[0].substring(1);
        String encryptValue = split[1];
        EncryptTypeEnum encryptTypeEnum = EncryptTypeEnum.valueOf(encryptType);
        if (ObjectUtils.isEmpty(encryptTypeEnum)) {
            throw new ServerException("加密参数类型有误");
        }
        return EncryptTypeDTO.builder().encryptType(encryptTypeEnum).encryptValue(encryptValue).build();
    }
}

定义加解密接口

定义加解密接口规范

  • 是否支持该加密方式
  • 加密数据
  • 解密数据
/**
 * 加密接口
 *
 * @Author: 清峰
 * @Description: May there be no bug in the world!
 */
public interface IEncryptTypeStrategy {
    /**
     * 是否支持该加密方式
     *
     * @return 结果集
     */
    EncryptTypeEnum isSupport();

    /**
     * 加密数据
     *
     * @param str         原始数据
     * @param key         公钥key(非对称加密使用)
     * @return 结果集
     */
    String encrypt(String str, String key);

    /**
     * 解密数据
     *
     * @param str 已加密数据
     * @param key 公钥key(非对称加密使用)
     * @return 结果集
     */
    String decrypt(String str, String key);
}

加密方式一:明文加密

不对数据做任何加解密处理,明文存储与传输

/** 明文加密
 * @Author: 清峰
 * @Description: May there be no bug in the world!
 */
@Component
public class PlainEncryptTypeAction implements IEncryptTypeStrategy{

    @Override
    public EncryptTypeEnum isSupport() {
        return EncryptTypeEnum.PLAIN;
    }

    @Override
    public String encrypt(String str, String key) {

        return str;
    }

    @Override
    public String decrypt(String str, String key) {
        return str;
    }
}

加密方式二:AES加解密

​ AES加密/解密算法是一种可逆的对称加密算法,这类算法在加密和解密时使用相同的密钥,或是使用两个可以简单地相互推算的密钥,一般用于服务端对服务端之间对数据进行加密/解密。

​ 作为可逆且对称的块加密,AES加密算法的速度比公钥加密等加密算法快很多,在很多场合都需要AES对称加密,但是要求加密端和解密端双方都使用相同的密钥是AES算法的主要缺点之一。

/**
 * @Author: 清峰
 * @Description: May there be no bug in the world!
 */
@Component
public class AesEncryptTypeAction implements IEncryptTypeStrategy{

   private AES aes;

    @Resource
    private EncryptTypeConfig encryptTypeConfig;

    @PostConstruct
    private void constructAes(){
        SecretKey secretKey = SecureUtil.generateKey(EncryptTypeEnum.AES.getValue(),
                encryptTypeConfig.getPriKey().getBytes(StandardCharsets.UTF_8));
         this.aes = SecureUtil.aes(secretKey.getEncoded());
    }

    @Override
    public EncryptTypeEnum isSupport() {
        return EncryptTypeEnum.AES;
    }

    @Override
    public String encrypt(String str, String key) {
        str = aes.encryptBase64(str);
        return str;
    }

    @Override
    public String decrypt(String str, String key) {
        str = aes.decryptStr(str);
        return str;
    }

    public static void main(String[] args) {
        SecretKey aes = KeyUtil.generateKey(EncryptTypeEnum.AES.getValue(),128);
        String key = HexUtil.encodeHexStr(aes.getEncoded());
        System.out.println(key);
    }
}

加密方式三:SM2加解密

​ SM2算法是中国国家密码局推出的国产化算法,是基于椭圆曲线的非对称算法,相对于RSA算法,SM2具有密钥更小,运算速度更快,相同密钥长度下具有更高安全性等优势。

/**
 * @Author: 清峰
 * @Description: May there be no bug in the world!
 * 私钥:5b27a37cf7305146c062f4b59b257d1888e9f5e65c2017b9c48556aaf27d9f5b
 * 公钥:02bb7b3cf323f53921f3cf2d05131144d7378c10ceb2fa5bfe3756643722f252a6
 */
@Component
public class Sm2EncryptTypeAction implements IEncryptTypeStrategy{

    @Resource
    private EncryptTypeConfig encryptTypeConfig;

    @Override
    public EncryptTypeEnum isSupport() {
        return EncryptTypeEnum.SM2;
    }

    @Override
    public String encrypt(String str, String key) {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        String publicKey = request.getHeader(encryptTypeConfig.getPubKeyName());
        str = Sm2Utils.encrypt(publicKey, str);
        return str;
    }

    @Override
    public String decrypt(String str, String key) {
        str = Sm2Utils.decrypt(encryptTypeConfig.getPriKey(), str);
        return str;
    }
}

标记加解密接口与属性

   
    @EncryptionFields(decryptStorage = true,encryptStorage = true,decryptReturn = true,encryptReturn = true)
    private String name;
    
    @EncryptionFields(decryptStorage = false,encryptStorage = true,decryptReturn = false,encryptReturn = true)
    private String age;

    @EncryptionMethod
    @PostMapping("saveUser")
    private void saveUser(User user){
        System.out.println("保存用户信息逻辑...");
    }

总结

​ 通过加密技术保证信息的机密性、完整性、鉴别性和不可否认性,使用相应的密钥解密后显示出加密前的内容,使信息只对允许可读的接收者可读,以防止私有化信息在网络中被拦截和窃取。

​ 实现数据加解密时只需要确定注解的作用位置和运行时机,以上只是对数据加解密处理的简单实现,我这里抛砖引玉出一个实现思路,当然还可以对其进行扩展,如:考虑对不同数据类型的处理,扩展各种加解密方式DES、SM4等。后续通过不断的迭代优化,形成一套比较完整的加解密处理机制。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值