利用Mybatis拦截器+反射机制,设计加解密注解,可以对特定字段入库出库时,实现自动加解密。
①拦截器原理详见Mybatis插件系列一:拦截器的基础知识
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EncryptionAndDecryption {
String value() default "";
}
@Slf4j
public abstract class EncryptionAndDecryptionPlugin implements Interceptor {
public EncryptionAndDecryptionPlugin() {
}
@Override
public Object intercept(Invocation invocation) throws Throwable {
//log.info("Begin SQL Plugin");
if (invocation.getArgs() != null && invocation.getArgs().length > 1) {
MappedStatement statement = (MappedStatement) invocation.getArgs()[0];
String methodName = invocation.getMethod().getName();
Object parameter = invocation.getArgs()[1];
BoundSql sql = statement.getBoundSql(parameter);
//log.info("sql is {}", sql.getSql());
// Handle input parameters
if (parameter != null) {
List<Field> inputFields = getAnnotationFields(parameter.getClass(), EncryptionAndDecryption.class);
if (inputFields.size() > 0) {
log.info("################ methodName: {}", methodName);
if (methodName.equals("query") || methodName.equals("update")) {
for (Field field : inputFields) {
if (field.getType().equals(String.class)) {
field.setAccessible(true);
String plaintextValue = (String) field.get(parameter);
if (!StringUtil.isEmpty(plaintextValue)) {
String ciphertextValue = encryptInner(plaintextValue, parameter, field.getName());
field.set(parameter, ciphertextValue);
}
}
}
}
}
}
}
Object returnValue = invocation.proceed();
// if resul data has EncryptionAndDecryption annotation, clear this local cache
boolean isClearLocalCache = false;
try {
//Handle output parameters
if (returnValue instanceof ArrayList<?>) {
log.info("################ result-list: {}", ((ArrayList) returnValue).size());
List<?> list = (ArrayList<?>) returnValue;
for (Object val : list) {
if (val == null) {
continue;
}
List<Field> outputFields = getAnnotationFields(val.getClass(), EncryptionAndDecryption.class);
if (outputFields.size() > 0) {
isClearLocalCache = true;
for (Field field : outputFields) {
if (field.getType().equals(String.class)) {
field.setAccessible(true);
String cipheredValue = (String) field.get(val);
if (StringUtils.isNotBlank(cipheredValue)) {
String plaintextValue = decryptInner(cipheredValue, val, field.getName());
field.set(val, plaintextValue);
}
}
}
}
}
} else {
log.info("################ result: {}", returnValue);
}
} finally {
if (isClearLocalCache) {
// clear local cache
Object target = invocation.getTarget();
if (target instanceof CachingExecutor) {
CachingExecutor executor = (CachingExecutor)target;
executor.clearLocalCache();
}
}
}
return returnValue;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
private List<Field> getAnnotationFields(Class entityCls, Class annotationCls) {
ArrayList list = new ArrayList();
try {
Field[] arr$ = entityCls.getDeclaredFields();
int len$ = arr$.length;
for(int i$ = 0; i$ < len$; ++i$) {
Field field = arr$[i$];
if (field.isAnnotationPresent(annotationCls)) {
try {
list.add(field);
} catch (Throwable var9) {
log.error("################ CheckAnnotation failed!",var9);
}
}
}
} catch (SecurityException var10) {
log.error("################ CheckAnnotation failed!",var10);
}
return list;
}
protected abstract String encryptInner(String plaintext, Object entityObj, String attributeName);
protected abstract String decryptInner(String cipheredValue, Object entityObj, String attributeName);
}
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class,
RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class,
RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class})
})
@Component
@Slf4j
public class MyPlugin extends EncryptionAndDecryptionPlugin {
@Resource
private DeviceCryptoService deviceCryptoService;
/**
* 密钥别名
*/
@Value("${wyaks.aksAliasName}")
private String aliasName;
@Override
protected String encryptInner(String plaintext, Object entityObj, String attributeName) {
log.info("encryptInner begin,attributeName:" + attributeName);
if (StringUtils.isBlank(plaintext)) {
log.info("encryptInner empty,attributeName:" + attributeName);
return plaintext;
}
return encrypt(plaintext);
}
@Override
protected String decryptInner(String cipheredValue, Object entityObj, String attributeName) {
log.info("decryptInner begin,attributeName:" + attributeName);
if (StringUtils.isBlank(cipheredValue)) {
log.info("decryptInner empty,attributeName:" + attributeName);
return cipheredValue;
}
return decrypt(cipheredValue);
}
public String encrypt(String plaintext) {
log.info("加密数据:{}", plaintext);
String encryptData = null;
try {
encryptData = deviceCryptoService.encryptString(aliasName, plaintext.getBytes("UTF-8"));
} catch (Exception e) {
log.error("加密数据异常:" + e.toString(), e);
throw new RuntimeException("加密异常");
}
log.info("加密数据返回:{}", encryptData);
return encryptData;
}
public String decrypt(String cipheredValue) {
log.info("解密数据:{}", cipheredValue);
String decryptData = null;
try {
decryptData = new String(deviceCryptoService.decryptString(aliasName, cipheredValue), "UTF-8");
} catch (Exception e) {
log.error("解密数据异常:" + e.toString(), e);
throw new RuntimeException("解密异常");
}
log.info("解密数据返回:{}", decryptData);
return decryptData;
}
}