涉及隐私信息的字段需要加密存储数据库,返回给前端时又需要解密显示正确信息。故采用mybatis自动加解密的方案,该方案基于自定义注解+拦截器进行实现。加密后的信息不支持模糊匹配(可参考业界流行方案,基于业务需求做分词或采用其他方案以支持模糊匹配,本文不涉及)。
网上的解决方案会有以下问题(已在代码中完善解决,不再细述):
1.对于对象List中的需加密属性没有加密
2.重复加密,导致密文有误
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.10</version>
</dependency>
//加解密工具类
public class EncryptDecryptUtil {
private final static Logger logger = LoggerFactory.getLogger(EncryptDecryptUtil.class);
/**
* 对称加密的秘钥
*/
private final static String key = "加密密钥";
/**
* * 加密 *
* * @param declaredFields paramsObject所声明的字段
* * @param paramsObject mapper中paramsType的实例
* * @return T
* * @throws IllegalAccessException 字段不可访问异常
*/
public static <T> T encrypt(Field[] declaredFields, T paramsObject) throws IllegalAccessException {
for (Field field : declaredFields) { //取出所有被EncryptDecryptField注解的字段
EncryptDecryptField sensitiveField = field.getAnnotation(EncryptDecryptField.class);
if (!Objects.isNull(sensitiveField)) {
field.setAccessible(true);
Object object = field.get(paramsObject); //暂时只实现String类型的加密
if (object instanceof String) {
String value = (String) object; //加密 Des加密工具
String encryptHexStr = SecureUtil.des(key.getBytes()).encryptHex(value);
//logger.info(value+"加密后是"+encryptHexStr);
field.set(paramsObject, encryptHexStr);
}
}
}
return paramsObject;
}
/**
* 解密
* * @param result resultType的实例
* * @return T
* * @throws IllegalAccessException 字段不可访问异常
*/
public static <T> T decrypt(T result) throws IllegalAccessException {
//取出resultType的类
Class<?> resultClass = result.getClass();
Field[] declaredFields = resultClass.getDeclaredFields();
for (Field field : declaredFields) {
//取出所有被EncryptDecryptField注解的字段
EncryptDecryptField sensitiveField = field.getAnnotation(EncryptDecryptField.class);
if (!Objects.isNull(sensitiveField)) {
field.setAccessible(true);
Object object = field.get(result); //只支持String的解密
if (object instanceof String) {
String value = (String) object;
//对注解的字段进行逐一解密
String decryptStr = SecureUtil.des(key.getBytes()).decryptStr(value);
//logger.info("{}字段需要解密,{}解密后的值是{}",field.getName(),value,decryptStr);
field.set(result, decryptStr);
}
}
}
return result;
}
}
/**
* 该注解定义在类上
* 插件通过扫描类对象是否包含这个注解来决定是否继续扫描其中的字段注解
* 这个注解要配合EncryptDecryptField注解
**/
@Inherited
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptDecryptClass {
}
/**
* 该注解有两种使用方式
* ①:配合@EncryptDecryptClass加在类中的字段上
* ②:直接在Mapper中的方法参数上使用
**/
@Inherited
@Target({ ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptDecryptField {
}
//写入数据拦截器
@Intercepts({
@Signature(type = ParameterHandler.class,
method = "setParameters",
args = PreparedStatement.class)
})
public class WriteInterceptor implements Interceptor {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public Object intercept(Invocation invocation) throws Throwable {
ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
Object testParame=parameterHandler.getParameterObject();
Field parameterField = parameterHandler.getClass().getDeclaredField("parameterObject");
parameterField.setAccessible(true); //取出实例
Object parameterObject = parameterField.get(parameterHandler);
if (parameterObject != null) {
Map<String,Object> test= BeanUtil.beanToMap(parameterObject);
Collection values = test.values();
Set<Object> setObj = new LinkedHashSet<>(values);
for (Object value : setObj) {
if(value instanceof List){
for(Object item : (List<?>) value){
Class<?> parameterObjectClass = item.getClass();
//校验该实例的类是否被EncryptDecryptClass所注解
EncryptDecryptClass encryptDecryptClass =
AnnotationUtils.findAnnotation(parameterObjectClass, EncryptDecryptClass.class);
if (Objects.nonNull(encryptDecryptClass)) {
//取出当前当前类所有字段,传入加密方法
Field[] declaredFields = parameterObjectClass.getDeclaredFields();
EncryptDecryptUtil.encrypt(declaredFields, item);
}
}
}else {
Class<?> parameterObjectClass = value.getClass();
//校验该实例的类是否被EncryptDecryptClass所注解
EncryptDecryptClass encryptDecryptClass =
AnnotationUtils.findAnnotation(parameterObjectClass, EncryptDecryptClass.class);
if (Objects.nonNull(encryptDecryptClass)) {
//取出当前当前类所有字段,传入加密方法
Field[] declaredFields = parameterObjectClass.getDeclaredFields();
EncryptDecryptUtil.encrypt(declaredFields, value);
}
}
}
}
return invocation.proceed();
}
@Override
public Object plugin(Object o) {
//这里必须写入,会判定是否把当前拦截器启动
return Plugin.wrap(o, this);
}
}
//读取数据拦截器
@Intercepts({
@Signature(type = ResultSetHandler.class,
method = "handleResultSets",
args = {Statement.class})})
public class ReadInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
//取出查询的结果
Object resultObject = invocation.proceed();
if (Objects.isNull(resultObject)) {
return null;
}
//基于selectList
if (resultObject instanceof ArrayList) {
ArrayList resultList = (ArrayList) resultObject;
if (!CollectionUtils.isEmpty(resultList) && needToDecrypt(resultList.get(0))) {
for (Object result : resultList) {
//逐一解密
EncryptDecryptUtil.decrypt(result);
}
}
//基于selectOne
} else {
if (needToDecrypt(resultObject)) {
EncryptDecryptUtil.decrypt(resultObject);
}
}
return resultObject;
}
private boolean needToDecrypt(Object object) {
Class<?> objectClass = object.getClass();
EncryptDecryptClass sensitiveData =
AnnotationUtils.findAnnotation(objectClass, EncryptDecryptClass.class);
return Objects.nonNull(sensitiveData);
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
}
//配置自定义的mybatis拦截器
@Configuration
@Component
public class MybatisCryptoConfig {
@Bean
public ReadInterceptor readInterceptorPlugin(){
return new ReadInterceptor();
}
@Bean
public WriteInterceptor writeInterceptorPlugin(){
return new WriteInterceptor();
}
}
在涉及需要加解密的实体类上使用@EncryptDecryptClass注解,在需要加解密的属性上使用@EncryptDecryptField注解。
/**
* 管理员信息
*/
@Data
@EncryptDecryptClass
public class SysAdminVo {
/**
* 管理员id
* @mock 123455
*/
private String admin_id;
/**
* 管理员姓名
* @mock 张三
*/
private String admin_name;
/**
* 管理员手机号;
* @mock 13112341234
*/
@EncryptDecryptField
private String admin_phone;
}
在需要自动加解密的时候,请使用已配置注解的实体类作为入参或出参,否则不会进行加解密转换。
参考链接: