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


    目录

    1.主要代码如下:

    1.注解

    2.定义枚举脱敏规则

    3.增加mybatis拦截器

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

    2.原理简单介绍

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


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

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

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

    1.主要代码如下:

    1.注解

    1. package cn.xm.exam.mybatis;
    2. import java.lang.annotation.ElementType;
    3. import java.lang.annotation.Retention;
    4. import java.lang.annotation.RetentionPolicy;
    5. import java.lang.annotation.Target;
    6. @Target(ElementType.FIELD)
    7. @Retention(RetentionPolicy.RUNTIME)
    8. public @interface Desensitization {
    9. DesensitionType type();
    10. String[] attach() default "";
    11. }

    2.定义枚举脱敏规则

    1. package cn.xm.exam.mybatis;
    2. public enum DesensitionType {
    3. PHONE("phone", "11位手机号", "^(\\d{3})\\d{4}(\\d{4})$", "$1****$2"),
    4. ID_CARD("idCard", "16或者18身份证号", "^(\\d{4})\\d{11,13}(\\w{1})$", "$1****$2"), BANK_CARD("bankCardNo", "银行卡号",
    5. "^(\\d{4})\\d*(\\d{4})$", "$1****$2"), ADDRESS("addrss", "地址", "(?<=.{3}).*(?=.{3})", "*"), REAL_NAME(
    6. "realName", "真实姓名", "(?<=.{1}).*(?=.{1})", "*"), EMAIL("email", "电子邮箱", "(\\w+)\\w{5}@(\\w+)",
    7. "$1***@$2"), CUSTOM("custom", "自定义正则处理", ""), TRUNCATE("truncate", "字符串截取处理", "");
    8. private String type;
    9. private String describe;
    10. private String[] regular;
    11. DesensitionType(String type, String describe, String... regular) {
    12. this.type = type;
    13. this.describe = describe;
    14. this.regular = regular;
    15. }
    16. public String getType() {
    17. return type;
    18. }
    19. public void setType(String type) {
    20. this.type = type;
    21. }
    22. public String getDescribe() {
    23. return describe;
    24. }
    25. public void setDescribe(String describe) {
    26. this.describe = describe;
    27. }
    28. public String[] getRegular() {
    29. return regular;
    30. }
    31. public void setRegular(String[] regular) {
    32. this.regular = regular;
    33. }
    34. }

    3.增加mybatis拦截器

    1. package cn.xm.exam.mybatis;
    2. import java.lang.reflect.Field;
    3. import java.util.ArrayList;
    4. import java.util.Arrays;
    5. import java.util.Collections;
    6. import java.util.LinkedHashMap;
    7. import java.util.List;
    8. import java.util.Map;
    9. import java.util.Properties;
    10. import java.util.Set;
    11. import org.apache.commons.collections.CollectionUtils;
    12. import org.apache.commons.collections.MapUtils;
    13. import org.apache.commons.lang3.ArrayUtils;
    14. import org.apache.commons.lang3.StringUtils;
    15. import org.apache.ibatis.cache.CacheKey;
    16. import org.apache.ibatis.executor.Executor;
    17. import org.apache.ibatis.mapping.BoundSql;
    18. import org.apache.ibatis.mapping.MappedStatement;
    19. import org.apache.ibatis.plugin.Interceptor;
    20. import org.apache.ibatis.plugin.Intercepts;
    21. import org.apache.ibatis.plugin.Invocation;
    22. import org.apache.ibatis.plugin.Plugin;
    23. import org.apache.ibatis.plugin.Signature;
    24. import org.apache.ibatis.session.ResultHandler;
    25. import org.apache.ibatis.session.RowBounds;
    26. import org.slf4j.Logger;
    27. import org.slf4j.LoggerFactory;
    28. @Component
    29. @Intercepts({
    30. @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,
    31. RowBounds.class, ResultHandler.class }),
    32. })
    33. public class DesensitizationInterceptor implements Interceptor {
    34. private static final Logger logger = LoggerFactory.getLogger(DesensitizationInterceptor.class);
    35. @Value("${desensitization}")
    36. private boolean desensitization = false;// 脱敏
    37. private static final Map desensitionMap = new LinkedHashMap<>();
    38. static {
    39. initDensensitionMap();
    40. }
    41. @Override
    42. public Object intercept(Invocation invocation) throws Throwable {
    43. Object result = invocation.proceed();
    44. // 如果需要对结果脱敏,则执行
    45. if (desensitization) {
    46. // 先对Map进行处理
    47. if (result != null && result instanceof Map) {
    48. return this.desensitizationMap(result);
    49. }
    50. // 处理集合
    51. if (result instanceof ArrayList) {
    52. List list = (ArrayList) result;
    53. return this.desensitization(list);
    54. }
    55. // 处理单个bean
    56. return this.desensitization(result);
    57. }
    58. return result;
    59. }
    60. private static void initDensensitionMap() {
    61. desensitionMap.put("idCode", DesensitionType.ID_CARD);
    62. desensitionMap.put("idCard", DesensitionType.ID_CARD);
    63. desensitionMap.put("userIDCard", DesensitionType.ID_CARD);
    64. desensitionMap.put("userIdCard", DesensitionType.ID_CARD);
    65. desensitionMap.put("username", DesensitionType.REAL_NAME);
    66. desensitionMap.put("address", DesensitionType.ADDRESS);
    67. }
    68. /*
    69. * 对map脱敏
    70. */
    71. private Object desensitizationMap(Object result) {
    72. Map mapResult = (Map) result;
    73. if (MapUtils.isEmpty(mapResult)) {
    74. return mapResult;
    75. }
    76. Set keySet = mapResult.keySet();
    77. for (String key : keySet) {
    78. if (desensitionMap.containsKey(key)) {
    79. DesensitionType desensitionType = desensitionMap.get(key);
    80. String replacedVal = getReplacedVal(desensitionType, MapUtils.getString(mapResult, key), null);
    81. mapResult.put(key, replacedVal);
    82. }
    83. }
    84. return result;
    85. }
    86. private List desensitization(List list) {
    87. if (CollectionUtils.isEmpty(list)) {
    88. return Collections.emptyList();
    89. }
    90. Class cls = null;
    91. for (Object o : list) {
    92. // 脱敏map,改变引用地址(根据静态配置脱敏)
    93. if (o != null && o instanceof Map) {
    94. o = desensitizationMap(o);
    95. continue;
    96. }
    97. // 脱敏bean(根据注解脱敏)
    98. if (cls == null) {
    99. cls = o.getClass();
    100. }
    101. o = desensitization(o);
    102. }
    103. return list;
    104. }
    105. @Override
    106. public Object plugin(Object target) {
    107. return Plugin.wrap(target, this);
    108. }
    109. @Override
    110. public void setProperties(Properties properties) {
    111. }
    112. private Object desensitization(Object obj) {
    113. if (obj == null) {
    114. return obj;
    115. }
    116. Class cls = obj.getClass();
    117. Field[] objFields = cls.getDeclaredFields();
    118. if (ArrayUtils.isEmpty(objFields)) {
    119. return obj;
    120. }
    121. for (Field field : objFields) {
    122. if ("serialVersionUID".equals(field.getName())) {
    123. continue;
    124. }
    125. Desensitization desensitization = null;
    126. if (String.class != field.getType()
    127. || (desensitization = field.getAnnotation(Desensitization.class)) == null) {
    128. continue;
    129. }
    130. try {
    131. field.setAccessible(true);
    132. String value = field.get(obj) != null ? field.get(obj).toString() : null;
    133. if (StringUtils.isBlank(value)) {
    134. continue;
    135. }
    136. value = getReplacedVal(desensitization.type(), value, desensitization.attach());
    137. field.set(obj, value);
    138. } catch (Exception ignore) {
    139. ignore.printStackTrace();
    140. }
    141. }
    142. return obj;
    143. }
    144. private String getReplacedVal(DesensitionType type, String value, String[] attachs) {
    145. List regular = null;
    146. switch (type) {
    147. case CUSTOM:
    148. regular = Arrays.asList(attachs);
    149. break;
    150. case TRUNCATE:
    151. regular = truncateRender(attachs);
    152. break;
    153. default:
    154. regular = Arrays.asList(type.getRegular());
    155. }
    156. if (regular != null && regular.size() > 1) {
    157. String match = regular.get(0);
    158. String result = regular.get(1);
    159. if (null != match && result != null && match.length() > 0) {
    160. value = ((String) value).replaceAll(match, result);
    161. return value;
    162. }
    163. }
    164. return "";
    165. }
    166. private List truncateRender(String[] attachs) {
    167. List regular = new ArrayList<>();
    168. if (null != attachs && attachs.length > 1) {
    169. String rule = attachs[0];
    170. String size = attachs[1];
    171. String template, result;
    172. if ("0".equals(rule)) {
    173. template = "^(\\S{%s})(\\S+)$";
    174. result = "$1";
    175. } else if ("1".equals(rule)) {
    176. template = "^(\\S+)(\\S{%s})$";
    177. result = "$2";
    178. } else {
    179. return regular;
    180. }
    181. try {
    182. if (Integer.parseInt(size) > 0) {
    183. regular.add(0, String.format(template, size));
    184. regular.add(1, result);
    185. }
    186. } catch (Exception e) {
    187. logger.warn("ValueDesensitizeFilter truncateRender size {} exception", size);
    188. }
    189. }
    190. return regular;
    191. }
    192. public boolean isDesensitization() {
    193. return desensitization;
    194. }
    195. public void setDesensitization(boolean desensitization) {
    196. this.desensitization = desensitization;
    197. }
    198. }

    解释: 

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

    1. package org.apache.ibatis.plugin;
    2. import java.util.Properties;
    3. public interface Interceptor {
    4. Object intercept(Invocation invocation) throws Throwable;
    5. Object plugin(Object target);
    6. void setProperties(Properties properties);
    7. }

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

    (2)@Intercepts注解:

    1. package org.apache.ibatis.plugin;
    2. import java.lang.annotation.ElementType;
    3. import java.lang.annotation.Retention;
    4. import java.lang.annotation.RetentionPolicy;
    5. import java.lang.annotation.Target;
    6. /**
    7. * @author Clinton Begin
    8. */
    9. @Retention(RetentionPolicy.RUNTIME)
    10. @Target(ElementType.TYPE)
    11. public @interface Intercepts {
    12. Signature[] value();
    13. }

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

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

    1. package org.apache.ibatis.plugin;
    2. import java.lang.annotation.ElementType;
    3. import java.lang.annotation.Retention;
    4. import java.lang.annotation.RetentionPolicy;
    5. import java.lang.annotation.Target;
    6. @Retention(RetentionPolicy.RUNTIME)
    7. @Target(ElementType.TYPE)
    8. public @interface Signature {
    9. Class type();
    10. String method();
    11. Class[] args();
    12. }

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

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

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

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

    1. public class EmployeeIn {
    2. private String employeeid;
    3. /**
    4. * 员工编号
    5. */
    6. private String employeenumber;
    7. private String name;
    8. /**
    9. * 身份证号
    10. */
    11. @Desensitization(type = DesensitionType.ID_CARD)
    12. private String idcode;
    13. ...
    14. }

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

     

     

    2.原理简单介绍

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

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

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

    1.Executor接口如下:

    1. package org.apache.ibatis.executor;
    2. import java.sql.SQLException;
    3. import java.util.List;
    4. import org.apache.ibatis.cache.CacheKey;
    5. import org.apache.ibatis.mapping.BoundSql;
    6. import org.apache.ibatis.mapping.MappedStatement;
    7. import org.apache.ibatis.reflection.MetaObject;
    8. import org.apache.ibatis.session.ResultHandler;
    9. import org.apache.ibatis.session.RowBounds;
    10. import org.apache.ibatis.transaction.Transaction;
    11. /**
    12. * @author Clinton Begin
    13. */
    14. public interface Executor {
    15. ResultHandler NO_RESULT_HANDLER = null;
    16. int update(MappedStatement ms, Object parameter) throws SQLException;
    17. List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
    18. List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
    19. List flushStatements() throws SQLException;
    20. void commit(boolean required) throws SQLException;
    21. void rollback(boolean required) throws SQLException;
    22. CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);
    23. boolean isCached(MappedStatement ms, CacheKey key);
    24. void clearLocalCache();
    25. void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class targetType);
    26. Transaction getTransaction();
    27. void close(boolean forceRollback);
    28. boolean isClosed();
    29. void setExecutorWrapper(Executor executor);
    30. }

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

    2.ParameterHandler

    1. /** Eclipse Class Decompiler plugin, Copyright (c) 2017 Chen Chao. */
    2. package org.apache.ibatis.executor.parameter;
    3. import java.sql.PreparedStatement;
    4. import java.sql.SQLException;
    5. /**
    6. * A parameter handler sets the parameters of the {@code PreparedStatement}
    7. *
    8. * @author Clinton Begin
    9. */
    10. public interface ParameterHandler {
    11. Object getParameterObject();
    12. void setParameters(PreparedStatement ps)
    13. throws SQLException;
    14. }

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

    3.StatementHandler

    1. /** Eclipse Class Decompiler plugin, Copyright (c) 2017 Chen Chao. */
    2. package org.apache.ibatis.executor.statement;
    3. import java.sql.Connection;
    4. import java.sql.SQLException;
    5. import java.sql.Statement;
    6. import java.util.List;
    7. import org.apache.ibatis.cursor.Cursor;
    8. import org.apache.ibatis.executor.parameter.ParameterHandler;
    9. import org.apache.ibatis.mapping.BoundSql;
    10. import org.apache.ibatis.session.ResultHandler;
    11. /**
    12. * @author Clinton Begin
    13. */
    14. public interface StatementHandler {
    15. Statement prepare(Connection connection, Integer transactionTimeout)
    16. throws SQLException;
    17. void parameterize(Statement statement)
    18. throws SQLException;
    19. void batch(Statement statement)
    20. throws SQLException;
    21. int update(Statement statement)
    22. throws SQLException;
    23. List query(Statement statement, ResultHandler resultHandler)
    24. throws SQLException;
    25. Cursor queryCursor(Statement statement)
    26. throws SQLException;
    27. BoundSql getBoundSql();
    28. ParameterHandler getParameterHandler();
    29. }

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

    4.ResultSetHandler

    1. /** Eclipse Class Decompiler plugin, Copyright (c) 2017 Chen Chao. */
    2. package org.apache.commons.dbutils;
    3. import java.sql.ResultSet;
    4. import java.sql.SQLException;
    5. public interface ResultSetHandler {
    6. T handle(ResultSet rs) throws SQLException;
    7. }

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

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

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

    1. 自定义注解Crypt

    1. package cn.xm.exam.mybatis;
    2. import java.lang.annotation.ElementType;
    3. import java.lang.annotation.Retention;
    4. import java.lang.annotation.RetentionPolicy;
    5. import java.lang.annotation.Target;
    6. /**
    7. * 加密解密注解
    8. *
    9. * @author Administrator
    10. *
    11. */
    12. @Target(ElementType.FIELD)
    13. @Retention(RetentionPolicy.RUNTIME)
    14. public @interface Crypt {
    15. }

    2. 加密工具类

    1. package cn.xm.exam.mybatis;
    2. import java.util.HashMap;
    3. import java.util.Map;
    4. import org.apache.commons.lang3.StringUtils;
    5. import cn.xm.exam.utils.SHAUtils;
    6. public class CryptUtils {
    7. /**
    8. * 加密后字符串-原字符串
    9. */
    10. private static final Map cryptedValues = new HashMap<>();
    11. public static String crypt(String originValue) {
    12. String encode = SHAUtils.sha1Hex(originValue + "mysalt");
    13. if (!cryptedValues.containsKey(encode)) {
    14. cryptedValues.put(encode, originValue);
    15. }
    16. return encode;
    17. }
    18. public static boolean hasCryptedVal(String value) {
    19. return cryptedValues.containsKey(value);
    20. }
    21. public static String decrypt(String value) {
    22. if (StringUtils.isBlank(value) || !hasCryptedVal(value)) {
    23. return value;
    24. }
    25. String decodeVal = cryptedValues.get(value);
    26. if (StringUtils.isBlank(decodeVal)) {
    27. return decodeVal;
    28. }
    29. return StringUtils.substringBeforeLast(decodeVal, "mysalt");
    30. }
    31. }

    SHA摘要算法如下:

    1. package cn.xm.exam.utils;
    2. import org.apache.commons.codec.digest.DigestUtils;
    3. /**
    4. * 摘要算法:SHA算法Secure Hash Algorithm(安全hash算法) 安全散列算法(hash函数 将原始信息压缩
    5. * 返回散列值)可以是SHA-1,SHA1是目前最安全 的摘要算法 摘要的长度为 20字节
    6. *
    7. * 其他的SHA 包括 SHA-256(32字节)
    8. *
    9. * 20byte = 160 bit,换成16进制字符串就是40位字符串
    10. *
    11. * @author Administrator
    12. *
    13. */
    14. public class SHAUtils {
    15. /**
    16. *
    17. * @param sourceCode
    18. * @return 40位的16进制字符串
    19. */
    20. public static String sha1Hex(String sourceCode) {
    21. return DigestUtils.sha1Hex(sourceCode);
    22. }
    23. /**
    24. *
    25. * @param sourceCode
    26. * @return length为20的字节数组,如果转为字符串需要new String(Hex.encodeHex(return))
    27. */
    28. public static byte[] sha1(String sourceCode) {
    29. // length为20的字节数组
    30. return DigestUtils.sha1(sourceCode);
    31. }
    32. /**
    33. *
    34. * @param sourceCode
    35. * @return 40位的16进制字符串
    36. */
    37. public static String sha256Hex(String sourceCode) {
    38. return DigestUtils.sha256Hex(sourceCode);
    39. }
    40. /**
    41. *
    42. * @param sourceCode
    43. * @return length为20的字节数组,如果转为字符串需要new String(Hex.encodeHex(return))
    44. */
    45. public static byte[] sha256(String sourceCode) {
    46. // length为20的字节数组
    47. return DigestUtils.sha256(sourceCode);
    48. }
    49. public static void main(String[] args) {
    50. System.out.println(sha1Hex("qlq"));
    51. }
    52. }

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

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

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

    1. package cn.xm.exam.mybatis;
    2. import java.lang.reflect.Field;
    3. import java.sql.Statement;
    4. import java.util.ArrayList;
    5. import java.util.LinkedList;
    6. import java.util.List;
    7. import java.util.Map;
    8. import java.util.Properties;
    9. import java.util.Set;
    10. import org.apache.commons.collections.CollectionUtils;
    11. import org.apache.commons.collections.MapUtils;
    12. import org.apache.commons.lang3.StringUtils;
    13. import org.apache.commons.lang3.reflect.FieldUtils;
    14. import org.apache.ibatis.executor.resultset.ResultSetHandler;
    15. import org.apache.ibatis.plugin.Interceptor;
    16. import org.apache.ibatis.plugin.Intercepts;
    17. import org.apache.ibatis.plugin.Invocation;
    18. import org.apache.ibatis.plugin.Plugin;
    19. import org.apache.ibatis.plugin.Signature;
    20. import org.slf4j.Logger;
    21. import org.slf4j.LoggerFactory;
    22. /**
    23. * 加密。出库后加密(加密后传到界面)
    24. *
    25. * @author Administrator
    26. *
    27. */
    28. @Intercepts({ @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = { Statement.class }) })
    29. public class EncryptInterceptor implements Interceptor {
    30. private static final Logger log = LoggerFactory.getLogger(EncryptInterceptor.class);
    31. private static final List cryptedKeys = new LinkedList<>();
    32. static {
    33. cryptedKeys.add("fullname");
    34. cryptedKeys.add("idCode");
    35. cryptedKeys.add("idCard");
    36. cryptedKeys.add("userIDCard");
    37. cryptedKeys.add("userIdCard");
    38. cryptedKeys.add("username");
    39. cryptedKeys.add("address");
    40. }
    41. @Override
    42. public Object intercept(Invocation invocation) throws Throwable {
    43. Object resultObject = invocation.proceed();
    44. if (resultObject == null) {
    45. return null;
    46. }
    47. // 基于selectList
    48. if (resultObject instanceof ArrayList) {
    49. ArrayList resultList = (ArrayList) resultObject;
    50. if (CollectionUtils.isNotEmpty(resultList)) {
    51. for (Object result : resultList) {
    52. doEncrypt(result);
    53. }
    54. }
    55. } else {
    56. doEncrypt(resultObject);
    57. }
    58. return resultObject;
    59. }
    60. private void doEncrypt(Object result) {
    61. if (result instanceof Map) {
    62. Map resultMap = (Map) result;
    63. doEncryptMap(resultMap);
    64. return;
    65. }
    66. doEncryptPlainBean(result);
    67. }
    68. /**
    69. * 加密普通bean,用反射获取字段进行加密
    70. *
    71. * @param result
    72. */
    73. private void doEncryptPlainBean(Object result) {
    74. List allFieldsList = FieldUtils.getFieldsListWithAnnotation(result.getClass(), Crypt.class);
    75. for (Field field : allFieldsList) {
    76. field.setAccessible(true);
    77. try {
    78. Class type = field.getType();
    79. if (!type.equals(String.class)) {
    80. continue;
    81. }
    82. String value = (String) field.get(result);
    83. if (StringUtils.isBlank(value)) {
    84. continue;
    85. }
    86. value = encrypt(value);
    87. field.set(result, value);
    88. } catch (Exception e) {
    89. log.error("doEncryptPlainBean error", e);
    90. }
    91. }
    92. }
    93. private String encrypt(String value) {
    94. return CryptUtils.crypt(value);
    95. }
    96. /**
    97. * 加密map
    98. *
    99. * @param resultMap
    100. */
    101. private void doEncryptMap(Map resultMap) {
    102. if (MapUtils.isEmpty(resultMap)) {
    103. return;
    104. }
    105. Set keySet = resultMap.keySet();
    106. for (Object key : keySet) {
    107. String keyStr = (String) key;
    108. if (cryptedKeys.contains(keyStr)) {
    109. resultMap.put(key, encrypt(String.valueOf(resultMap.get(key))));
    110. }
    111. }
    112. }
    113. @Override
    114. public Object plugin(Object target) {
    115. return Plugin.wrap(target, this);
    116. }
    117. @Override
    118. public void setProperties(Properties properties) {
    119. }
    120. }

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

    1. package cn.xm.exam.mybatis;
    2. import java.lang.reflect.Field;
    3. import java.sql.PreparedStatement;
    4. import java.util.ArrayList;
    5. import java.util.Iterator;
    6. import java.util.List;
    7. import java.util.Map;
    8. import java.util.Properties;
    9. import java.util.Set;
    10. import org.apache.commons.collections.CollectionUtils;
    11. import org.apache.commons.collections.MapUtils;
    12. import org.apache.commons.lang3.reflect.FieldUtils;
    13. import org.apache.ibatis.executor.parameter.ParameterHandler;
    14. import org.apache.ibatis.plugin.Interceptor;
    15. import org.apache.ibatis.plugin.Intercepts;
    16. import org.apache.ibatis.plugin.Invocation;
    17. import org.apache.ibatis.plugin.Plugin;
    18. import org.apache.ibatis.plugin.Signature;
    19. import org.slf4j.Logger;
    20. import org.slf4j.LoggerFactory;
    21. /**
    22. * 解密拦截器。(查询或者进行修改等操作时对参数进行解密)
    23. *
    24. * @author Administrator
    25. *
    26. */
    27. @Intercepts({ @Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class) })
    28. public class DecryptInterceptor implements Interceptor {
    29. private static final Logger log = LoggerFactory.getLogger(DecryptInterceptor.class);
    30. @Override
    31. public Object intercept(Invocation invocation) throws Throwable {
    32. // @Signature 指定了 type= parameterHandler 后,这里的 invocation.getTarget()
    33. // 便是parameterHandler
    34. // 若指定ResultSetHandler ,这里则能强转为ResultSetHandler
    35. ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
    36. // 获取参数对像,即 mapper 中 paramsType 的实例
    37. Field parameterField = parameterHandler.getClass().getDeclaredField("parameterObject");
    38. parameterField.setAccessible(true);
    39. // 取出实例(参数实例)
    40. Object parameterObject = parameterField.get(parameterHandler);
    41. if (parameterObject != null) {
    42. int count = 0;
    43. Class parameterObjectClass = parameterObject.getClass();
    44. // 集合
    45. if (parameterObject instanceof List) {
    46. ArrayList resultList = (ArrayList) parameterObject;
    47. if (CollectionUtils.isNotEmpty(resultList)) {
    48. for (Object result : resultList) {
    49. result = doDecrypt(result);
    50. }
    51. }
    52. }
    53. // 普通的bean
    54. parameterObject = doDecrypt(parameterObject);
    55. }
    56. // 重新赋值引用
    57. parameterField.set(parameterHandler, parameterObject);
    58. Object proceed = invocation.proceed();
    59. return proceed;
    60. }
    61. private Object doDecrypt(Object result) {
    62. if (result == null) {
    63. return result;
    64. }
    65. Classextends Object> clazz = result.getClass();
    66. // String 类型
    67. if (clazz != null && clazz.equals(String.class)) {
    68. return decryptStr(result.toString());
    69. }
    70. if (result instanceof Map) {
    71. return decryptMap((Map) result);
    72. }
    73. // 普通bean
    74. return decryptPlainBean(result);
    75. }
    76. private Object decryptPlainBean(Object result) {
    77. List allFieldsList = FieldUtils.getFieldsListWithAnnotation(result.getClass(), Crypt.class);
    78. for (Field field : allFieldsList) {
    79. field.setAccessible(true);
    80. try {
    81. Class type = field.getType();
    82. if (!type.equals(String.class)) {
    83. continue;
    84. }
    85. Object object = field.get(result);
    86. field.set(result, decryptStr((String) object));
    87. } catch (Exception e) {
    88. log.error("doEncryptPlainBean error", e);
    89. }
    90. }
    91. return result;
    92. }
    93. private Object decryptMap(Map result) {
    94. if (result == null || MapUtils.isEmpty(result)) {
    95. return result;
    96. }
    97. Set keySet = result.keySet();
    98. Iterator iterator = keySet.iterator();
    99. while (iterator.hasNext()) {
    100. Object key = iterator.next();
    101. Object object = result.get(key);
    102. if (object == null) {
    103. continue;
    104. }
    105. if (object instanceof String) {
    106. result.put(key, decryptStr((String) object));
    107. }
    108. }
    109. return result;
    110. }
    111. private Object decryptStr(String string) {
    112. return CryptUtils.decrypt(string);
    113. }
    114. @Override
    115. public Object plugin(Object target) {
    116. return Plugin.wrap(target, this);
    117. }
    118. @Override
    119. public void setProperties(Properties properties) {
    120. }
    121. }

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

    1. @Crypt
    2. private String useridcard;

  • 相关阅读:
    【MySQL】基础操作-DML 与 约束
    学习总结:jQuery插件——Validation Plugin
    分组后取最大值对应记录
    leetcode:914. 卡牌分组(python3解法)
    连接表(二)
    【JVM】虚拟机调优 常用的调优参数
    Android 实战项目分享(一)用Android Studio绘制贝塞尔曲线的艺术之旅
    外滩大会今日开幕 近20位“两院”院士、诺贝尔奖和图灵奖得主齐聚
    css的position:sticky吸顶功能的兼容性
    学了HTML,快来试着做一份简历吧
  • 原文地址:https://blog.csdn.net/weixin_58670730/article/details/126954268