基于Mybatis拦截器实现数据脱敏
2018-09-10 本文已影响405人
汪先森出版社
前言
纯干货 纯干货 纯干货
简介
数据脱敏(保形加密)与数据加密的区别
1、脱敏兼顾数据安全与数据使用,采用专业的数据脱敏算法;而加密则是通过对数据进行编码来保护数据,检索原始值的唯一方法是使用解密密钥解码数据。
2、脱敏数据仍然便于使用,但加密数据不是。
3、加密优点在于它的可逆性,但是解密密钥存储位置、如何存储以及确定谁具有访问权限等工作都会给整个安全工作增加额外的成本、故障点,加剧复杂性。
加密
脱敏
对于手机号和身份证号码此类数据利用脱敏的形式,能够达到数据安全防护与数据挖掘或分析两重目的。平衡了可用性与机密性。例如:我依然可以使用like来查询。
实现方案
实现方案上源码
注解定义
/**
* 加密字段注解
*
* @author wangzhuhua
* @date 2018/09/04 下午5:19
**/
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface CryptField {
CryptTypeEnum value() default CryptTypeEnum.AES;
}
/**
* 验证类型枚举
*
* @author wangzhuhua
* @date 2018/09/04 下午5:20
**/
public enum CryptTypeEnum {
/** AES加密(这个可是加密,不是脱敏) */
AES,
/** 手机号 */
@Deprecated
PhoneNumber,
/** 身份证 */
@Deprecated
IdCard,
@Deprecated
/** 银行卡 */
BankCard
}
脱敏算法接口
/**
* 脱敏算法接口定义
*
* @author wangzhuhua
* @date 2018/09/04 下午5:34
**/
public interface Crypt {
/**
* 加密
*
* @param plain
* 原始明文
* @return 密文
*/
String encrypt(String plain);
/**
* 解密
*
* @param cipher
* 密文
* @return 原始明文
*/
String decrypt(String cipher);
}
/**
* 脱敏方式上下文
*
* @author wangzhuhua
* @date 2018/09/04 下午5:45
**/
public class CryptContext {
private static Map<CryptTypeEnum, Crypt> Crypts = new HashMap<>(CryptTypeEnum.values().length);
/**
* 获取加密方式
*
* @param cryptTypeEnum
* 加密方式枚举
* @return 机密方式实现类
*/
public static Crypt getCrypt(CryptTypeEnum cryptTypeEnum) {
Crypt crypt = Crypts.get(cryptTypeEnum);
if (crypt == null) {
crypt = ..get(CryptTypeEnum.AES);
}
return crypt;
}
/**
* 设置加密方式
*
* @param cryptTypeEnum
* 加密类型
* @param crypt
* 加载方式
*/
public static void setCrypt(CryptTypeEnum cryptTypeEnum, Crypt crypt) {
Crypts.put(cryptTypeEnum, crypt);
}
}
/**
* 脱敏实现类加载器(先简单实现)
*
* @author wangzhuhua
* @date 2018/09/04 下午6:22
**/
public class CryptLoader {
/**
* 加载所有加密方式实现类
*/
public void loadCrypt() {
CryptContext.setCrypt(CryptTypeEnum.AES, new AESCryptImpl());
}
}
脱敏算法实现 (略......)
Mybatis 拦截器(干货)
/**
* 基于Mybatis拦截器的脱敏实现
*
* @author wangzhuhua
* @date 2018/09/04 下午7:10
**/
@Intercepts(value = {
@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, CacheKey.class, BoundSql.class }),
@Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,
RowBounds.class, ResultHandler.class }) })
public class CryptInterceptor implements Interceptor {
/** 参数注解缓存 */
private static final ConcurrentHashMap<String, Map<String, CryptField>> PARAM_ANNOTATIONS_MAP = new ConcurrentHashMap<>();
/** 返回值注解缓存 */
private static final ConcurrentHashMap<String, CryptField> RETURN_ANNOTATIONS_MAP = new ConcurrentHashMap<>();
/** 参数名解析器 */
private final ParameterNameDiscoverer parameterNameDiscoverer = new MyBatisParameterNameDiscoverer();
public CryptInterceptor() {
(new CryptLoader()).loadCrypt();
}
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object[] args = invocation.getArgs();
// 入参
Object parameter = args[1];
MappedStatement statement = (MappedStatement) args[0];
// 判断是否需要解析
if (!isNotCrypt(parameter)) {
Map<String, CryptField> cryptFieldMap = getParameterAnnotations(statement);
// 单参数 string
if (parameter instanceof String && !cryptFieldMap.isEmpty()) {
args[1] = stringEncrypt(cryptFieldMap.keySet().iterator().next(), (String) parameter,
getParameterAnnotations(statement));
// 单参数 list
} else if (parameter instanceof DefaultSqlSession.StrictMap) {
DefaultSqlSession.StrictMap<Object> strictMap = (DefaultSqlSession.StrictMap<Object>) parameter;
for (Map.Entry<String, Object> entry : strictMap.entrySet()) {
if (entry.getKey().contains("collection")) {
continue;
}
if (entry.getKey().contains("list")) {
listEncrypt((List) entry.getValue(), cryptFieldMap.get(entry.getKey()));
}
}
// 多参数
} else if (parameter instanceof MapperMethod.ParamMap) {
MapperMethod.ParamMap<Object> paramMap = (MapperMethod.ParamMap<Object>) parameter;
// 解析每一个参数
for (Map.Entry<String, Object> entry : paramMap.entrySet()) {
// 判断不需要解析的类型 不解析map
if (isNotCrypt(entry.getValue()) || entry.getValue() instanceof Map
|| entry.getKey().contains("param")) {
continue;
}
// 如果string
if (entry.getValue() instanceof String) {
entry.setValue(stringEncrypt(entry.getKey(), (String) entry.getValue(), cryptFieldMap));
continue;
}
// 如果 list
if (entry.getValue() instanceof List) {
listEncrypt((List) entry.getValue(), cryptFieldMap.get(entry.getKey()));
continue;
}
beanEncrypt(entry.getValue());
}
// bean
} else {
beanEncrypt(parameter);
}
}
// 获得出参
Object returnValue = invocation.proceed();
// 出参解密
if (isNotCrypt(returnValue)) {
return returnValue;
}
// 获得方法注解(针对返回值)
CryptField cryptField = getMethodAnnotations(statement);
if (returnValue instanceof String) {
return stringDecrypt((String) returnValue, cryptField);
}
if (returnValue instanceof List) {
listDecrypt((List) returnValue, cryptField);
return returnValue;
}
return returnValue;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
/**
* 获取 方法上的注解
*
* @param statement
* MappedStatement
* @return 方法上的加密注解 {@link CryptField}
* @throws ClassNotFoundException
*/
private CryptField getMethodAnnotations(MappedStatement statement) throws ClassNotFoundException {
String id = statement.getId();
CryptField cryptField = RETURN_ANNOTATIONS_MAP.get(id);
if (cryptField != null) {
return cryptField;
}
// 获取执行方法
Method method = null;
final Class clazz = Class.forName(id.substring(0, id.lastIndexOf(".")));
for (Method _method : clazz.getDeclaredMethods()) {
if (_method.getName().equals(id.substring(id.lastIndexOf(".") + 1))) {
method = _method;
break;
}
}
if (method == null) {
return null;
}
return method.getAnnotation(CryptField.class);
}
/**
* 获取 方法参数上的注解
*
* @param statement
* MappedStatement
* @return 参数名与其对应加密注解
* @throws ClassNotFoundException
*/
private Map<String, CryptField> getParameterAnnotations(MappedStatement statement) throws ClassNotFoundException {
// 执行ID
final String id = statement.getId();
Map<String, CryptField> cryptFieldMap = PARAM_ANNOTATIONS_MAP.get(id);
if (cryptFieldMap != null) {
return cryptFieldMap;
} else {
cryptFieldMap = new HashMap<>();
}
// 获取执行方法
Method method = null;
final Class clazz = Class.forName(id.substring(0, id.lastIndexOf(".")));
for (Method _method : clazz.getDeclaredMethods()) {
if (_method.getName().equals(id.substring(id.lastIndexOf(".") + 1))) {
method = _method;
break;
}
}
if (method == null) {
return cryptFieldMap;
}
// 获取参数名称
String[] paramNames = parameterNameDiscoverer.getParameterNames(method);
// 获取方法参数注解列表
final Annotation[][] paramAnnotations = method.getParameterAnnotations();
// 填充参数注解
for (int i = 0; i < paramAnnotations.length; i++) {
Annotation[] paramAnnotation = paramAnnotations[i];
for (Annotation annotation : paramAnnotation) {
if (annotation instanceof CryptField) {
cryptFieldMap.put(paramNames[i], (CryptField) annotation);
break;
}
}
}
// 存入缓存
PARAM_ANNOTATIONS_MAP.put(id, cryptFieldMap);
return cryptFieldMap;
}
/**
* 判断是否需要加解密
*
* @param obj
* 待加密对象
* @return 是否需要加密
*/
private boolean isNotCrypt(Object obj) {
return obj == null || obj instanceof Double || obj instanceof Integer || obj instanceof Long
|| obj instanceof Boolean;
}
/**
* String 加密
*
* @param name
* 参数名称
* @param plain
* 参数明文
* @param paramAnnotations
* 加密注解
* @return 密文
*/
private String stringEncrypt(String name, String plain, Map<String, CryptField> paramAnnotations) {
return stringEncrypt(plain, paramAnnotations.get(name));
}
/**
* String 加密
*
* @param plain
* 参数明文
* @param cryptField
* 加密注解
* @return 密文
*/
private String stringEncrypt(String plain, CryptField cryptField) {
if (StringUtils.isBlank(plain) || cryptField == null) {
return plain;
}
return CryptContext.getCrypt(cryptField.value()).encrypt(plain);
}
/**
* String 解密
*
* @param cipher
* 参数密文
* @param cryptField
* 加密注解
* @return 明文
*/
private String stringDecrypt(String cipher, CryptField cryptField) {
if (StringUtils.isBlank(cipher) || cryptField == null) {
return cipher;
}
return CryptContext.getCrypt(cryptField.value()).decrypt(cipher);
}
/**
* list 加密
*
* @param plainList
* 明文列表
* @param cryptField
* 加密方式注解
* @return 密文列表
* @throws IllegalAccessException
*/
private List listEncrypt(List plainList, CryptField cryptField) throws IllegalAccessException {
for (int i = 0; i < plainList.size(); i++) {
Object plain = plainList.get(i);
// 判断不需要解析的类型
if (isNotCrypt(plain) || plain instanceof Map) {
break;
}
if (plain instanceof String) {
plainList.set(i, stringEncrypt((String) plain, cryptField));
continue;
}
beanEncrypt(plain);
}
return plainList;
}
/**
* list 解密
*
* @param cipherList
* 密文列表
* @param cryptField
* 加密方式注解
* @return 明文列表
* @throws IllegalAccessException
*/
private List listDecrypt(List cipherList, CryptField cryptField) throws IllegalAccessException {
for (int i = 0; i < cipherList.size(); i++) {
Object cipher = cipherList.get(i);
// 判断不需要解析的类型
if (isNotCrypt(cipher) || cipher instanceof Map) {
break;
}
if (cipher instanceof String) {
cipherList.set(i, stringDecrypt((String) cipher, cryptField));
continue;
}
beanDecrypt(cipher);
}
return cipherList;
}
/**
* bean 加密
*
* @param plainObject
* 明文对象
* @throws IllegalAccessException
*/
private void beanEncrypt(Object plainObject) throws IllegalAccessException {
Class objClazz = plainObject.getClass();
Field[] objFields = objClazz.getDeclaredFields();
for (Field field : objFields) {
CryptField cryptField = field.getAnnotation(CryptField.class);
if (cryptField != null) {
field.setAccessible(true);
Object plain = field.get(plainObject);
if (plain == null) {
continue;
}
if (field.getType().equals(String.class)) {
field.set(plainObject, stringEncrypt((String) plain, cryptField));
continue;
}
if (field.getType().equals(List.class)) {
field.set(plainObject, listEncrypt((List) plain, cryptField));
continue;
}
field.setAccessible(false);
}
}
}
/**
* bean 解密
*
* @param cipherObject
* 密文对象
* @throws IllegalAccessException
*/
private void beanDecrypt(Object cipherObject) throws IllegalAccessException {
Class objClazz = cipherObject.getClass();
Field[] objFields = objClazz.getDeclaredFields();
for (Field field : objFields) {
CryptField cryptField = field.getAnnotation(CryptField.class);
if (cryptField != null) {
field.setAccessible(true);
Object cipher = field.get(cipherObject);
if (cipher == null) {
continue;
}
if (field.getType().equals(String.class)) {
field.set(cipherObject, stringDecrypt((String) cipher, cryptField));
continue;
}
if (field.getType().equals(List.class)) {
field.set(cipherObject, listDecrypt((List) cipher, cryptField));
continue;
}
}
}
}
}
/**
* MyBatis接口参数名称发现器
*
* @author wangzhuhua
* @date 2018/09/05 下午3:12
**/
public class MyBatisParameterNameDiscoverer implements ParameterNameDiscoverer {
@Override
public String[] getParameterNames(Method method) {
return getParameterNames(method.getParameters(), method.getParameterAnnotations());
}
@Override
public String[] getParameterNames(Constructor<?> ctor) {
return getParameterNames(ctor.getParameters(), ctor.getParameterAnnotations());
}
/**
* Mybatis参数名称解析
*
* @param parameters
* 参数数组
* @param parameterAnnotations
* 参数注解数组
* @return 参数名称
*/
private String[] getParameterNames(Parameter[] parameters, Annotation[][] parameterAnnotations) {
String[] parameterNames = new String[parameters.length];
for (int i = 0; i < parameters.length; i++) {
Parameter param = parameters[i];
String paramName = param.getName();
// mybatis 自定义参数名称
for (Annotation annotation : parameterAnnotations[i]) {
if (annotation instanceof Param) {
String customName = ((Param) annotation).value();
if (StringUtils.isNotEmpty(customName)) {
paramName = customName;
break;
}
}
}
parameterNames[i] = paramName;
}
return parameterNames;
}
}