场景
应公司安全部门要求,需要对数据库敏感数据进行加密存储(第一期只包含证件号&手机号)。
由于这是一个技改类需求,与业务无关,我们考虑用自定义注解+aop来做(orm用mybatis,aop选用Aspectj)。这样做对业务代码没有侵入,并且后期扩展非常方便。
code
NeedEncryption
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.TYPE, ElementType.FIELD})
@Documented
public @interface NeedEncryption {
}
DataEncryptionAspect
import org.apache.commons.lang.StringUtils;
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.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
@Aspect
@Component
public class DataEncryptionAspect {
@Pointcut(value = "execution(* com.tbryant.mapper.*.*(..))")
public void encryptAndDecrypt() {
}
@Around("encryptAndDecrypt()")
public Object handle(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
// before
Object[] args = proceedingJoinPoint.getArgs();
for (Object arg : args) {
// 集合类型目前只支持List,处理其他类型需要扩展
if (arg instanceof List) {
for (Object obj : (List) arg) {
doEncrypt(obj, true);
}
} else {
doEncrypt(arg, true);
}
}
// proceed
Object result;
result = proceedingJoinPoint.proceed(args);
// after
// 集合类型目前只支持List,处理其他类型需要扩展
if (result instanceof List) {
for (Object obj : (List) result) {
doEncrypt(obj, false);
}
} else {
doEncrypt(result, false);
}
return result;
}
private void doEncrypt(Object obj, Boolean isEncrypt) throws Exception {
if (null != obj) {
Class objClazz = obj.getClass();
if (objClazz.isAnnotationPresent(NeedEncryption.class)) {
Field[] clazzFields = objClazz.getDeclaredFields();
for (Field field : clazzFields) {
// 需要脱敏的字段类型目前只支持String
if ("class java.lang.String".equals(field.getGenericType().toString()) && field.isAnnotationPresent(NeedEncryption.class)) {
Method getFieldMethod = objClazz.getMethod("get" + getMethodName(field.getName()));
Method setFieldMethod = objClazz.getMethod("set" + getMethodName(field.getName()), String.class);
String fieldValue = (String) getFieldMethod.invoke(obj);
if (StringUtils.isNotBlank(fieldValue)) {
setFieldMethod.invoke(obj, handle(fieldValue, isEncrypt));
}
}
}
}
}
}
private String handle(String content, Boolean isEncrypt) throws Exception {
if (StringUtils.isBlank(content)) {
return content;
}
// 加解密算法可自行替换,对称非对称都可以
return isEncrypt ? DESUtils.encrypt(content) : DESUtils.decrypt(content);
}
private static String getMethodName(String fildeName) {
byte[] items = fildeName.getBytes();
items[0] = (byte) ((char) items[0] - 'a' + 'A');
return new String(items);
}
}
使用
由于是非侵入式设计,所以使用起来非常方便。找到需要加密的字段,在该PO和该字段上添加@NeedEncryption即可。
为何PO上也要添加注解:主要是为了提升效率,如果PO上没有@NeedEncryption,那就直接跳过加密处理。