mybatis拦截器实现数据脱敏&拦截器使用

  今天遇到个需求需要对现有的数据进行脱敏处理。于是简单研究了下。

  其实拦截器对脱敏处理主要处理两种数据,一种是bean类型,一种是map类型。

  普通的javabean利用注解+反射来处理,map的数据自己维护需要脱敏的key以及规则。bean类型是用mybatis以及mybatis-plus自动生成的SQL映射的;map类型是手写的返回map类型的SQL和mybatis-plus的返回map类型的数据。

1.主要代码如下:

1.注解

package cn.xm.exam.mybatis;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Desensitization {
    
    DesensitionType type();

    String[] attach() default "";
}

2.定义枚举脱敏规则

package cn.xm.exam.mybatis;

public enum DesensitionType {
    PHONE("phone", "11位手机号", "^(\d{3})\d{4}(\d{4})$", "$1****$2"),
    // ID_CARD("idCard", "16或者18身份证号", "^(\d{4})\d{8,10}(\w{4})$",
    // "$1****$2"),
    ID_CARD("idCard", "16或者18身份证号", "^(\d{4})\d{11,13}(\w{1})$", "$1****$2"), BANK_CARD("bankCardNo", "银行卡号",
            "^(\d{4})\d*(\d{4})$", "$1****$2"), ADDRESS("addrss", "地址", "(?<=.{3}).*(?=.{3})", "*"), REAL_NAME(
                    "realName", "真实姓名", "(?<=.{1}).*(?=.{1})", "*"), EMAIL("email", "电子邮箱", "(\w+)\w{5}@(\w+)",
                            "$1***@$2"), CUSTOM("custom", "自定义正则处理", ""), TRUNCATE("truncate", "字符串截取处理", "");

    private String type;

    private String describe;

    private String[] regular;

    DesensitionType(String type, String describe, String... regular) {
        this.type = type;
        this.describe = describe;
        this.regular = regular;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getDescribe() {
        return describe;
    }

    public void setDescribe(String describe) {
        this.describe = describe;
    }

    public String[] getRegular() {
        return regular;
    }

    public void setRegular(String[] regular) {
        this.regular = regular;
    }

}

3.增加mybatis拦截器

package cn.xm.exam.mybatis;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
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.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@SuppressWarnings({ "rawtypes", "unchecked" })
@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 DesensitizationInterceptor implements Interceptor {

    private static final Logger logger = LoggerFactory.getLogger(DesensitizationInterceptor.class);

    private boolean desensitization = false;// 脱敏

    private static final Map<String, DesensitionType> desensitionMap = new LinkedHashMap<>();
    static {
        initDensensitionMap();
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object result = invocation.proceed();

        // 如果需要对结果脱敏,则执行
        if (desensitization) {
            // 先对Map进行处理
            if (result != null && result instanceof Map) {
                return this.desensitizationMap(result);
            }

            // 处理集合
            if (result instanceof ArrayList<?>) {
                List<?> list = (ArrayList<?>) result;
                return this.desensitization(list);
            }

            // 处理单个bean
            return this.desensitization(result);
        }

        return result;
    }

    private static void initDensensitionMap() {
        desensitionMap.put("idCode", DesensitionType.ID_CARD);
        desensitionMap.put("idCard", DesensitionType.ID_CARD);
        desensitionMap.put("userIDCard", DesensitionType.ID_CARD);
        desensitionMap.put("userIdCard", DesensitionType.ID_CARD);

        desensitionMap.put("username", DesensitionType.REAL_NAME);
        desensitionMap.put("address", DesensitionType.ADDRESS);
    }

    /*
     * 对map脱敏
     */
    private Object desensitizationMap(Object result) {
        Map mapResult = (Map) result;
        if (MapUtils.isEmpty(mapResult)) {
            return mapResult;
        }

        Set<String> keySet = mapResult.keySet();
        for (String key : keySet) {
            if (desensitionMap.containsKey(key)) {
                DesensitionType desensitionType = desensitionMap.get(key);
                String replacedVal = getReplacedVal(desensitionType, MapUtils.getString(mapResult, key), null);
                mapResult.put(key, replacedVal);
            }
        }
        return result;
    }

    private List desensitization(List list) {
        if (CollectionUtils.isEmpty(list)) {
            return Collections.emptyList();
        }

        Class cls = null;
        for (Object o : list) {
            // 脱敏map,改变引用地址(根据静态配置脱敏)
            if (o != null && o instanceof Map) {
                o = desensitizationMap(o);
                continue;
            }

            // 脱敏bean(根据注解脱敏)
            if (cls == null) {
                cls = o.getClass();
            }
            o = desensitization(o);
        }
        return list;
    }

    @Override
    public Object plugin(Object target) {
        // TODO Spring bean 方式配置时,如果没有配置属性就不会执行下面的 setProperties
        // 方法,就不会初始化,因此考虑在这个方法中做一次判断和初始化
        return Plugin.wrap(target, this);
    }

    /**
     * 用于在Mybatis配置文件中指定一些属性的,注册当前拦截器的时候可以设置一些属性
     */
    @Override
    public void setProperties(Properties properties) {
    }

    private Object desensitization(Object obj) {
        if (obj == null) {
            return obj;
        }
        Class cls = obj.getClass();
        Field[] objFields = cls.getDeclaredFields();
        if (ArrayUtils.isEmpty(objFields)) {
            return obj;
        }

        for (Field field : objFields) {
            if ("serialVersionUID".equals(field.getName())) {
                continue;
            }

            Desensitization desensitization = null;
            if (String.class != field.getType()
                    || (desensitization = field.getAnnotation(Desensitization.class)) == null) {
                continue;
            }

            try {
                field.setAccessible(true);
                String value = field.get(obj) != null ? field.get(obj).toString() : null;
                if (StringUtils.isBlank(value)) {
                    continue;
                }

                value = getReplacedVal(desensitization.type(), value, desensitization.attach());
                field.set(obj, value);
            } catch (Exception ignore) {
                ignore.printStackTrace();
            }
        }

        return obj;
    }

    private String getReplacedVal(DesensitionType type, String value, String[] attachs) {
        List<String> regular = null;
        switch (type) {
        case CUSTOM:
            regular = Arrays.asList(attachs);
            break;
        case TRUNCATE:
            regular = truncateRender(attachs);
            break;
        default:
            regular = Arrays.asList(type.getRegular());
        }

        if (regular != null && regular.size() > 1) {
            String match = regular.get(0);
            String result = regular.get(1);
            if (null != match && result != null && match.length() > 0) {
                value = ((String) value).replaceAll(match, result);
                return value;
            }
        }

        return "";
    }

    private List<String> truncateRender(String[] attachs) {
        List<String> regular = new ArrayList<>();
        if (null != attachs && attachs.length > 1) {
            String rule = attachs[0];
            String size = attachs[1];
            String template, result;
            if ("0".equals(rule)) {
                template = "^(\S{%s})(\S+)$";
                result = "$1";
            } else if ("1".equals(rule)) {
                template = "^(\S+)(\S{%s})$";
                result = "$2";
            } else {
                return regular;
            }
            try {
                if (Integer.parseInt(size) > 0) {
                    regular.add(0, String.format(template, size));
                    regular.add(1, result);
                }
            } catch (Exception e) {
                logger.warn("ValueDesensitizeFilter truncateRender size {} exception", size);
            }
        }
        return regular;
    }

    public boolean isDesensitization() {
        return desensitization;
    }

    public void setDesensitization(boolean desensitization) {
        this.desensitization = desensitization;
    }

}

解释: 

(1)Interceptor接口有三个方法,如下:

package org.apache.ibatis.plugin;
import java.util.Properties;

public interface Interceptor {

  Object intercept(Invocation invocation) throws Throwable;

  Object plugin(Object target);

  void setProperties(Properties properties);

}

intercept方法中编写我们自己的处理逻辑。类似于AOP。

(2)@Intercepts注解:

package org.apache.ibatis.plugin;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author Clinton Begin
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Intercepts {
  Signature[] value();
}

 Intercepts注解需要一个Signature(拦截点)参数数组。通过Signature来指定拦截哪个对象里面的哪个方法。

(3)Signature注解指定需要拦截那个类对象的哪个方法

package org.apache.ibatis.plugin;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Signature {
  Class<?> type();

  String method();

  Class<?>[] args();
}

class:指定定义拦截的类 Executor、ParameterHandler、StatementHandler、ResultSetHandler当中的一个。

method:指定拦截的方法,方法名字即可

args:指定拦截的方法对应的参数,JAVA里面方法可能重载,不指定参数,不能确定调用那个方法。

4.mybatis的sqlSessionFactory中注册拦截器

    <!--2. 配置 Mybatis的会话工厂 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 数据源 -->
        <property name="dataSource" ref="dataSource" />
        <!-- 配置Mybatis的核心 配置文件所在位置 -->
        <property name="configLocation" value="classpath:mybatis/SqlMapConfig.xml" />
        <!-- 注意其他配置 -->
        <property name="plugins">
            <array>
                <bean class="com.github.pagehelper.PageInterceptor">
                    <property name="properties">
                        <!--使用下面的方式配置参数,一行配置一个 -->
                        <value>
                             helperDialect=mysql
                             reasonable=true
                        </value>
                    </property>
                </bean>
                <bean class="cn.xm.exam.mybatis.DesensitizationInterceptor">
                    <property name="desensitization" value="true"></property>
                </bean>
            </array>
        </property>
    </bean>

5.Javabean中增加注解,如果是查询返回的map,会根据desensitionMap的规则进行脱敏

public class EmployeeIn {
    private String employeeid;

    /**
     * 员工编号
     */
    private String employeenumber;

    private String name;

    /**
     * 身份证号
     */
    @Desensitization(type = DesensitionType.ID_CARD)
    private String idcode;

    ...
}

至此就可以实现一些基本的数据脱敏,前台查看返回的信息如下:

2.原理简单介绍

  Mybatis拦截器设计的初衷就是为了供用户在某些时候可以实现自己的逻辑而不必去动Mybatis固有的逻辑。通过Mybatis拦截器我们可以拦截某些方法的调用,我们可以选择在这些被拦截的方法执行前后加上某些逻辑,也可以在执行这些被拦截的方法时执行自己的逻辑而不再执行被拦截的方法。

  Mybatis里面的核心对象还是比较多的,如下:

   Mybatis拦截器并不是每个对象里面的方法都可以被拦截的。Mybatis拦截器只能拦截Executor、ParameterHandler、StatementHandler、ResultSetHandler四个对象里面的方法。

1.Executor接口如下:

/** <a href="http://www.cpupk.com/decompiler">Eclipse Class Decompiler</a> plugin, Copyright (c) 2017 Chen Chao. */
package org.apache.ibatis.executor;
import java.sql.SQLException;
import java.util.List;

import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.transaction.Transaction;

/**
 * @author Clinton Begin
 */
public interface Executor {

  ResultHandler NO_RESULT_HANDLER = null;

  int update(MappedStatement ms, Object parameter) throws SQLException;

  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;

  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;

  List<BatchResult> flushStatements() throws SQLException;

  void commit(boolean required) throws SQLException;

  void rollback(boolean required) throws SQLException;

  CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);

  boolean isCached(MappedStatement ms, CacheKey key);

  void clearLocalCache();

  void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);

  Transaction getTransaction();

  void close(boolean forceRollback);

  boolean isClosed();
  
  void setExecutorWrapper(Executor executor);

}

   Mybatis中所有的Mapper语句的执行都是通过Executor进行的。Executor是Mybatis的核心接口。从其定义的接口方法我们可以看出,对应的增删改语句是通过Executor接口的update方法进行的,查询是通过query方法进行的。

2.ParameterHandler

/** <a href="http://www.cpupk.com/decompiler">Eclipse Class Decompiler</a> plugin, Copyright (c) 2017 Chen Chao. */
package org.apache.ibatis.executor.parameter;
import java.sql.PreparedStatement;
import java.sql.SQLException;

/**
 * A parameter handler sets the parameters of the {@code PreparedStatement}
 *
 * @author Clinton Begin
 */
public interface ParameterHandler {

  Object getParameterObject();

  void setParameters(PreparedStatement ps)
      throws SQLException;

}

  ParameterHandler用来设置参数规则,当StatementHandler使用prepare()方法后,接下来就是使用它来设置参数。所以如果有对参数做自定义逻辑处理的时候,可以通过拦截ParameterHandler来实现。

3.StatementHandler

/** <a href="http://www.cpupk.com/decompiler">Eclipse Class Decompiler</a> plugin, Copyright (c) 2017 Chen Chao. */
package org.apache.ibatis.executor.statement;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;

import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.session.ResultHandler;

/**
 * @author Clinton Begin
 */
public interface StatementHandler {

  Statement prepare(Connection connection, Integer transactionTimeout)
      throws SQLException;

  void parameterize(Statement statement)
      throws SQLException;

  void batch(Statement statement)
      throws SQLException;

  int update(Statement statement)
      throws SQLException;

  <E> List<E> query(Statement statement, ResultHandler resultHandler)
      throws SQLException;

  <E> Cursor<E> queryCursor(Statement statement)
      throws SQLException;

  BoundSql getBoundSql();

  ParameterHandler getParameterHandler();

}

  StatementHandler负责处理Mybatis与JDBC之间Statement的交互。

4.ResultSetHandler

/** <a href="http://www.cpupk.com/decompiler">Eclipse Class Decompiler</a> plugin, Copyright (c) 2017 Chen Chao. */
package org.apache.commons.dbutils;
import java.sql.ResultSet;
import java.sql.SQLException;

public interface ResultSetHandler<T> {

    T handle(ResultSet rs) throws SQLException;

}

  ResultSetHandler用于对查询到的结果做处理。所以如果你有需求需要对返回结果做特殊处理的情况下可以去拦截ResultSetHandler的处理。

3.常见的拦截器

PageInterceptor 分页插件

脱敏插件

4.拦截器实现数据库查询完加密,编辑关联查询解密

  今天遇到个需求是查询之后需要对身份证以及住址信息,并且在关联查询的时候需要使用原字符串,也就是需要解密。并且在编辑完保存的时候需要还原成原字符串。

1. 自定义注解Crypt

package cn.xm.exam.mybatis;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 加密解密注解
 * 
 * @author Administrator
 *
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Crypt {

}

2. 加密工具类

package cn.xm.exam.mybatis;

import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;

import cn.xm.exam.utils.SHAUtils;

public class CryptUtils {

    /**
     * 加密后字符串-原字符串
     */
    private static final Map<String, String> cryptedValues = new HashMap<>();

    public static String crypt(String originValue) {
        String encode = SHAUtils.sha1Hex(originValue + "mysalt");
        if (!cryptedValues.containsKey(encode)) {
            cryptedValues.put(encode, originValue);
        }

        return encode;
    }

    public static boolean hasCryptedVal(String value) {
        return cryptedValues.containsKey(value);
    }

    public static String decrypt(String value) {
        if (StringUtils.isBlank(value) || !hasCryptedVal(value)) {
            return value;
        }

        String decodeVal = cryptedValues.get(value);
        if (StringUtils.isBlank(decodeVal)) {
            return decodeVal;
        }

        return StringUtils.substringBeforeLast(decodeVal, "mysalt");
    }

}

SHA摘要算法如下:

package cn.xm.exam.utils;


import org.apache.commons.codec.digest.DigestUtils;

/**
 * 摘要算法:SHA算法Secure Hash Algorithm(安全hash算法) 安全散列算法(hash函数 将原始信息压缩
 * 返回散列值)可以是SHA-1,SHA1是目前最安全 的摘要算法 摘要的长度为 20字节
 * 
 * 其他的SHA 包括 SHA-256(32字节)
 * 
 * 20byte = 160 bit,换成16进制字符串就是40位字符串
 * 
 * @author Administrator
 *
 */
public class SHAUtils {

    /**
     * 
     * @param sourceCode
     * @return 40位的16进制字符串
     */
    public static String sha1Hex(String sourceCode) {
        return DigestUtils.sha1Hex(sourceCode);
    }

    /**
     * 
     * @param sourceCode
     * @return length为20的字节数组,如果转为字符串需要new String(Hex.encodeHex(return))
     */
    public static byte[] sha1(String sourceCode) {
        // length为20的字节数组
        return DigestUtils.sha1(sourceCode);
    }

    /**
     * 
     * @param sourceCode
     * @return 40位的16进制字符串
     */
    public static String sha256Hex(String sourceCode) {
        return DigestUtils.sha256Hex(sourceCode);
    }

    /**
     * 
     * @param sourceCode
     * @return length为20的字节数组,如果转为字符串需要new String(Hex.encodeHex(return))
     */
    public static byte[] sha256(String sourceCode) {
        // length为20的字节数组
        return DigestUtils.sha256(sourceCode);
    }

    public static void main(String[] args) {
        System.out.println(sha1Hex("qlq"));
    }

}

  SHA应该是不可逆的,我是将加密后的字符串作为key、原字符串作为value存入了map中来实现可逆。

3. 加密拦截器以及解密拦截器

  加密拦截器,拦截 ResultSetHandler 的Statement 方法,对查出的数据进行加密。

package cn.xm.exam.mybatis;

import java.lang.reflect.Field;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 加密。出库后加密(加密后传到界面)
 * 
 * @author Administrator
 *
 */
@Intercepts({ @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = { Statement.class }) })
public class EncryptInterceptor implements Interceptor {

    private static final Logger log = LoggerFactory.getLogger(EncryptInterceptor.class);

    private static final List<String> cryptedKeys = new LinkedList<>();
    static {
        cryptedKeys.add("fullname");
        cryptedKeys.add("idCode");
        cryptedKeys.add("idCard");
        cryptedKeys.add("userIDCard");
        cryptedKeys.add("userIdCard");
        cryptedKeys.add("username");
        cryptedKeys.add("address");
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object resultObject = invocation.proceed();
        if (resultObject == null) {
            return null;
        }

        // 基于selectList
        if (resultObject instanceof ArrayList) {
            ArrayList resultList = (ArrayList) resultObject;
            if (CollectionUtils.isNotEmpty(resultList)) {
                for (Object result : resultList) {
                    doEncrypt(result);
                }
            }
        } else {
            doEncrypt(resultObject);
        }

        return resultObject;
    }

    private void doEncrypt(Object result) {
        if (result instanceof Map) {
            Map resultMap = (Map) result;
            doEncryptMap(resultMap);
            return;
        }

        doEncryptPlainBean(result);
    }

    /**
     * 加密普通bean,用反射获取字段进行加密
     * 
     * @param result
     */
    private void doEncryptPlainBean(Object result) {
        List<Field> allFieldsList = FieldUtils.getFieldsListWithAnnotation(result.getClass(), Crypt.class);
        for (Field field : allFieldsList) {
            field.setAccessible(true);
            try {
                Class<?> type = field.getType();
                if (!type.equals(String.class)) {
                    continue;
                }
                String value = (String) field.get(result);
                if (StringUtils.isBlank(value)) {
                    continue;
                }

                value = encrypt(value);
                field.set(result, value);
            } catch (Exception e) {
                log.error("doEncryptPlainBean error", e);
            }
        }
    }

    private String encrypt(String value) {
        return CryptUtils.crypt(value);
    }

    /**
     * 加密map
     * 
     * @param resultMap
     */
    private void doEncryptMap(Map resultMap) {
        if (MapUtils.isEmpty(resultMap)) {
            return;
        }

        Set keySet = resultMap.keySet();
        for (Object key : keySet) {
            String keyStr = (String) key;
            if (cryptedKeys.contains(keyStr)) {
                resultMap.put(key, encrypt(String.valueOf(resultMap.get(key))));
            }
        }
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {

    }

}

解密拦截器:对入参进行解密。先判断类型,只对string、list<String>、map中的string、bean中的string属性进行解密

package cn.xm.exam.mybatis;

import java.lang.reflect.Field;
import java.sql.PreparedStatement;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 解密拦截器。(查询或者进行修改等操作时对参数进行解密)
 * 
 * @author Administrator
 *
 */
@Intercepts({ @Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class) })
public class DecryptInterceptor implements Interceptor {

    private static final Logger log = LoggerFactory.getLogger(DecryptInterceptor.class);

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // @Signature 指定了 type= parameterHandler 后,这里的 invocation.getTarget()
        // 便是parameterHandler
        // 若指定ResultSetHandler ,这里则能强转为ResultSetHandler
        ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
        // 获取参数对像,即 mapper 中 paramsType 的实例
        Field parameterField = parameterHandler.getClass().getDeclaredField("parameterObject");
        parameterField.setAccessible(true);
        // 取出实例(参数实例)
        Object parameterObject = parameterField.get(parameterHandler);
        if (parameterObject != null) {
            int count = 0;
            Class<?> parameterObjectClass = parameterObject.getClass();
            // 集合
            if (parameterObject instanceof List) {
                ArrayList resultList = (ArrayList) parameterObject;
                if (CollectionUtils.isNotEmpty(resultList)) {
                    for (Object result : resultList) {
                        result = doDecrypt(result);
                    }
                }
            }

            // 普通的bean
            parameterObject = doDecrypt(parameterObject);
        }
        // 重新赋值引用
        parameterField.set(parameterHandler, parameterObject);

        Object proceed = invocation.proceed();
        return proceed;
    }

    private Object doDecrypt(Object result) {
        if (result == null) {
            return result;
        }

        Class<? extends Object> clazz = result.getClass();
        // String 类型
        if (clazz != null && clazz.equals(String.class)) {
            return decryptStr(result.toString());
        }

        if (result instanceof Map) {
            return decryptMap((Map) result);
        }

        // 普通bean
        return decryptPlainBean(result);
    }

    private Object decryptPlainBean(Object result) {
        List<Field> allFieldsList = FieldUtils.getFieldsListWithAnnotation(result.getClass(), Crypt.class);
        for (Field field : allFieldsList) {
            field.setAccessible(true);
            try {
                Class<?> type = field.getType();
                if (!type.equals(String.class)) {
                    continue;
                }

                Object object = field.get(result);
                field.set(result, decryptStr((String) object));
            } catch (Exception e) {
                log.error("doEncryptPlainBean error", e);
            }
        }

        return result;
    }

    private Object decryptMap(Map result) {
        if (result == null || MapUtils.isEmpty(result)) {
            return result;
        }

        Set keySet = result.keySet();
        Iterator iterator = keySet.iterator();
        while (iterator.hasNext()) {
            Object key = iterator.next();
            Object object = result.get(key);
            if (object == null) {
                continue;
            }

            if (object instanceof String) {
                result.put(key, decryptStr((String) object));
            }
        }

        return result;
    }

    private Object decryptStr(String string) {
        return CryptUtils.decrypt(string);
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {

    }

}

4. mybatis的会话工厂配置拦截器

    <!--2. 配置 Mybatis的会话工厂 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 数据源 -->
        <property name="dataSource" ref="dataSource" />
        <!-- 配置Mybatis的核心 配置文件所在位置 -->
        <property name="configLocation" value="classpath:mybatis/SqlMapConfig.xml" />
        <!-- 注意其他配置 -->
        <property name="plugins">
            <array>
                <bean class="com.github.pagehelper.PageInterceptor">
                    <property name="properties">
                        <!--使用下面的方式配置参数,一行配置一个 -->
                        <value>
                             helperDialect=mysql
                             reasonable=true
                        </value>
                    </property>
                </bean>
                <bean class="cn.xm.exam.mybatis.EncryptInterceptor">
                </bean>
                <bean class="cn.xm.exam.mybatis.DecryptInterceptor">
                </bean>
            </array>
        </property>
    </bean>

5.自定义的注解加在bean对应的实体类上,会被上面拦截器反射的时候读取到了

    @Crypt
    private String useridcard;
原文地址:https://www.cnblogs.com/qlqwjy/p/13388047.html