Java 注解实现字段加密和解密

前言
一般对防止数据泄露有两种方式,第一种是在数据库层面去做加密,还有一种就是在应用层去加密。数据库层面做数据加密的是最安全的,实现需要对驱动做操作,略显复杂,如果对保密程度不是非常高,可以在应用层面去实现数据加解密。下面将基于应用层面结合aop去实现数据加解密功能。

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.6.5</version>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

2.涉及基本的类

package com.ex.demo.entity;

import com.ex.demo.config.Decrypt;
import lombok.Data;
import java.util.List;

/**
 * @author lyu
 * @version: 1.0
 * @create 2024-12-06 15:48
 */
@Data
public class Temp {

    @Decrypt
    private String decryptText;

    private int id;

    private List<Order> orders;
}
package com.ex.demo.entity;


import com.baomidou.mybatisplus.annotation.TableName;
import com.ex.demo.config.Decrypt;
import lombok.Builder;
import lombok.Data;

import java.math.BigDecimal;

/**
 * @author lyu
 * @description:
 * @create 2024-08-26 15:05
 */
@Data
@Builder(toBuilder = true)
@TableName(value ="t_order")
public class Order {
    private Integer qty;

    private BigDecimal amount;

    @Decrypt
    private String decryptTxt;

    private String msg;

    private Parse parse;


}
package com.ex.demo.entity;

import com.ex.demo.config.Decrypt;
import com.ex.demo.config.Encrypt;
import lombok.Data;

/**
 * @author lyu
 * @version: 1.0
 * @create 2024-12-06 16:39
 */
@Data
public class Parse {

    @Encrypt
    private String encryptTxt;
    @Decrypt
    private String decryptCls;
}

3.定义加解密注解

package com.ex.demo.config;

import java.lang.annotation.*;

/**
 * @author lyu
 * @version: 1.0 解密
 * @create 2024-12-06 15:01
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Decrypt {
}

package com.ex.demo.config;

import java.lang.annotation.*;

/**
 * @author lyu
 * @version: 1.0 加密
 * @create 2024-12-06 15:00
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Encrypt {
}

4.加解密核心处理逻辑

package com.ex.demo.desens;


import com.ex.demo.config.Decrypt;
import com.ex.demo.config.Encrypt;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 加解密的核心处理逻辑,可通过DesensitizationAdapter属性指定加解密算法
 * 可扩展 CLAZZ 属性去忽略要检测的对象
 * @author lyu
 * @version: 1.0
 * @create 2024-12-09 09:32
 */
public class DesensitizationHandler {


    private static final DesensitizationAdapter DEFAULT_ADAPTER = new Base64DesensitizationAdapter();

    private final DesensitizationAdapter desensitizationAdapter;


    // 以下类型的不需要被检测
    private static List<Class<?>> CLAZZ =
            Arrays.asList(Date.class, ServletResponse.class, ServletRequest.class, Map.class, Number.class,
                    Serializable.class, MultipartFile.class);

    /**
     * 可指定加密适配器和忽略的类型
     * @param desensitizationAdapter 加密类型
     * @param classes 忽略加密的方法
     */
    public DesensitizationHandler(DesensitizationAdapter desensitizationAdapter, List<Class<?>> classes){
        this(desensitizationAdapter);
        if(classes!=null && !classes.isEmpty()){
            CLAZZ = new ArrayList<>(CLAZZ);
            CLAZZ.addAll(classes);
        }
    }

    public DesensitizationHandler() {
        this(null);
    }

    public DesensitizationHandler(DesensitizationAdapter desensitizationAdapter) {
        this.desensitizationAdapter = desensitizationAdapter == null ? DEFAULT_ADAPTER : desensitizationAdapter;
    }



    public void desensitizationProcessor(Object arg, boolean decrypt) throws IllegalAccessException {
        if (arg == null) {
            return;
        }
        Class<?> clazz = arg.getClass();
        if (clazz.isArray()) {
            //按照array处理
            List<Object> list = Arrays.stream((Object[]) arg).collect(Collectors.toList());
            processorList(list, decrypt);
        } else if (List.class.isAssignableFrom(clazz)) {
            List<?> list = (List<?>) arg;
            processorList(list, decrypt);
        } else {
            desensitizationProcessorProxy(arg, decrypt);
        }
    }


    private void desensitizationProcessorProxy(Object arg, boolean decrypt) throws IllegalAccessException {
        Class<?> clazz = arg.getClass();
        if (!classMatch(clazz)) {
            Field[] declaredFields = clazz.getDeclaredFields();
            for (Field declaredField : declaredFields) {
                Class<?> fieldType = declaredField.getType();
                if (!classMatch(fieldType)) {
                    // 确保可以访问私有字段
                    declaredField.setAccessible(true);
                    if (fieldType == String.class) {
                        handleStringField(arg, declaredField, decrypt);
                    } else if (List.class.isAssignableFrom(fieldType)) {
                        List<?> list = (List<?>) declaredField.get(arg);
                        processorList(list, decrypt);
                    } else if (fieldType.isArray()) {
                        List<Object> list = Arrays.stream((Object[]) arg).collect(Collectors.toList());
                        processorList(list, decrypt);
                    } else {
                        Object fieldValue = declaredField.get(arg);
                        if (fieldValue != null) {
                            desensitizationProcessor(fieldValue, decrypt);
                        }
                    }
                }
            }
        }
    }

    private void processorList(List<?> list, boolean decrypt) throws IllegalAccessException {
        if (list != null) {
            for (Object item : list) {
                if (item != null) {
                    desensitizationProcessorProxy(item, decrypt);
                }
            }
        }
    }

    /**
     * 基于base64 加解密,可替换成对称加密:AES DES 非对称加密:RSA DSA 等加密方式
     *
     * @param arg           arg
     * @param declaredField filed
     * @param decrypt       解密
     * @throws IllegalAccessException 异常
     */
    private void handleStringField(Object arg, Field declaredField, boolean decrypt) throws IllegalAccessException {
        String str = (String) declaredField.get(arg);
        if (StringUtils.isNotEmpty(str)) {
            if (decrypt) {
                Decrypt annotation = declaredField.getAnnotation(Decrypt.class);
                if (annotation != null) {
                    String decodedStr = desensitizationAdapter.decrypt(str);
                    declaredField.set(arg, decodedStr);
                }
            } else {
                Encrypt annotation = declaredField.getAnnotation(Encrypt.class);
                if (annotation != null) {
                    String encodedStr = desensitizationAdapter.encrypt(str);
                    declaredField.set(arg, encodedStr);
                }
            }
        }
    }


    public static boolean classMatch(Class<?> cls) {
        return ClassUtils.isPrimitiveOrWrapper(cls) || CLAZZ.stream().anyMatch(elm -> elm.isAssignableFrom(cls));
    }
}


5.加解密接口,通过实现此接口,注入到DesensitizationHandler可实现指定加解密功能

package com.ex.demo.desens;

/**
 * @author lyu
 * @version: 1.0
 * @create 2024-12-09 09:32
 */
public interface DesensitizationAdapter {


    /**
     * 解密
     * @param str 密文
     * @return 明文
     */
    String decrypt(String str);

    /**
     *  加密
     * @param str 明文
     * @return 密文
     */
    String encrypt(String str);
}

6.AES加密和base64加密

package com.ex.demo.desens;


import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.security.SecureRandom;
import java.util.Base64;

/**
 * @author lyu
 * @version: 1.0
 * @create 2024-12-09 09:40
 */
public class AesDesensitizationAdapter implements DesensitizationAdapter {
    private static final String DEFAULT_KEY = "15SwdprzXjNo3hFpaI/azA==";


    private final String secretKey;

    public AesDesensitizationAdapter() {
        this(null);
    }

    public AesDesensitizationAdapter(String key) {
        this.secretKey = (key == null || key.isEmpty()) ? DEFAULT_KEY : key;
    }

    @Override
    public String decrypt(String str) {
        try {
            // 密钥base64解码
            byte[] secretKeyBytes = Base64.getDecoder().decode(secretKey);
            // AES专用密钥
            Key aesKey = new SecretKeySpec(secretKeyBytes, "AES");
            // 创建密码器
            Cipher cipher = Cipher.getInstance("AES");
            // 初始化密码器
            cipher.init(Cipher.DECRYPT_MODE, aesKey);
            // base64解码,加密内容转为字节数组
            byte[] contentBytes = Base64.getDecoder().decode(str);
            // 解密
            byte[] result = cipher.doFinal(contentBytes);
            return new String(result);
        } catch (Exception e) {
            return null;
        }
    }

    @Override
    public String encrypt(String str) {
        try {
            // 密钥base64解码
            byte[] secretKeyBytes = Base64.getDecoder().decode(secretKey);
            // AES专用密钥
            Key aesKey = new SecretKeySpec(secretKeyBytes, "AES");
            // 创建密码器
            Cipher cipher = Cipher.getInstance("AES");
            // 初始化密码器
            cipher.init(Cipher.ENCRYPT_MODE, aesKey);
            // 加密
            byte[] secretBytes = cipher.doFinal(str.getBytes(StandardCharsets.UTF_8));
            // base64编码,加密内容转为字符串
            return Base64.getEncoder().encodeToString(secretBytes);
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 生成密钥
     *
     * @param key 根据Key生成密钥
     * @return 字符串格式的密钥
     */
    public static String generateKey(String key) {
        try {
            // 创建AES密码器
            KeyGenerator keygen = KeyGenerator.getInstance("AES");
            // 获取安全随机数
            SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
            // 根据加密key的字节数组设置安全随机数的种子
            secureRandom.setSeed(key.getBytes());
            // 根据安全随机数初始化128位的密码器
            keygen.init(128, secureRandom);
            // 密码器生成密钥
            SecretKey secretKey = keygen.generateKey();
            // 密钥的字节数组
            byte[] secretKeyBytes = secretKey.getEncoded();
            // base64编码,密钥转为字符串
            return Base64.getEncoder().encodeToString(secretKeyBytes);
        } catch (Exception e) {
            return null;
        }
    }


}

package com.ex.demo.desens;

import cn.hutool.core.codec.Base64Decoder;
import cn.hutool.core.codec.Base64Encoder;

/**
 * @author lyu
 * @version: 1.0
 * @create 2024-12-09 09:37
 */
public class Base64DesensitizationAdapter implements DesensitizationAdapter{
    @Override
    public String decrypt(String str) {
        return Base64Decoder.decodeStr(str);
    }

    @Override
    public String encrypt(String str) {
        return Base64Encoder.encode(str);
    }
}

7.Aop 切面实现加解密功能

package com.ex.demo.config;

import cn.hutool.json.JSONUtil;
import com.ex.demo.desens.DesensitizationHandler;
import com.ex.demo.entity.Order;
import com.ex.demo.entity.Parse;
import com.ex.demo.entity.Temp;
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 java.math.BigDecimal;
import java.util.*;

/**
 * @author lyu
 * @version: 1.0 加解密
 * @create 2024-12-06 15:00
 */
@Aspect
@Component
@Slf4j
public class DataDesensitizationAop {


    @Pointcut("execution(public * com.ex.demo.controller..*.*(..))")
    public void entryPoint() {

    }

    @Around("entryPoint()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        DesensitizationHandler desensitizationHandler = new DesensitizationHandler();
        Object[] args = pjp.getArgs();
        desensitizationHandler.desensitizationProcessor(args, true);
        Object proceed = pjp.proceed();
        desensitizationHandler.desensitizationProcessor(proceed, false);
        return proceed;
    }

    public static void main(String[] args) throws IllegalAccessException {
        Temp temp = new Temp();
        temp.setDecryptText("YmFzZTY0");

        ArrayList<Order> list = new ArrayList<>();
        Parse parse = new Parse();
        parse.setDecryptCls("YmFzZTY0");
        parse.setEncryptTxt("base64");
        Order build = Order.builder().decryptTxt("YmFzZTY0").msg("YmFzZTY0").amount(new BigDecimal("2.45"))
                .qty(20).parse(parse).build();
        list.add(build);
        temp.setOrders(list);
        System.out.println(JSONUtil.toJsonStr(temp));
        new DesensitizationHandler().desensitizationProcessor(temp, false);
        System.out.println(JSONUtil.toJsonStr(temp));
    }
}

8.SpringBoot 测试

package com.ex.demo.controller;

import cn.hutool.json.JSONUtil;
import com.ex.demo.entity.Order;
import com.ex.demo.entity.Temp;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import java.math.BigDecimal;

@EnableRetry
@RestController
public class DemoController {

    @PostMapping("/data")
    public Temp test(@RequestBody Temp temp){
        System.out.println(JSONUtil.toJsonStr(temp));
        return temp;
    }
}

  1. 结果
输入
{"orders":[{"msg":"YmFzZTY0","amount":2.45,"decryptTxt":"YmFzZTY0","parse":{"decryptCls":"YmFzZTY0","encryptTxt":"base64"},"qty":20}],"id":0,"decryptText":"YmFzZTY0"}
中间
{"orders":[{"msg":"YmFzZTY0","amount":2.45,"decryptTxt":"base64","parse":{"decryptCls":"base64","encryptTxt":"base64"},"qty":20}],"id":0,"decryptText":"base64"}
输出
{"orders":[{"qty":20,"amount":2.45,"decryptTxt":"base64","msg":"YmFzZTY0","parse":{"encryptTxt":"YmFzZTY0","decryptCls":"base64"}}],"id":0,"decryptText":"base64"}

可以看到被Encrypt注解修饰的字段都被加密了,被Decrypt注解修饰的字段被解密了。

posted @ 2024-12-06 18:32  lyu6  阅读(674)  评论(0)    收藏  举报