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;
}
}
- 结果
输入
{"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注解修饰的字段被解密了。

浙公网安备 33010602011771号