Mybatis使用拦截器实现数据的加解密
实现思路
- mybatis配置类中加入自定义拦截器
- 自定义拦截器对指定mapper层指定方法(修改或新增相关业务表的方法)进行拦截,对指定属性或字段进行加解密操作
实现代码
// Copyright 2016-2101 Pica.
package com.pica.cloud.patient.health.server.annotation;
import com.google.common.collect.Lists;
import com.pica.cloud.foundation.entity.PicaWarnException;
import com.pica.cloud.patient.health.common.enums.HealthResultCode;
import com.pica.cloud.patient.health.common.utils.EncryptUtils;
import com.pica.cloud.patient.health.server.entity.*;
import com.pica.cloud.patient.health.server.model.patient.PatDocPatModel;
import com.pica.cloud.patient.health.server.util.EmojiFilterUtil;
import org.apache.commons.lang.StringUtils;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import java.lang.reflect.Field;
import java.util.*;
/**
* 加密patient patientInfo中的昵称和身份证号
*
* @ClassName EncryptNameIdInterceptor
* @Description
* @Author Chongwen.jiang
* @Date 2019/4/3 11:31
* @ModifyDate 2019/4/3 11:31
* @Version 1.0
*/
@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
public class EncryptNameIdInterceptor implements Interceptor {
private static final List<String> handlerMethod = Lists.newArrayList(
"com.pica.cloud.patient.server.mapper.PatPatientInfoMapper.updateByPrimaryKeySelective",
"com.pica.cloud.patient.server.mapper.PatPatientInfoMapper.insertBatchPatientInfo",
"com.pica.cloud.patient.server.mapper.PatPatientInfoMapper.updateByExampleSelective",
"com.pica.cloud.patient.server.mapper.PatPatientMapper.insertBatchPatientBasic",
"com.pica.cloud.patient.server.mapper.PatHospitalPatInfoMapper.insertBatch",
"com.pica.cloud.patient.server.mapper.PatHospitalPatInfoMapper.batchUpdate",
"com.pica.cloud.patient.server.mapper.PatDocPatInfoMapper.insertBatch"
);
@Override
public Object intercept(Invocation invocation) throws Throwable {
final MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
Executor executor = (Executor) invocation.getTarget();
Object parameter;
boolean isEncrypted = false;
parameter = invocation.getArgs()[1];
BoundSql boundSql = mappedStatement.getBoundSql(parameter);
//只处理patientInfo 和patient 的mapper里面相关的sql
if (parameter instanceof PatPatientInfo || parameter instanceof PatPatient || parameter instanceof PatientInfo
|| parameter instanceof PatHospitalPatInfo || parameter instanceof PatDocPatMapping
|| parameter instanceof PatDocPatInfo || parameter instanceof PatDocPatModel
|| handlerMethod.contains(mappedStatement.getId())) {
Object obj = boundSql.getParameterObject();
// 加密处理
isEncrypted = dealPatientValue(obj, true, parameter);
}
Object result = executor.update(mappedStatement, parameter);
if (isEncrypted) {
//防止多次使用同一个对象,导致字段被多次加密
Object obj = boundSql.getParameterObject();
dealPatientValue(obj, false, parameter);
}
return result;
}
private boolean dealPatientValue(Object obj, boolean encryptFlag, Object parameter) throws IllegalAccessException {
boolean isEncrypted = false;
if (obj != null) {
if (parameter instanceof Map) {
Map paramMap = (Map) parameter;
Set<Object> objectSet = new HashSet<>();
for (Object key : paramMap.keySet()) {
Object map = paramMap.get(key);
if (map != null && objectSet.add(map)) {
if (map.getClass().getName().equals("java.util.ArrayList")) {
for (Object param : (ArrayList) map) {
isEncrypted = encryptFiled(param.getClass(), param, encryptFlag);
}
} else {
isEncrypted = encryptFiled(map.getClass(), map, encryptFlag);
}
}
}
objectSet.clear();
} else {
isEncrypted = encryptFiled(parameter.getClass(), obj, encryptFlag);
}
}
return isEncrypted;
}
private boolean encryptFiled(Class<?> clazz, Object object, boolean encryptFlag) throws IllegalAccessException {
boolean isEncrypted = false;
//考虑继承的情况
for (; clazz != Object.class; clazz = clazz.getSuperclass()) {
Field[] declaredFields = clazz.getDeclaredFields();
{
for (Field field : declaredFields) {
//加密居民姓名字段
if ("nickname".equals(field.getName().toLowerCase())) {
field.setAccessible(true);
String nickname = (String) field.get(object);
if (StringUtils.isNotEmpty(nickname)) {
if (encryptFlag) {
//过滤emoji表情
nickname = EmojiFilterUtil.filterEmoji(nickname);
if (StringUtils.isEmpty(nickname)) {
throw new PicaWarnException(HealthResultCode.NICKNAME_EMPTY.code(), HealthResultCode.NICKNAME_EMPTY.message());
}
field.set(object, EncryptUtils.encryptNickname(nickname));
isEncrypted = true;
} else {
field.set(object, EncryptUtils.decryptNickname(nickname));
}
}
}
//加密居民身份证号字段
if ("idno".equals(field.getName().toLowerCase())) {
field.setAccessible(true);
String idNo = (String) field.get(object);
if (encryptFlag) {
field.set(object, EncryptUtils.encryptIdNo(idNo));
isEncrypted = true;
} else {
field.set(object, EncryptUtils.decryptIdNo(idNo));
}
}
}
}
}
return isEncrypted;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
// Copyright 2016-2101 Pica.
package com.pica.cloud.patient.health.server.annotation;
import com.pica.cloud.patient.health.common.dto.DocSmsEntityModel;
import com.pica.cloud.patient.health.common.dto.SmsRecordPatient;
import com.pica.cloud.patient.health.common.utils.EncryptUtils;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.util.ReflectionUtils;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Properties;
/**
* @ClassName QueryPatientInterceptor
* @Description 解密查询到的居民数据(居民姓名和身份证号)
* @Author Chongwen.jiang
* @Date 2019/4/3 17:59
* @ModifyDate 2019/4/3 17:59
* @Version 1.0
*/
@Intercepts({@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})})
public class QueryPatientInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object[] args = invocation.getArgs();
MappedStatement ms = (MappedStatement) args[0];
Object parameter = args[1];
RowBounds rowBounds = (RowBounds) args[2];
ResultHandler resultHandler = (ResultHandler) args[3];
Executor executor = (Executor) invocation.getTarget();
CacheKey cacheKey;
BoundSql boundSql;
if (args.length == 4) {
//4 个参数时
boundSql = ms.getBoundSql(parameter);
cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
} else {
//6 个参数时
cacheKey = (CacheKey) args[4];
boundSql = (BoundSql) args[5];
}
List<?> list = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
if (list!=null){
for (Object obj : list) {
if (obj != null) {
decryptPatientValue(obj);
}
}
}
return list;
}
private void decryptPatientValue(Object obj) throws IllegalAccessException {
if (obj instanceof DocSmsEntityModel) {
DocSmsEntityModel info = (DocSmsEntityModel) obj;
info.setPatientName(EncryptUtils.decryptNickname(info.getPatientName()));
} else if (obj instanceof SmsRecordPatient) {
SmsRecordPatient info = (SmsRecordPatient) obj;
info.setName(EncryptUtils.decryptNickname(info.getName()));
} else {
Field[] fields = obj.getClass().getDeclaredFields();
for (Field field : fields) {
if ("nickname".equals(field.getName().toLowerCase())) {
field.setAccessible(true);
String nickname = (String) ReflectionUtils.getField(field, obj);
field.set(obj, EncryptUtils.decryptNickname(nickname));
}
if ("idno".equals(field.getName().toLowerCase())) {
field.setAccessible(true);
String idNo = (String) ReflectionUtils.getField(field, obj);
field.set(obj, EncryptUtils.decryptIdNo(idNo));
}
}
}
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
将拦截器注入mybatis配置类中:
// Copyright 2016-2101 Pica.
package com.pica.cloud.patient.health.server.configuration;
import com.pica.cloud.patient.health.server.annotation.EncryptNameIdInterceptor;
import com.pica.cloud.patient.health.server.annotation.QueryPatientInterceptor;
import org.mybatis.spring.boot.autoconfigure.ConfigurationCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @ClassName MybatisConfig
* @Description mybatis配置类
* @Author Chongwen.jiang
* @Date 2019/4/3 10:21
* @ModifyDate 2019/4/3 10:21
* @Version 1.0
*/
@Configuration
public class MybatisConfiguration {
@Bean
public ConfigurationCustomizer configurationCustomizer() {
return configuration -> {
//修改或新增时加密居民姓名、身份证号
configuration.addInterceptor(new EncryptNameIdInterceptor());
//查询时解密居民姓名、身份证号
configuration.addInterceptor(new QueryPatientInterceptor());
};
}
}
// Copyright 2016-2101 Pica.
package com.pica.cloud.patient.health.common.utils;
import com.pica.cloud.foundation.utils.utils.StringUtil;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang3.StringUtils;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Arrays;
/**
* @ClassName EncryptUtils
* @Description 使用AES对称加密 (common-codec)
* @Author Chongwen.jiang
* @Date 2019/1/11 10:53
* @ModifyDate 2019/1/11 10:53
* @Version 1.0
*/
public class EncryptUtils {
public static final String COMMON_ID_CARD_KEY = "!abcedf#sadfsd";
public static final String COMMON_NICKNAME_KEY = "%gGdfadae^DFD";
public static String encryptAES(String key, String value) {
try {
byte[] keyBytes = Arrays.copyOf(key.getBytes("ASCII"), 16);
SecretKey keyStr = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, keyStr);
byte[] cleartext = value.getBytes("UTF-8");
byte[] ciphertextBytes = cipher.doFinal(cleartext);
return new String(Hex.encodeHex(ciphertextBytes));
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static String decryptAES(String key, String encrypted) {
try {
byte[] keyBytes = Arrays.copyOf(key.getBytes("ASCII"), 16);
SecretKey keyStr = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, keyStr);
byte[] content = Hex.decodeHex(encrypted.toCharArray());
byte[] ciphertextBytes = cipher.doFinal(content);
return new String(ciphertextBytes);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static String encryptNickname(String value) {
return StringUtil.isNull(value) ? null : encryptAES(COMMON_NICKNAME_KEY, value.trim());
}
public static String encryptIdNo(String value) {
return StringUtil.isNull(value) ? null : encryptAES(COMMON_ID_CARD_KEY, value.trim());
}
public static String decryptNickname(String value) {
if (StringUtils.isNotEmpty(value)) {
String value2 = decryptAES(COMMON_NICKNAME_KEY, value);
value = StringUtils.isEmpty(value2) ? value : value2;
}
return value;
}
public static String decryptIdNo(String value) {
if (StringUtils.isNotEmpty(value)) {
String value2 = decryptAES(COMMON_ID_CARD_KEY, value);
value = StringUtils.isEmpty(value2) ? value : value2;
}
return value;
}
public static void main(String[] args) {
System.out.println(encryptIdNo("331521199508172351"));
System.out.println(encryptNickname("哈哈"));
}
}
mysql加解密函数
AES_ENCRYPT(str,key)
返回用密钥key对字符串str利用高级加密标准算法加密后的结果,调用AES_ENCRYPT的结果是一个二进制字符串,以BLOB类型存储
AES_DECRYPT(str,key) 返回用密钥key对字符串str利用高级加密标准算法解密后的结果
DECODE(str,key) 使用key作为密钥解密加密字符串str
ENCRYPT(str,salt) 使用UNIXcrypt()函数,用关键词salt(一个可以惟一确定口令的字符串,就像钥匙一样)加密字符串str
ENCODE(str,key) 使用key作为密钥加密字符串str,调用ENCODE()的结果是一个二进制字符串,它以BLOB类型存储
MD5() 计算字符串str的MD5校验和
PASSWORD(str) 返回字符串str的加密版本,这个加密过程是不可逆转的,和UNIX密码加密过程使用不同的算法。
SHA() 计算字符串str的安全散列算法(SHA)校验和
示例:
SELECT ENCRYPT(‘root’,‘salt’);
SELECT ENCODE(‘xufeng’,‘key’);
SELECT DECODE(ENCODE(‘xufeng’,‘key’),‘key’);#加解密放在一起
SELECT AES_ENCRYPT(‘root’,‘key’);
SELECT AES_DECRYPT(AES_ENCRYPT(‘root’,‘key’),‘key’);
SELECT MD5(‘123456’);
SELECT SHA(‘123456’);
select AES_DECRYPT(UNHEX(‘AEE1FE30ED93AA2246A249715FFF04BA’),’%gGdfadae^DFD’) from pat_patient
where id=12991809 and delete_flag=1;
SELECT HEX(AES_ENCRYPT(‘老十一’,’%gGdfadae^DFD’)) FROM pat_patient_info WHERE patient_id=12669605 and delete_flag=1;