本文已参与「新人创作礼」活动,一起开启掘金创作之路。
## 引言
因为业务需求的变更,最近接到了一个关于在最小改变原有代码基础上,通过注解去对敏感数据入库出库的加解密的需求。
1. 设置aes加密方法
import org.springframework.stereotype.Component;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
/**
* author:
* @Param
* @return
**/
@Component
public class ADESUtils implements EncrptUtils {
private static final String defaultV = "6859505890402435";
private static final String key = "1061697007556132";
private static final String head = "aes_";
private static SecretKeySpec getKey(String strKey) throws Exception {
byte[] arrBTmp = strKey.getBytes();
byte[] arrB = new byte[16]; // 创建一个空的16位字节数组(默认值为0)
for (int i = 0; i < arrBTmp.length && i < arrB.length; i++) {
arrB[i] = arrBTmp[i];
}
SecretKeySpec skeySpec = new SecretKeySpec(arrB, "AES");
return skeySpec;
}
/**
* 加密.
* @param content
* @return
* @throws Exception
*/
public String encrypt(String content) throws Exception {
final Base64.Encoder encoder = Base64.getEncoder();
SecretKeySpec skeySpec = getKey(key);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec iv = new IvParameterSpec(defaultV.getBytes());
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
byte[] encrypted = cipher.doFinal(content.getBytes());
String encodedText = (head + encoder.encodeToString(encrypted));
return encodedText;
}
/**
* 判断加密.
* @param content
* @return
* @throws Exception
*/
public boolean isEncrypt(String content) throws Exception {
String decrypt = content.substring(0, 4);
if (decrypt.equals(head)) {
return true;
}
return false;
}
/**
* 解密.
* @param content
* @return
* @throws Exception
*/
public String decrypt(String content) throws Exception {
final Base64.Decoder decoder = Base64.getDecoder();
SecretKeySpec skeySpec = getKey(key);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec iv = new IvParameterSpec(defaultV.getBytes());
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
byte[] base64 = decoder.decode(content.substring(4));
byte[] original = cipher.doFinal(base64);
String originalString = new String(original);
return originalString;
}
}
** 2.对象加解密**
import com.copote.comb.common.annotate.DecryptField;
import com.copote.comb.common.annotate.EncryptField;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.util.Objects;
/**
* author:lxw.
* @Param
* @return
**/
@Component
public class CryptPojoUtils {
@Value("${key.rule}")
private String ruler;
@Autowired
private EncrptUtils encrptUtils;
/**
* 对对象t加密.
* @return
*/
public <T> T encrypt(Field[] declaredFields, T paramsObject) throws IllegalAccessException {
for (Field field : declaredFields) {
//取出所有被EncryptTransaction注解的字段
EncryptField encryptTransaction = field.getAnnotation(EncryptField.class);
if (!Objects.isNull(encryptTransaction)) {
field.setAccessible(true);
Object object = field.get(paramsObject);
//暂时只实现String类型的加密
if (object instanceof String) {
String value = (String) object;
//加密
try {
field.set(paramsObject, encrptUtils.encrypt(value));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
return paramsObject;
}
/**
* 对象解密.
* @param <T>
* @return
*/
public <T> T decrypt(T result) throws Exception {
//取出resultType的类
Class<?> resultClass = result.getClass();
Field[] declaredFields = resultClass.getDeclaredFields();
for (Field field : declaredFields) {
//取出所有被DecryptTransaction注解的字段
DecryptField decryptTransaction = field.getAnnotation(DecryptField.class);
if (!Objects.isNull(decryptTransaction)) {
field.setAccessible(true);
Object object = field.get(result);
//String的解密
if (object instanceof String) {
String value = (String) object;
if (encrptUtils.isEncrypt(value)) {
//对注解的字段进行逐一解密
try {
field.set(result, encrptUtils.decrypt(value));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
return result;
}
}
3.设置注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* author:lxw.
* @Param
* @return
**/
@Inherited
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface CryptAnnotation {
String value() default "";
}
/**
* author:lxw.
* 解密注解
*/
@Documented
@Inherited
@Target({ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface DecryptField {
String value() default "";
}
/**
* author:lxw.
* 加密注解
*/
@Documented
@Inherited
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptField {
String value() default "";
}
4.入库拦截对象加密
import com.copote.comb.common.annotate.CryptAnnotation;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.sql.PreparedStatement;
import java.util.List;
import java.util.Objects;
import java.util.Properties;
/**
* 数据库更新操作拦截器.
* 一、支持的使用场景
* ①场景一:通过mybatis-plus BaseMapper自动映射的方法
* ②场景一:通过mapper接口自定义的方法,更新对象为自定义DO
* 二、使用方法
* ①在自定义的DO上加上注解 CryptAnnotation
* ②在需要加解密的字段属性上加上CryptAnnotation
* @author: lxw
*/
@Component
@Intercepts(value =
{@Signature(type = ParameterHandler.class// 确定要拦截的对象
, method = "setParameters", args = PreparedStatement.class// 拦截方法的参数
) })
public class UpdateInterceptor implements Interceptor {
@Autowired
private CryptPojoUtils cryptService;
@Override
public Object intercept(Invocation invocation) throws Throwable {
//@Signature 指定了 type= parameterHandler 后,这里的 invocation.getTarget() 便是parameterHandler
//若指定ResultSetHandler ,这里则能强转为ResultSetHandler
ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
// 获取参数对像,即 mapper 中 paramsType 的实例
Field parameterField = parameterHandler.getClass().getDeclaredField("parameterObject");
parameterField.setAccessible(true);
//取出实例
Object parameterObject = parameterHandler.getParameterObject();
if (parameterObject != null) {
if (parameterObject instanceof List) {
List<Object> list = (List)parameterObject;
if (list.size() != 0) {
for (int i = 0; i < list.size(); i++) {
Object obj = list.get(i);
Class<?> objectClass = obj.getClass();
//校验该实例的类是否被@SensitiveData所注解
CryptAnnotation sensitiveData = AnnotationUtils.findAnnotation(objectClass, CryptAnnotation.class);
if (Objects.nonNull(sensitiveData)) {
Field[] declaredFields = objectClass.getDeclaredFields();
cryptService.encrypt(declaredFields, obj);
}
}
}
} else {
Class<?> parameterObjectClass = parameterObject.getClass();
//校验该实例的类是否被@SensitiveData所注解
CryptAnnotation sensitiveData = AnnotationUtils.findAnnotation(parameterObjectClass, CryptAnnotation.class);
if (Objects.nonNull(sensitiveData)) {
//取出当前当前类所有字段,传入加密方法
Field[] declaredFields = parameterObjectClass.getDeclaredFields();
cryptService.encrypt(declaredFields,parameterObject);
}
}
}
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
5.出库拦截解密
import com.copote.comb.common.annotate.CryptAnnotation;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Objects;
import java.util.Properties;
/**
* 查询拦截器.
* 查询条件加密使用方式:使用 @Param("decrypt")注解的自定义类型
* 返回结果解密使用方式: ①在自定义的DO上加上注解 CryptAnnotation ②在需要加解密的字段属性上加上CryptAnnotation
* @author: lxw
*/
@Component
@Intercepts({
@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
})
public class QueryInterceptor implements Interceptor {
@Autowired
private CryptPojoUtils encryptDecrypt;
@Autowired
private ADESUtils adesUtils;
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object result = invocation.proceed();
if (Objects.isNull(result)) {
return null;
}
if (result instanceof ArrayList) {
ArrayList resultList = (ArrayList) result;
if (resultList.size() != 0) {
for (int i = 0; i < resultList.size(); i++) {
if (needToDecrypt(resultList.get(i))) {
encryptDecrypt.decrypt(resultList.get(i));
}
}
}
} else {
if (needToDecrypt(result)) {
encryptDecrypt.decrypt(result);
}
}
return result;
}
public boolean needToDecrypt(Object object) {
Class<?> parameterObjectClass = object.getClass();
CryptAnnotation sensitiveData = AnnotationUtils.findAnnotation(parameterObjectClass, CryptAnnotation.class);
if (Objects.nonNull(sensitiveData)) {
return true;
}
return false;
}
@Override
public Object plugin(Object o) {
return Plugin.wrap(o, this);
}
@Override
public void setProperties(Properties properties) {
}
}
6.对需要拦截实体类添加注解
@Data
@CryptAnnotation
public class OpenProgramOfflineEntity implements Serializable {
@ApiModelProperty(value = "业务值key")
@EncryptField
@Deprecated
private String busiKey;
@ApiModelProperty(value = "业务名")
private String busiName;
@ApiModelProperty(value = "业务值")
private String busiValue;
}