├── src ├── test │ ├── resources │ │ ├── application.yml │ │ └── user.sql │ └── java │ │ └── com │ │ └── chenhaiyang │ │ └── plugin │ │ └── mybatis │ │ └── sensitive │ │ └── test │ │ ├── encrypt │ │ └── AesEncryptTest.java │ │ ├── all │ │ ├── SpringBootTestApplication.java │ │ ├── mapper │ │ │ ├── UserMapper.java │ │ │ └── UserMapper.xml │ │ ├── config │ │ │ └── EncryptPluginConfig.java │ │ ├── dto │ │ │ └── UserDTO.java │ │ └── testcase │ │ │ └── MapperTestCase.java │ │ └── sensitive │ │ └── SensitiveHandlerTest.java └── main │ └── java │ └── com │ └── chenhaiyang │ └── plugin │ └── mybatis │ └── sensitive │ ├── annotation │ ├── EncryptField.java │ ├── SensitiveEncryptEnabled.java │ ├── SensitiveJSONField.java │ ├── SensitiveField.java │ ├── SensitiveJSONFieldKey.java │ └── SensitiveBinded.java │ ├── type │ ├── SensitiveTypeHandler.java │ ├── handler │ │ ├── NoneSensitiveHandler.java │ │ ├── CnapsSensitiveHandler.java │ │ ├── NameSensitiveHandler.java │ │ ├── MobilePhoneSensitiveHandler.java │ │ ├── FixedPhoneSensitiveHandler.java │ │ ├── IDCardSensitiveHandler.java │ │ ├── BandCardSensitiveHandler.java │ │ ├── PaySignNoSensitiveHandler.java │ │ ├── EmailSensitiveHandler.java │ │ ├── AddressSensitiveHandler.java │ │ └── DafaultSensitiveHandler.java │ ├── SensitiveType.java │ └── SensitiveTypeRegisty.java │ ├── Encrypt.java │ ├── utils │ ├── PluginUtils.java │ ├── JsonUtils.java │ └── Hex.java │ ├── encrypt │ └── AesSupport.java │ └── interceptor │ ├── DecryptReadInterceptor.java │ └── SensitiveAndEncryptWriteInterceptor.java ├── README.md └── pom.xml /src/test/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | datasource: 3 | driver-class-name: com.mysql.jdbc.Driver 4 | url: jdbc:mysql://localhost:3306/mybatis_plugin_test?useUnicode=true&characterEncoding=utf8&autoReconnect=true&failOverReadOnly=false 5 | password: root 6 | username: root -------------------------------------------------------------------------------- /src/main/java/com/chenhaiyang/plugin/mybatis/sensitive/annotation/EncryptField.java: -------------------------------------------------------------------------------- 1 | package com.chenhaiyang.plugin.mybatis.sensitive.annotation; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * 标记了注解的字段会在写请求时对数据进行加密,在写请求时进行解密 7 | * @author chenhaiyang 8 | */ 9 | @Target({ElementType.FIELD,ElementType.METHOD}) 10 | @Retention(RetentionPolicy.RUNTIME) 11 | @Inherited 12 | @Documented 13 | public @interface EncryptField { 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/chenhaiyang/plugin/mybatis/sensitive/type/SensitiveTypeHandler.java: -------------------------------------------------------------------------------- 1 | package com.chenhaiyang.plugin.mybatis.sensitive.type; 2 | 3 | /** 4 | * 脱敏处理类 5 | * @author ; 6 | */ 7 | public interface SensitiveTypeHandler { 8 | /** 9 | * 获取脱敏的类型枚举 10 | * @return ; 11 | */ 12 | SensitiveType getSensitiveType(); 13 | /** 14 | * 对数据的值进行脱敏处理 15 | * @param src src 16 | * @return 脱敏后的数据 17 | */ 18 | String handle(Object src); 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/chenhaiyang/plugin/mybatis/sensitive/annotation/SensitiveEncryptEnabled.java: -------------------------------------------------------------------------------- 1 | package com.chenhaiyang.plugin.mybatis.sensitive.annotation; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * 标记在DTO类上,用于说明是否支持加解密 7 | * @author ; 8 | */ 9 | @Target({ElementType.TYPE}) 10 | @Retention(RetentionPolicy.RUNTIME) 11 | @Inherited 12 | @Documented 13 | public @interface SensitiveEncryptEnabled { 14 | /** 15 | * 是否开启加解密和脱敏模式 16 | * @return SensitiveEncryptEnabled 17 | */ 18 | boolean value() default true; 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/chenhaiyang/plugin/mybatis/sensitive/annotation/SensitiveJSONField.java: -------------------------------------------------------------------------------- 1 | package com.chenhaiyang.plugin.mybatis.sensitive.annotation; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * 对json内的key_value进行脱敏 7 | * @author chenhaiyang 8 | */ 9 | @Target({ElementType.FIELD,ElementType.METHOD}) 10 | @Retention(RetentionPolicy.RUNTIME) 11 | @Inherited 12 | @Documented 13 | public @interface SensitiveJSONField { 14 | /** 15 | * 需要脱敏的字段的数组 16 | * @return 返回结果 17 | */ 18 | SensitiveJSONFieldKey[] sensitivelist(); 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/chenhaiyang/plugin/mybatis/sensitive/Encrypt.java: -------------------------------------------------------------------------------- 1 | package com.chenhaiyang.plugin.mybatis.sensitive; 2 | 3 | /** 4 | * 加解密接口 5 | * @author chenhaiyang 6 | */ 7 | public interface Encrypt { 8 | /** 9 | * 对字符串进行加密存储 10 | * @param src 源 11 | * @return 返回加密后的密文 12 | * @throws RuntimeException 算法异常 13 | */ 14 | String encrypt(String src); 15 | 16 | /** 17 | * 对加密后的字符串进行解密 18 | * @param encrypt 加密后的字符串 19 | * @return 返回解密后的原文 20 | * @throws RuntimeException 算法异常 21 | */ 22 | String decrypt(String encrypt); 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/chenhaiyang/plugin/mybatis/sensitive/annotation/SensitiveField.java: -------------------------------------------------------------------------------- 1 | package com.chenhaiyang.plugin.mybatis.sensitive.annotation; 2 | 3 | import com.chenhaiyang.plugin.mybatis.sensitive.type.SensitiveType; 4 | 5 | import java.lang.annotation.*; 6 | 7 | 8 | /** 9 | * 标注在字段上,用以说明字段上那些类型需要脱敏 10 | * 脱敏后,插件在写请求后对数据脱敏后存在数据库,对读请求不拦截 11 | * @author ; 12 | */ 13 | @Target({ElementType.FIELD,ElementType.METHOD}) 14 | @Retention(RetentionPolicy.RUNTIME) 15 | @Inherited 16 | @Documented 17 | public @interface SensitiveField { 18 | /** 19 | * 脱敏类型 20 | * 不同的脱敏类型置换*的方式不同 21 | * @return SensitiveType 22 | */ 23 | SensitiveType value(); 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/chenhaiyang/plugin/mybatis/sensitive/type/handler/NoneSensitiveHandler.java: -------------------------------------------------------------------------------- 1 | package com.chenhaiyang.plugin.mybatis.sensitive.type.handler; 2 | 3 | import com.chenhaiyang.plugin.mybatis.sensitive.type.SensitiveType; 4 | import com.chenhaiyang.plugin.mybatis.sensitive.type.SensitiveTypeHandler; 5 | 6 | /** 7 | * 不脱敏 8 | * @author chenhaiyang 9 | */ 10 | public class NoneSensitiveHandler implements SensitiveTypeHandler { 11 | @Override 12 | public SensitiveType getSensitiveType() { 13 | return SensitiveType.NONE; 14 | } 15 | 16 | @Override 17 | public String handle(Object src) { 18 | if(src!=null){ 19 | return src.toString(); 20 | } 21 | return null; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/chenhaiyang/plugin/mybatis/sensitive/annotation/SensitiveJSONFieldKey.java: -------------------------------------------------------------------------------- 1 | package com.chenhaiyang.plugin.mybatis.sensitive.annotation; 2 | 3 | import com.chenhaiyang.plugin.mybatis.sensitive.type.SensitiveType; 4 | 5 | import java.lang.annotation.*; 6 | 7 | /** 8 | * json字段中需要脱敏的key字段以及key脱敏类型 9 | * @author chenhaiyang 10 | */ 11 | @Target({ElementType.FIELD,ElementType.METHOD}) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @Inherited 14 | @Documented 15 | public @interface SensitiveJSONFieldKey { 16 | /** 17 | * json中的key的类型 18 | * @return key 19 | */ 20 | String key(); 21 | /** 22 | * 脱敏类型 23 | * 不同的脱敏类型置换*的方式不同 24 | * @return SensitiveType 25 | */ 26 | SensitiveType type(); 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/com/chenhaiyang/plugin/mybatis/sensitive/test/encrypt/AesEncryptTest.java: -------------------------------------------------------------------------------- 1 | package com.chenhaiyang.plugin.mybatis.sensitive.test.encrypt; 2 | 3 | import com.chenhaiyang.plugin.mybatis.sensitive.encrypt.AesSupport; 4 | import org.junit.Test; 5 | 6 | import java.security.NoSuchAlgorithmException; 7 | 8 | public class AesEncryptTest { 9 | 10 | @Test 11 | public void test() throws NoSuchAlgorithmException { 12 | String key="1870577f29b17d6787782f35998c4a79"; 13 | String src ="测试原文"; 14 | AesSupport aesSupport = new AesSupport(key); 15 | String result = aesSupport.encrypt(src); 16 | System.out.println(result); 17 | String src2 = aesSupport.decrypt(result); 18 | System.out.println(src2); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/com/chenhaiyang/plugin/mybatis/sensitive/test/all/SpringBootTestApplication.java: -------------------------------------------------------------------------------- 1 | package com.chenhaiyang.plugin.mybatis.sensitive.test.all; 2 | 3 | import org.springframework.boot.WebApplicationType; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.boot.builder.SpringApplicationBuilder; 6 | import org.springframework.context.annotation.ComponentScan; 7 | 8 | @ComponentScan("com.chenhaiyang.plugin.mybatis.sensitive.test") 9 | @SpringBootApplication 10 | public class SpringBootTestApplication { 11 | public static void main(String[] args) { 12 | new SpringApplicationBuilder(SpringBootTestApplication.class) 13 | .web(WebApplicationType.NONE) 14 | .run(args); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/chenhaiyang/plugin/mybatis/sensitive/type/SensitiveType.java: -------------------------------------------------------------------------------- 1 | package com.chenhaiyang.plugin.mybatis.sensitive.type; 2 | 3 | /** 4 | * 脱敏类型 5 | * @author ; 6 | */ 7 | public enum SensitiveType { 8 | /** 9 | * 不脱敏 10 | */ 11 | NONE, 12 | /** 13 | * 默认脱敏方式 14 | */ 15 | DEFAUL, 16 | /** 17 | * 中文名 18 | */ 19 | CHINESE_NAME, 20 | /** 21 | * 身份证号 22 | */ 23 | ID_CARD, 24 | /** 25 | * 座机号 26 | */ 27 | FIXED_PHONE, 28 | /** 29 | * 手机号 30 | */ 31 | MOBILE_PHONE, 32 | /** 33 | * 地址 34 | */ 35 | ADDRESS, 36 | /** 37 | * 电子邮件 38 | */ 39 | EMAIL, 40 | /** 41 | * 银行卡 42 | */ 43 | BANK_CARD, 44 | /** 45 | * 公司开户银行联号 46 | */ 47 | CNAPS_CODE, 48 | /** 49 | * 支付签约协议号 50 | */ 51 | PAY_SIGN_NO 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/chenhaiyang/plugin/mybatis/sensitive/utils/PluginUtils.java: -------------------------------------------------------------------------------- 1 | package com.chenhaiyang.plugin.mybatis.sensitive.utils; 2 | 3 | import org.apache.ibatis.reflection.MetaObject; 4 | import org.apache.ibatis.reflection.SystemMetaObject; 5 | 6 | import java.lang.reflect.Proxy; 7 | 8 | /** 9 | * 插件工具类,获取到对象的真实对象,可能存在多层代理 10 | * @author ; 11 | */ 12 | public final class PluginUtils { 13 | 14 | private PluginUtils() { 15 | // to do nothing 16 | } 17 | 18 | /** 19 | * 获得真正的处理对象,可能多层代理. 20 | */ 21 | @SuppressWarnings("unchecked") 22 | public static T realTarget(Object target) { 23 | if (Proxy.isProxyClass(target.getClass())) { 24 | MetaObject metaObject = SystemMetaObject.forObject(target); 25 | return realTarget(metaObject.getValue("h.target")); 26 | } 27 | return (T) target; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/chenhaiyang/plugin/mybatis/sensitive/type/handler/CnapsSensitiveHandler.java: -------------------------------------------------------------------------------- 1 | package com.chenhaiyang.plugin.mybatis.sensitive.type.handler; 2 | 3 | import com.chenhaiyang.plugin.mybatis.sensitive.type.SensitiveType; 4 | import com.chenhaiyang.plugin.mybatis.sensitive.type.SensitiveTypeHandler; 5 | import org.apache.commons.lang3.StringUtils; 6 | 7 | /** 8 | * 公司开户银行联号 9 | * 前四位明文,后面脱敏 10 | * @author chenhaiyang 11 | */ 12 | public class CnapsSensitiveHandler implements SensitiveTypeHandler { 13 | @Override 14 | public SensitiveType getSensitiveType() { 15 | return SensitiveType.CNAPS_CODE; 16 | } 17 | 18 | @Override 19 | public String handle(Object src) { 20 | if(src==null){ 21 | return null; 22 | 23 | } 24 | String snapCard =src.toString(); 25 | 26 | return StringUtils.rightPad(StringUtils.left(snapCard, 4), StringUtils.length(snapCard), "*"); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/chenhaiyang/plugin/mybatis/sensitive/type/handler/NameSensitiveHandler.java: -------------------------------------------------------------------------------- 1 | package com.chenhaiyang.plugin.mybatis.sensitive.type.handler; 2 | 3 | import com.chenhaiyang.plugin.mybatis.sensitive.type.SensitiveType; 4 | import com.chenhaiyang.plugin.mybatis.sensitive.type.SensitiveTypeHandler; 5 | import org.apache.commons.lang3.StringUtils; 6 | 7 | /** 8 | * 姓名脱敏的解析类 9 | * 中文姓名只显示第一个汉字,其他隐藏为2个星号 10 | * 例子:李** 11 | * 张三丰 :张** 12 | * @author ; 13 | */ 14 | public class NameSensitiveHandler implements SensitiveTypeHandler { 15 | @Override 16 | public SensitiveType getSensitiveType() { 17 | return SensitiveType.CHINESE_NAME; 18 | } 19 | 20 | @Override 21 | public String handle(Object src) { 22 | if (src==null) { 23 | return ""; 24 | } 25 | String fullName = src.toString(); 26 | String name = StringUtils.left(fullName, 1); 27 | return StringUtils.rightPad(name, StringUtils.length(fullName), "*"); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/test/java/com/chenhaiyang/plugin/mybatis/sensitive/test/all/mapper/UserMapper.java: -------------------------------------------------------------------------------- 1 | package com.chenhaiyang.plugin.mybatis.sensitive.test.all.mapper; 2 | 3 | import com.chenhaiyang.plugin.mybatis.sensitive.test.all.dto.UserDTO; 4 | import org.apache.ibatis.annotations.Mapper; 5 | import org.apache.ibatis.annotations.Param; 6 | 7 | import java.util.List; 8 | @Mapper 9 | public interface UserMapper { 10 | 11 | int insert(UserDTO userDTO); 12 | 13 | int insert2(@Param("userName") String userName,@Param("idcard") String idcard); 14 | List findAll(); 15 | List findAll2(); 16 | UserDTO findOne(UserDTO userDTO); 17 | /** 18 | * 测试 通过加密字段查询的情况 19 | * @param userDTO ; 20 | * @return ; 21 | */ 22 | List findByCondition(UserDTO userDTO); 23 | 24 | /** 25 | * 测试更新时带条件字段,update的字段里有需要加密的字段,where条件里也有需要加密的 26 | * @param userDTO userDto 27 | * @return return 28 | */ 29 | int updateByCondition(UserDTO userDTO); 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/chenhaiyang/plugin/mybatis/sensitive/type/handler/MobilePhoneSensitiveHandler.java: -------------------------------------------------------------------------------- 1 | package com.chenhaiyang.plugin.mybatis.sensitive.type.handler; 2 | 3 | import com.chenhaiyang.plugin.mybatis.sensitive.type.SensitiveType; 4 | import com.chenhaiyang.plugin.mybatis.sensitive.type.SensitiveTypeHandler; 5 | import org.apache.commons.lang3.StringUtils; 6 | 7 | /** 8 | * 手机号脱敏处理类 9 | * 18233583070 脱敏后: 182****3030 10 | * @author ; 11 | */ 12 | public class MobilePhoneSensitiveHandler implements SensitiveTypeHandler { 13 | @Override 14 | public SensitiveType getSensitiveType() { 15 | return SensitiveType.MOBILE_PHONE; 16 | } 17 | 18 | @Override 19 | public String handle(Object src) { 20 | if(src==null){ 21 | return null; 22 | } 23 | String value = src.toString(); 24 | return StringUtils.left(value, 3).concat(StringUtils.removeStart(StringUtils.leftPad(StringUtils.right(value, 4), StringUtils.length(value), "*"), "***")); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/chenhaiyang/plugin/mybatis/sensitive/type/handler/FixedPhoneSensitiveHandler.java: -------------------------------------------------------------------------------- 1 | package com.chenhaiyang.plugin.mybatis.sensitive.type.handler; 2 | 3 | import com.chenhaiyang.plugin.mybatis.sensitive.type.SensitiveType; 4 | import com.chenhaiyang.plugin.mybatis.sensitive.type.SensitiveTypeHandler; 5 | import org.apache.commons.lang3.StringUtils; 6 | 7 | /** 8 | * 座机信息脱敏 9 | * 座机的前2位和后4位保留,其余的隐藏。 10 | * @author chenhaiyang 11 | */ 12 | public class FixedPhoneSensitiveHandler implements SensitiveTypeHandler { 13 | @Override 14 | public SensitiveType getSensitiveType() { 15 | return SensitiveType.FIXED_PHONE; 16 | } 17 | 18 | @Override 19 | public String handle(Object src) { 20 | if(src==null){ 21 | return null; 22 | } 23 | String fixedPhone=src.toString(); 24 | return StringUtils.left(fixedPhone, 2).concat(StringUtils.removeStart(StringUtils.leftPad(StringUtils.right(fixedPhone, 4), StringUtils.length(fixedPhone), "*"), "***")); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/chenhaiyang/plugin/mybatis/sensitive/utils/JsonUtils.java: -------------------------------------------------------------------------------- 1 | package com.chenhaiyang.plugin.mybatis.sensitive.utils; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.TypeReference; 5 | import com.alibaba.fastjson.serializer.SerializerFeature; 6 | 7 | import java.util.LinkedHashMap; 8 | import java.util.Map; 9 | 10 | /** 11 | * json工具类 12 | * @author chenhaiyang 13 | */ 14 | public class JsonUtils { 15 | /** 16 | * 将json字符串转化为StringObject类型的map 17 | * @param jsonStr json字符串 18 | * @return map 19 | */ 20 | public static Map parseToObjectMap(String jsonStr) { 21 | return JSON.parseObject(jsonStr,new TypeReference>(){}); 22 | } 23 | 24 | /** 25 | * 将map转化为json字符串 26 | * @param params 参数集合 27 | * @return json 28 | */ 29 | public static String parseMaptoJSONString(Map params){ 30 | return JSON.toJSONString(params, SerializerFeature.WriteMapNullValue); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/chenhaiyang/plugin/mybatis/sensitive/type/handler/IDCardSensitiveHandler.java: -------------------------------------------------------------------------------- 1 | package com.chenhaiyang.plugin.mybatis.sensitive.type.handler; 2 | 3 | import com.chenhaiyang.plugin.mybatis.sensitive.type.SensitiveType; 4 | import com.chenhaiyang.plugin.mybatis.sensitive.type.SensitiveTypeHandler; 5 | import org.apache.commons.lang3.StringUtils; 6 | 7 | /** 8 | * 身份证号脱敏类型 9 | * 前3位,后4位 10 | * 130722199102323232 脱敏后: 130*************3232 11 | * @author ; 12 | */ 13 | public class IDCardSensitiveHandler implements SensitiveTypeHandler { 14 | @Override 15 | public SensitiveType getSensitiveType() { 16 | return SensitiveType.ID_CARD; 17 | } 18 | 19 | @Override 20 | public String handle(Object src) { 21 | if(src==null){ 22 | return null; 23 | } 24 | String idCard = src.toString(); 25 | return StringUtils.left(idCard, 3).concat(StringUtils.removeStart(StringUtils.leftPad(StringUtils.right(idCard, 4), StringUtils.length(idCard), "*"), "***")); 26 | 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/chenhaiyang/plugin/mybatis/sensitive/type/handler/BandCardSensitiveHandler.java: -------------------------------------------------------------------------------- 1 | package com.chenhaiyang.plugin.mybatis.sensitive.type.handler; 2 | 3 | import com.chenhaiyang.plugin.mybatis.sensitive.type.SensitiveType; 4 | import com.chenhaiyang.plugin.mybatis.sensitive.type.SensitiveTypeHandler; 5 | import org.apache.commons.lang3.StringUtils; 6 | 7 | /** 8 | * 银行卡号脱敏 9 | * 只留前四位和后四位 10 | * 6227 0383 3938 3938 393 脱敏结果: 6227 **** **** ***8 393 11 | * @author chenhaiyang 12 | */ 13 | public class BandCardSensitiveHandler implements SensitiveTypeHandler{ 14 | @Override 15 | public SensitiveType getSensitiveType() { 16 | return SensitiveType.BANK_CARD; 17 | } 18 | 19 | @Override 20 | public String handle(Object src) { 21 | if(src==null){ 22 | return null; 23 | } 24 | String bankCard = src.toString(); 25 | return StringUtils.left(bankCard, 4).concat(StringUtils.removeStart(StringUtils.leftPad(StringUtils.right(bankCard, 4), StringUtils.length(bankCard), "*"), "***")); 26 | 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/chenhaiyang/plugin/mybatis/sensitive/type/handler/PaySignNoSensitiveHandler.java: -------------------------------------------------------------------------------- 1 | package com.chenhaiyang.plugin.mybatis.sensitive.type.handler; 2 | 3 | import com.chenhaiyang.plugin.mybatis.sensitive.type.SensitiveType; 4 | import com.chenhaiyang.plugin.mybatis.sensitive.type.SensitiveTypeHandler; 5 | import org.apache.commons.lang3.StringUtils; 6 | 7 | /** 8 | * 签约协议号脱敏方式 9 | * 19031317273364059018 10 | * 签约协议号脱敏格式为前6位后6位保留明文,中间脱敏 11 | * @author chenhaiyang 12 | */ 13 | public class PaySignNoSensitiveHandler implements SensitiveTypeHandler { 14 | @Override 15 | public SensitiveType getSensitiveType() { 16 | return SensitiveType.PAY_SIGN_NO; 17 | } 18 | 19 | @Override 20 | public String handle(Object src) { 21 | if(src==null){ 22 | return null; 23 | } 24 | String agreementNo = src.toString(); 25 | return StringUtils.left(agreementNo, 6).concat(StringUtils.removeStart(StringUtils.leftPad(StringUtils.right(agreementNo, 6), StringUtils.length(agreementNo), "*"), "***")); 26 | 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/chenhaiyang/plugin/mybatis/sensitive/annotation/SensitiveBinded.java: -------------------------------------------------------------------------------- 1 | package com.chenhaiyang.plugin.mybatis.sensitive.annotation; 2 | 3 | import com.chenhaiyang.plugin.mybatis.sensitive.type.SensitiveType; 4 | 5 | import java.lang.annotation.*; 6 | 7 | /** 8 | * 此注解适用于如下场景: 9 | * 例如,数据库只存了username字段的加密信息,没有脱敏冗余字段。 10 | * 我的响应类里希望将数据库的加密的某个字段映射到响应的两个属性上(一个解密的,一个脱敏的)就可以使用该注解。 11 | * 例如,dto里有如下字段 12 | * EncryptField 13 | * private String username 14 | * 15 | * SensitiveBinded(bindField = "userName",value = SensitiveType.CHINESE_NAME) 16 | * private String userNameOnlyDTO; 17 | * 18 | * 19 | * 则当查询出结果时,userNameOnlyDTO会赋值为username解密后再脱敏的值。 20 | */ 21 | @Target({ElementType.FIELD,ElementType.METHOD}) 22 | @Retention(RetentionPolicy.RUNTIME) 23 | @Inherited 24 | @Documented 25 | public @interface SensitiveBinded { 26 | /** 27 | * 该属性从哪个字段取得 28 | * @return 返回字段名 29 | */ 30 | String bindField(); 31 | /** 32 | * 脱敏类型 33 | * 不同的脱敏类型置换*的方式不同 34 | * @return SensitiveType 35 | */ 36 | SensitiveType value(); 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/chenhaiyang/plugin/mybatis/sensitive/type/handler/EmailSensitiveHandler.java: -------------------------------------------------------------------------------- 1 | package com.chenhaiyang.plugin.mybatis.sensitive.type.handler; 2 | 3 | import com.chenhaiyang.plugin.mybatis.sensitive.type.SensitiveTypeHandler; 4 | import com.chenhaiyang.plugin.mybatis.sensitive.type.SensitiveType; 5 | import org.apache.commons.lang3.StringUtils; 6 | 7 | /** 8 | * 邮件信息脱敏处理类 9 | * 邮箱前缀仅显示第一个字母,前缀其他隐藏,用星号代替,@及后面的地址显示 10 | * 例子:g**@163.com 11 | * @author ; 12 | */ 13 | public class EmailSensitiveHandler implements SensitiveTypeHandler { 14 | @Override 15 | public SensitiveType getSensitiveType() { 16 | return SensitiveType.EMAIL; 17 | } 18 | 19 | @Override 20 | public String handle(Object src) { 21 | if(src==null){ 22 | return null; 23 | } 24 | String email = src.toString(); 25 | int index = StringUtils.indexOf(email, "@"); 26 | if (index <= 1) { 27 | return email; 28 | } else { 29 | return StringUtils.rightPad(StringUtils.left(email, 1), index, "*").concat( 30 | StringUtils.mid(email, index, StringUtils.length(email))); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/test/resources/user.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Navicat Premium Data Transfer 3 | 4 | Source Server : 本地数据库 5 | Source Server Type : MySQL 6 | Source Server Version : 50719 7 | Source Host : localhost 8 | Source Database : mybatis_plugin_test 9 | 10 | Target Server Type : MySQL 11 | Target Server Version : 50719 12 | File Encoding : utf-8 13 | 14 | Date: 06/05/2019 00:29:39 AM 15 | */ 16 | 17 | SET NAMES utf8mb4; 18 | SET FOREIGN_KEY_CHECKS = 0; 19 | 20 | -- ---------------------------- 21 | -- Table structure for `user` 22 | -- ---------------------------- 23 | DROP TABLE IF EXISTS `user`; 24 | CREATE TABLE `user` ( 25 | `id` int(11) NOT NULL AUTO_INCREMENT, 26 | `user_name` varchar(200) DEFAULT NULL COMMENT '用户名', 27 | `user_name_sensitive` varchar(200) DEFAULT NULL COMMENT '用户名-脱敏后', 28 | `idcard` varchar(200) DEFAULT NULL COMMENT '身份证号', 29 | `idcard_sensitive` varchar(200) DEFAULT NULL COMMENT '脱敏的卡号', 30 | `json_str` varchar(500) DEFAULT '' COMMENT '一个json串,可能含有敏感信息', 31 | `age` int(11) DEFAULT NULL, 32 | `email` varchar(200) DEFAULT NULL COMMENT '邮箱', 33 | PRIMARY KEY (`id`) 34 | ) ENGINE=InnoDB AUTO_INCREMENT=46 DEFAULT CHARSET=utf8; 35 | 36 | SET FOREIGN_KEY_CHECKS = 1; 37 | -------------------------------------------------------------------------------- /src/main/java/com/chenhaiyang/plugin/mybatis/sensitive/type/handler/AddressSensitiveHandler.java: -------------------------------------------------------------------------------- 1 | package com.chenhaiyang.plugin.mybatis.sensitive.type.handler; 2 | 3 | import com.chenhaiyang.plugin.mybatis.sensitive.type.SensitiveType; 4 | import com.chenhaiyang.plugin.mybatis.sensitive.type.SensitiveTypeHandler; 5 | import org.apache.commons.lang3.StringUtils; 6 | 7 | /** 8 | * 收货地址脱敏处理类 9 | * 地址只显示到地区,不显示详细地址;我们要对个人信息增强保护 10 | * 例子:北京市海淀区**** 11 | * @author ; 12 | */ 13 | public class AddressSensitiveHandler implements SensitiveTypeHandler { 14 | 15 | private static final int RIGHT=10; 16 | private static final int LEFT=6; 17 | @Override 18 | public SensitiveType getSensitiveType() { 19 | return SensitiveType.ADDRESS; 20 | } 21 | 22 | @Override 23 | public String handle(Object src) { 24 | if(src==null){ 25 | return null; 26 | } 27 | String address = src.toString(); 28 | int length = StringUtils.length(address); 29 | if(length>RIGHT+LEFT){ 30 | return StringUtils.rightPad(StringUtils.left(address, length-RIGHT), length, "*"); 31 | } 32 | if(length<=LEFT){ 33 | return address; 34 | }else{ 35 | return address.substring(0,LEFT+1).concat("*****"); 36 | } 37 | 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/test/java/com/chenhaiyang/plugin/mybatis/sensitive/test/all/config/EncryptPluginConfig.java: -------------------------------------------------------------------------------- 1 | package com.chenhaiyang.plugin.mybatis.sensitive.test.all.config; 2 | 3 | import com.chenhaiyang.plugin.mybatis.sensitive.Encrypt; 4 | import com.chenhaiyang.plugin.mybatis.sensitive.encrypt.AesSupport; 5 | import com.chenhaiyang.plugin.mybatis.sensitive.interceptor.DecryptReadInterceptor; 6 | import com.chenhaiyang.plugin.mybatis.sensitive.interceptor.SensitiveAndEncryptWriteInterceptor; 7 | import org.mybatis.spring.boot.autoconfigure.ConfigurationCustomizer; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | 11 | /** 12 | * 插件配置 13 | */ 14 | @Configuration 15 | public class EncryptPluginConfig { 16 | 17 | 18 | @Bean 19 | Encrypt encryptor() throws Exception{ ; 20 | return new AesSupport("1870577f29b17d6787782f35998c4a79"); 21 | } 22 | 23 | @Bean 24 | ConfigurationCustomizer configurationCustomizer() throws Exception{ 25 | DecryptReadInterceptor decryptReadInterceptor = new DecryptReadInterceptor(encryptor()); 26 | SensitiveAndEncryptWriteInterceptor sensitiveAndEncryptWriteInterceptor = new SensitiveAndEncryptWriteInterceptor(encryptor()); 27 | 28 | return (configuration) -> { 29 | configuration.addInterceptor(decryptReadInterceptor); 30 | configuration.addInterceptor(sensitiveAndEncryptWriteInterceptor); 31 | }; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/com/chenhaiyang/plugin/mybatis/sensitive/test/all/dto/UserDTO.java: -------------------------------------------------------------------------------- 1 | package com.chenhaiyang.plugin.mybatis.sensitive.test.all.dto; 2 | 3 | import com.chenhaiyang.plugin.mybatis.sensitive.annotation.*; 4 | import com.chenhaiyang.plugin.mybatis.sensitive.type.SensitiveType; 5 | import lombok.Data; 6 | 7 | @SensitiveEncryptEnabled 8 | @Data 9 | public class UserDTO { 10 | 11 | private static final int vid=33; 12 | 13 | private Integer id; 14 | /** 15 | * 用户名 16 | */ 17 | @EncryptField 18 | private String userName; 19 | /** 20 | * 脱敏的用户名 21 | */ 22 | @SensitiveField(SensitiveType.CHINESE_NAME) 23 | private String userNameSensitive; 24 | /** 25 | * 值的赋值不从数据库取,而是从userName字段获得。 26 | */ 27 | @SensitiveBinded(bindField = "userName",value = SensitiveType.CHINESE_NAME) 28 | private String userNameOnlyDTO; 29 | /** 30 | * 身份证号 31 | */ 32 | @EncryptField 33 | private String idcard; 34 | /** 35 | * 脱敏的身份证号 36 | */ 37 | @SensitiveField(SensitiveType.ID_CARD) 38 | private String idcardSensitive; 39 | /** 40 | * 一个json串,需要脱敏 41 | * SensitiveJSONField标记json中需要脱敏的字段 42 | */ 43 | @SensitiveJSONField(sensitivelist = { 44 | @SensitiveJSONFieldKey(key = "idcard",type = SensitiveType.ID_CARD), 45 | @SensitiveJSONFieldKey(key = "username",type = SensitiveType.CHINESE_NAME), 46 | }) 47 | private String jsonStr; 48 | 49 | private int age; 50 | 51 | @SensitiveField(SensitiveType.EMAIL) 52 | private String email; 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/chenhaiyang/plugin/mybatis/sensitive/utils/Hex.java: -------------------------------------------------------------------------------- 1 | package com.chenhaiyang.plugin.mybatis.sensitive.utils; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | 5 | /** 6 | * 工具类 7 | * @author chenhaiyang 8 | */ 9 | public class Hex { 10 | /** 11 | * char 转 byte的适配器 12 | */ 13 | private static final String BYTE_CONVERTE="0123456789ABCDEF"; 14 | 15 | /** 16 | * 将byte转化为16进制的字符串 17 | * @param src byte[] 数组 18 | * @return 返回16进制字符串 19 | */ 20 | public static String bytesToHexString(byte[] src){ 21 | 22 | if (src == null || src.length <= 0) { 23 | return null; 24 | } 25 | 26 | StringBuilder stringBuilder = new StringBuilder(""); 27 | for (int j:src) { 28 | int v = j & 0xFF; 29 | String hv = Integer.toHexString(v); 30 | if (hv.length() < 2) { 31 | stringBuilder.append(0); 32 | } 33 | stringBuilder.append(hv); 34 | } 35 | return stringBuilder.toString(); 36 | } 37 | 38 | /** 39 | * 将16进制的字符串转化为byte[] 数组 40 | * @param hexString 16进制字符串 41 | * @return byte[] 数组 42 | */ 43 | public static byte[] hexStringToBytes(String hexString) { 44 | if (StringUtils.isEmpty(hexString)) { 45 | return new byte[0]; 46 | } 47 | hexString = hexString.toUpperCase(); 48 | int length = hexString.length() / 2; 49 | char[] hexChars = hexString.toCharArray(); 50 | byte[] d = new byte[length]; 51 | for (int i = 0; i < length; i++) { 52 | int pos = i * 2; 53 | d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1])); 54 | } 55 | return d; 56 | } 57 | 58 | /** 59 | * 将char转化为byte数组 60 | * @param c char 61 | * @return byte 62 | */ 63 | private static byte charToByte(char c) { 64 | return (byte) BYTE_CONVERTE.indexOf(c); 65 | } 66 | } -------------------------------------------------------------------------------- /src/main/java/com/chenhaiyang/plugin/mybatis/sensitive/type/SensitiveTypeRegisty.java: -------------------------------------------------------------------------------- 1 | package com.chenhaiyang.plugin.mybatis.sensitive.type; 2 | 3 | import com.chenhaiyang.plugin.mybatis.sensitive.type.handler.*; 4 | 5 | import java.util.Map; 6 | import java.util.concurrent.ConcurrentHashMap; 7 | 8 | /** 9 | * 脱敏处理注册表 10 | * @author chenhaiyang 11 | */ 12 | @SuppressWarnings("unused") 13 | public class SensitiveTypeRegisty { 14 | 15 | private static final Map HANDLER_REGISTY = new ConcurrentHashMap<>(); 16 | 17 | static { 18 | HANDLER_REGISTY.put(SensitiveType.NONE,new NoneSensitiveHandler()); 19 | HANDLER_REGISTY.put(SensitiveType.DEFAUL,new DafaultSensitiveHandler()); 20 | HANDLER_REGISTY.put(SensitiveType.CHINESE_NAME,new NameSensitiveHandler()); 21 | HANDLER_REGISTY.put(SensitiveType.ID_CARD,new IDCardSensitiveHandler()); 22 | HANDLER_REGISTY.put(SensitiveType.MOBILE_PHONE,new MobilePhoneSensitiveHandler()); 23 | HANDLER_REGISTY.put(SensitiveType.ADDRESS,new AddressSensitiveHandler()); 24 | HANDLER_REGISTY.put(SensitiveType.EMAIL,new EmailSensitiveHandler()); 25 | HANDLER_REGISTY.put(SensitiveType.BANK_CARD,new BandCardSensitiveHandler()); 26 | HANDLER_REGISTY.put(SensitiveType.FIXED_PHONE,new FixedPhoneSensitiveHandler()); 27 | HANDLER_REGISTY.put(SensitiveType.CNAPS_CODE,new CnapsSensitiveHandler()); 28 | HANDLER_REGISTY.put(SensitiveType.PAY_SIGN_NO,new PaySignNoSensitiveHandler()); 29 | } 30 | 31 | public static void put(SensitiveTypeHandler sensitiveTypeHandler){ 32 | HANDLER_REGISTY.put(sensitiveTypeHandler.getSensitiveType(),sensitiveTypeHandler); 33 | } 34 | 35 | public static SensitiveTypeHandler get(SensitiveType sensitiveType){ 36 | 37 | SensitiveTypeHandler sensitiveTypeHandler = HANDLER_REGISTY.get(sensitiveType); 38 | if(sensitiveTypeHandler==null){ 39 | throw new IllegalArgumentException("none sensitiveTypeHandler be found!, type:"+sensitiveType.name()); 40 | } 41 | return sensitiveTypeHandler; 42 | } 43 | 44 | /** 45 | * 是否已经是脱敏过的内容了 46 | * @param src 原始数据 47 | * @return 是否已经脱敏了 48 | */ 49 | public static boolean alreadyBeSentisived(Object src){ 50 | return src==null || src.toString().indexOf("*")>0; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/test/java/com/chenhaiyang/plugin/mybatis/sensitive/test/sensitive/SensitiveHandlerTest.java: -------------------------------------------------------------------------------- 1 | package com.chenhaiyang.plugin.mybatis.sensitive.test.sensitive; 2 | 3 | import com.chenhaiyang.plugin.mybatis.sensitive.type.handler.*; 4 | import org.junit.Test; 5 | 6 | public class SensitiveHandlerTest { 7 | 8 | @Test 9 | public void test(){ 10 | MobilePhoneSensitiveHandler mobilePhoneSensitiveHandler = new MobilePhoneSensitiveHandler(); 11 | String result = mobilePhoneSensitiveHandler.handle("18233583070"); 12 | System.out.println(result); 13 | } 14 | 15 | @Test 16 | public void test2(){ 17 | NameSensitiveHandler nameSensitiveHandler = new NameSensitiveHandler(); 18 | String result = nameSensitiveHandler.handle("克扽儿阿里巴斯"); 19 | System.out.println(result); 20 | } 21 | 22 | @Test 23 | public void test3(){ 24 | EmailSensitiveHandler nameSensitiveHandler = new EmailSensitiveHandler(); 25 | String result = nameSensitiveHandler.handle("er@ruubddfdfdfdypay.com"); 26 | System.out.println(result); 27 | } 28 | @Test 29 | public void test4(){ 30 | AddressSensitiveHandler nameSensitiveHandler = new AddressSensitiveHandler(); 31 | String result = nameSensitiveHandler.handle("啊事实上是sds ds dsd sds d "); 32 | System.out.println(result); 33 | } 34 | 35 | @Test 36 | public void test5(){ 37 | BandCardSensitiveHandler nameSensitiveHandler = new BandCardSensitiveHandler(); 38 | String result = nameSensitiveHandler.handle("622000"); 39 | System.out.println(result); 40 | } 41 | @Test 42 | public void test6(){ 43 | IDCardSensitiveHandler nameSensitiveHandler = new IDCardSensitiveHandler(); 44 | String result = nameSensitiveHandler.handle("130722199102323232"); 45 | System.out.println(result); 46 | } 47 | 48 | @Test 49 | public void test7(){ 50 | CnapsSensitiveHandler nameSensitiveHandler = new CnapsSensitiveHandler(); 51 | String result = nameSensitiveHandler.handle("130722199102323232"); 52 | System.out.println(result); 53 | } 54 | @Test 55 | public void test8(){ 56 | FixedPhoneSensitiveHandler nameSensitiveHandler = new FixedPhoneSensitiveHandler(); 57 | String result = nameSensitiveHandler.handle("86-10-66778899"); 58 | System.out.println(result); 59 | } 60 | 61 | @Test 62 | public void test9(){ 63 | PaySignNoSensitiveHandler nameSensitiveHandler = new PaySignNoSensitiveHandler(); 64 | String result = nameSensitiveHandler.handle("19031317273364059018"); 65 | System.out.println(result); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/chenhaiyang/plugin/mybatis/sensitive/type/handler/DafaultSensitiveHandler.java: -------------------------------------------------------------------------------- 1 | package com.chenhaiyang.plugin.mybatis.sensitive.type.handler; 2 | 3 | import com.chenhaiyang.plugin.mybatis.sensitive.type.SensitiveType; 4 | import com.chenhaiyang.plugin.mybatis.sensitive.type.SensitiveTypeHandler; 5 | 6 | /** 7 | * 默认脱敏处理类 8 | * @author chenhaiyang 9 | */ 10 | public class DafaultSensitiveHandler implements SensitiveTypeHandler { 11 | 12 | private static final int SIZE = 6; 13 | private static final int TWO =2; 14 | private static final String SYMBOL = "*"; 15 | 16 | @Override 17 | public SensitiveType getSensitiveType() { 18 | return SensitiveType.DEFAUL; 19 | } 20 | 21 | @Override 22 | public String handle(Object src) { 23 | if (null == src || "".equals(src)) { 24 | return null; 25 | } 26 | String value =src.toString(); 27 | 28 | int len = value.length(); 29 | int pamaone = len / TWO; 30 | int pamatwo = pamaone - 1; 31 | int pamathree = len % TWO; 32 | StringBuilder stringBuilder = new StringBuilder(); 33 | if (len <= TWO) { 34 | if (pamathree == 1) { 35 | return SYMBOL; 36 | } 37 | stringBuilder.append(SYMBOL); 38 | stringBuilder.append(value.charAt(len - 1)); 39 | } else { 40 | if (pamatwo <= 0) { 41 | stringBuilder.append(value.substring(0, 1)); 42 | stringBuilder.append(SYMBOL); 43 | stringBuilder.append(value.substring(len - 1, len)); 44 | 45 | } else if (pamatwo >= SIZE / TWO && SIZE + 1 != len) { 46 | int pamafive = (len - SIZE) / 2; 47 | stringBuilder.append(value.substring(0, pamafive)); 48 | for (int i = 0; i < SIZE; i++) { 49 | stringBuilder.append(SYMBOL); 50 | } 51 | if (ispamaThree(pamathree)) { 52 | stringBuilder.append(value.substring(len - pamafive, len)); 53 | } else { 54 | stringBuilder.append(value.substring(len - (pamafive + 1), len)); 55 | } 56 | } else { 57 | int pamafour = len - 2; 58 | stringBuilder.append(value.substring(0, 1)); 59 | for (int i = 0; i < pamafour; i++) { 60 | stringBuilder.append(SYMBOL); 61 | } 62 | stringBuilder.append(value.substring(len - 1, len)); 63 | } 64 | } 65 | return stringBuilder.toString(); 66 | } 67 | 68 | @SuppressWarnings("all") 69 | private boolean ispamaThree(int pamathree){ 70 | return (pamathree == 0 && SIZE / 2 == 0) || (pamathree != 0 && SIZE % 2 != 0); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/test/java/com/chenhaiyang/plugin/mybatis/sensitive/test/all/testcase/MapperTestCase.java: -------------------------------------------------------------------------------- 1 | package com.chenhaiyang.plugin.mybatis.sensitive.test.all.testcase; 2 | 3 | import com.chenhaiyang.plugin.mybatis.sensitive.test.all.dto.UserDTO; 4 | import com.chenhaiyang.plugin.mybatis.sensitive.test.all.mapper.UserMapper; 5 | import com.chenhaiyang.plugin.mybatis.sensitive.utils.JsonUtils; 6 | import org.springframework.stereotype.Service; 7 | 8 | import javax.annotation.PostConstruct; 9 | import javax.annotation.Resource; 10 | import java.util.HashMap; 11 | import java.util.List; 12 | import java.util.Map; 13 | 14 | @Service 15 | public class MapperTestCase { 16 | 17 | @Resource 18 | private UserMapper userMapper; 19 | 20 | @PostConstruct 21 | public void init(){ 22 | //sql条件是预编译的 23 | //testInsert1(); 24 | // testinsert2(); 25 | testfinbyAll1(); 26 | testfinbyAll2(); 27 | // testfinbyId1(); 28 | // testfinbyConditon1(); 29 | //testUpdateByCondition1(); 30 | 31 | } 32 | 33 | //=======以下的测试用例,sql都是预编译的============== 34 | private void testInsert1() { 35 | UserDTO userDTO = new UserDTO(); 36 | userDTO.setAge(1758695069); 37 | userDTO.setEmail("238484@qq.com"); 38 | userDTO.setIdcard("130722199102900995"); 39 | userDTO.setIdcardSensitive("130722199102900995"); 40 | userDTO.setUserName("张三丰"); 41 | userDTO.setUserNameSensitive("张三丰"); 42 | 43 | 44 | Map jsonMap = new HashMap<>(); 45 | jsonMap.put("username","张三丰"); 46 | jsonMap.put("idcard","13072219991947585896"); 47 | jsonMap.put("age",18); 48 | userDTO.setJsonStr(JsonUtils.parseMaptoJSONString(jsonMap)); 49 | int result = userMapper.insert(userDTO); 50 | System.out.println(result); 51 | } 52 | //这种传值方式,加解密和脱敏无法生效,必须基于javaBean 53 | private void testinsert2() { 54 | int result = userMapper.insert2("马达哈","12222"); 55 | System.out.println(result); 56 | } 57 | 58 | 59 | private void testfinbyAll1() { 60 | 61 | List userDTOS = userMapper.findAll(); 62 | System.out.println(userDTOS); 63 | } 64 | //此种方式是基于resultmap查询 65 | private void testfinbyAll2() { 66 | List userDTOS = userMapper.findAll2(); 67 | System.out.println(userDTOS); 68 | } 69 | 70 | private void testfinbyId1() { 71 | UserDTO userDTO = new UserDTO(); 72 | userDTO.setId(41); 73 | UserDTO userDTO2 = userMapper.findOne(userDTO); 74 | System.out.println(userDTO2); 75 | } 76 | private void testfinbyConditon1() { 77 | UserDTO userDTO = new UserDTO(); 78 | userDTO.setIdcard("130722199102900995"); 79 | List userDTO2 = userMapper.findByCondition(userDTO); 80 | System.out.println(userDTO2); 81 | } 82 | 83 | private void testUpdateByCondition1() { 84 | UserDTO userDTO = new UserDTO(); 85 | userDTO.setId(42); 86 | 87 | UserDTO userDTO2 = userMapper.findOne(userDTO); 88 | userDTO2.setEmail("测试用email@qq.com"); 89 | userDTO2.setIdcard("2344"); 90 | userDTO2.setIdcardSensitive("2344"); 91 | int result = userMapper.updateByCondition(userDTO2); 92 | System.out.println(result); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/com/chenhaiyang/plugin/mybatis/sensitive/encrypt/AesSupport.java: -------------------------------------------------------------------------------- 1 | package com.chenhaiyang.plugin.mybatis.sensitive.encrypt; 2 | 3 | import com.chenhaiyang.plugin.mybatis.sensitive.type.SensitiveType; 4 | import com.chenhaiyang.plugin.mybatis.sensitive.type.SensitiveTypeHandler; 5 | import com.chenhaiyang.plugin.mybatis.sensitive.type.SensitiveTypeRegisty; 6 | import com.chenhaiyang.plugin.mybatis.sensitive.utils.Hex; 7 | import com.chenhaiyang.plugin.mybatis.sensitive.Encrypt; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.apache.commons.lang3.StringUtils; 10 | 11 | import javax.crypto.*; 12 | import javax.crypto.spec.SecretKeySpec; 13 | import java.security.NoSuchAlgorithmException; 14 | import java.security.SecureRandom; 15 | 16 | /** 17 | * 数据脱敏用到的AES加解密类 18 | * @author chenhaiyang 19 | */ 20 | @Slf4j 21 | public class AesSupport implements Encrypt{ 22 | 23 | private static final String KEY_ALGORITHM = "AES"; 24 | private static final String DEFAULT_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding"; 25 | 26 | private String password; 27 | private SecretKeySpec secretKeySpec; 28 | private SensitiveTypeHandler sensitiveTypeHandler = SensitiveTypeRegisty.get(SensitiveType.DEFAUL); 29 | 30 | public AesSupport(String password) throws NoSuchAlgorithmException { 31 | 32 | if(StringUtils.isEmpty(password)){ 33 | throw new IllegalArgumentException("password should not be null!"); 34 | } 35 | 36 | this.password = password; 37 | this.secretKeySpec = getSecretKey(password); 38 | } 39 | 40 | @Override 41 | public String encrypt(String value){ 42 | 43 | try{ 44 | Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM); 45 | cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec); 46 | 47 | byte[] content = value.getBytes("UTF-8"); 48 | byte[] encryptData = cipher.doFinal(content); 49 | 50 | return Hex.bytesToHexString(encryptData); 51 | }catch (Exception e){ 52 | log.error("AES加密时出现问题,密钥为:{}",sensitiveTypeHandler.handle(password)); 53 | throw new IllegalStateException("AES加密时出现问题"+e.getMessage(),e); 54 | } 55 | } 56 | 57 | @Override 58 | public String decrypt(String value){ 59 | if (StringUtils.isEmpty(value)) { 60 | return ""; 61 | } 62 | try { 63 | byte[] encryptData = Hex.hexStringToBytes(value); 64 | Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM); 65 | cipher.init(Cipher.DECRYPT_MODE, secretKeySpec); 66 | byte[] content = cipher.doFinal(encryptData); 67 | return new String(content, "UTF-8"); 68 | }catch (Exception e){ 69 | log.error("AES解密时出现问题,密钥为:{},密文为:{}",sensitiveTypeHandler.handle(password),value); 70 | throw new IllegalStateException("AES解密时出现问题"+e.getMessage(),e); 71 | } 72 | 73 | } 74 | 75 | 76 | private static SecretKeySpec getSecretKey(final String password) throws NoSuchAlgorithmException{ 77 | //返回生成指定算法密钥生成器的 KeyGenerator 对象 78 | KeyGenerator kg = KeyGenerator.getInstance(KEY_ALGORITHM); 79 | //AES 要求密钥长度为 128 80 | SecureRandom random = SecureRandom.getInstance("SHA1PRNG"); 81 | random.setSeed(password.getBytes()); 82 | kg.init(128, random); 83 | //生成一个密钥 84 | SecretKey secretKey = kg.generateKey(); 85 | // 转换为AES专用密钥 86 | return new SecretKeySpec(secretKey.getEncoded(), KEY_ALGORITHM); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/test/java/com/chenhaiyang/plugin/mybatis/sensitive/test/all/mapper/UserMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | insert into 19 | user(user_name,user_name_sensitive,idcard,idcard_sensitive,json_str,age,email) 20 | values(#{userName},#{userNameSensitive},#{idcard},#{idcardSensitive},#{jsonStr},#{age},#{email}) 21 | 22 | 23 | 24 | insert into 25 | user(user_name,idcard) 26 | values(#{userName},#{idcard}) 27 | 28 | 29 | 41 | 42 | 43 | 55 | 56 | 69 | 82 | 83 | update user set 84 | user_name=#{userName}, 85 | user_name_sensitive=#{userNameSensitive}, 86 | idcard=#{idcard}, 87 | idcard_sensitive=#{idcardSensitive}, 88 | json_str=#{jsonStr}, 89 | age=#{age}, 90 | email=#{email} 91 | where 92 | id=#{id} and user_name=#{userName} 93 | 94 | 95 | -------------------------------------------------------------------------------- /src/main/java/com/chenhaiyang/plugin/mybatis/sensitive/interceptor/DecryptReadInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.chenhaiyang.plugin.mybatis.sensitive.interceptor; 2 | 3 | import com.chenhaiyang.plugin.mybatis.sensitive.Encrypt; 4 | import com.chenhaiyang.plugin.mybatis.sensitive.annotation.EncryptField; 5 | import com.chenhaiyang.plugin.mybatis.sensitive.annotation.SensitiveBinded; 6 | import com.chenhaiyang.plugin.mybatis.sensitive.annotation.SensitiveEncryptEnabled; 7 | import com.chenhaiyang.plugin.mybatis.sensitive.type.SensitiveType; 8 | import com.chenhaiyang.plugin.mybatis.sensitive.type.SensitiveTypeRegisty; 9 | import com.chenhaiyang.plugin.mybatis.sensitive.utils.PluginUtils; 10 | import lombok.extern.slf4j.Slf4j; 11 | import org.apache.ibatis.executor.resultset.ResultSetHandler; 12 | import org.apache.ibatis.mapping.MappedStatement; 13 | import org.apache.ibatis.mapping.ResultMap; 14 | import org.apache.ibatis.plugin.*; 15 | import org.apache.ibatis.reflection.MetaObject; 16 | import org.apache.ibatis.reflection.SystemMetaObject; 17 | 18 | import java.lang.reflect.Field; 19 | import java.util.*; 20 | 21 | 22 | /** 23 | * 对响应结果进行拦截处理,对需要解密的字段进行解密 24 | * SQL样例: 25 | * 1. UPDATE tbl SET x=?, y = 26 | * @author ; 27 | */ 28 | @Intercepts({ 29 | @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {java.sql.Statement.class}) 30 | }) 31 | @Slf4j 32 | public class DecryptReadInterceptor implements Interceptor { 33 | 34 | private static final String MAPPED_STATEMENT="mappedStatement"; 35 | 36 | private Encrypt encrypt; 37 | public DecryptReadInterceptor(Encrypt encrypt) { 38 | Objects.requireNonNull(encrypt,"encrypt should not be null!"); 39 | this.encrypt = encrypt; 40 | } 41 | 42 | @SuppressWarnings("unchecked") 43 | @Override 44 | public Object intercept(Invocation invocation) throws Throwable { 45 | final List results = (List)invocation.proceed(); 46 | 47 | if (results.isEmpty()) { 48 | return results; 49 | } 50 | 51 | final ResultSetHandler statementHandler = PluginUtils.realTarget(invocation.getTarget()); 52 | final MetaObject metaObject = SystemMetaObject.forObject(statementHandler); 53 | final MappedStatement mappedStatement = (MappedStatement)metaObject.getValue(MAPPED_STATEMENT); 54 | final ResultMap resultMap = mappedStatement.getResultMaps().isEmpty() ? null : mappedStatement.getResultMaps().get(0); 55 | 56 | Object result0 = results.get(0); 57 | SensitiveEncryptEnabled sensitiveEncryptEnabled = result0.getClass().getAnnotation(SensitiveEncryptEnabled.class); 58 | if(sensitiveEncryptEnabled == null || !sensitiveEncryptEnabled.value()){ 59 | return results; 60 | } 61 | 62 | final Map sensitiveFieldMap = getSensitiveByResultMap(resultMap); 63 | final Map sensitiveBindedMap = getSensitiveBindedByResultMap(resultMap); 64 | 65 | if (sensitiveBindedMap.isEmpty() && sensitiveFieldMap.isEmpty()) { 66 | return results; 67 | } 68 | 69 | for (Object obj: results) { 70 | final MetaObject objMetaObject = mappedStatement.getConfiguration().newMetaObject(obj); 71 | for (Map.Entry entry : sensitiveFieldMap.entrySet()) { 72 | String property = entry.getKey(); 73 | String value = (String) objMetaObject.getValue(property); 74 | if (value != null) { 75 | String decryptValue = encrypt.decrypt(value); 76 | objMetaObject.setValue(property, decryptValue); 77 | } 78 | } 79 | for (Map.Entry entry : sensitiveBindedMap.entrySet()) { 80 | 81 | String property = entry.getKey(); 82 | 83 | SensitiveBinded sensitiveBinded = entry.getValue(); 84 | String bindPropety = sensitiveBinded.bindField(); 85 | SensitiveType sensitiveType = sensitiveBinded.value(); 86 | try { 87 | String value = (String) objMetaObject.getValue(bindPropety); 88 | String resultValue = SensitiveTypeRegisty.get(sensitiveType).handle(value); 89 | objMetaObject.setValue(property,resultValue); 90 | }catch (Exception e){ 91 | //ignore it; 92 | } 93 | } 94 | } 95 | 96 | return results; 97 | } 98 | 99 | private Map getSensitiveBindedByResultMap(ResultMap resultMap) { 100 | if (resultMap == null) { 101 | return new HashMap<>(16); 102 | } 103 | Map sensitiveBindedMap = new HashMap<>(16); 104 | Class clazz = resultMap.getType(); 105 | for (Field field: clazz.getDeclaredFields()) { 106 | SensitiveBinded sensitiveBinded = field.getAnnotation(SensitiveBinded.class); 107 | if (sensitiveBinded != null) { 108 | sensitiveBindedMap.put(field.getName(), sensitiveBinded); 109 | } 110 | } 111 | return sensitiveBindedMap; 112 | } 113 | 114 | private Map getSensitiveByResultMap(ResultMap resultMap) { 115 | if (resultMap == null) { 116 | return new HashMap<>(16); 117 | } 118 | 119 | return getSensitiveByType(resultMap.getType()); 120 | } 121 | 122 | private Map getSensitiveByType(Class clazz) { 123 | Map sensitiveFieldMap = new HashMap<>(16); 124 | 125 | for (Field field: clazz.getDeclaredFields()) { 126 | EncryptField sensitiveField = field.getAnnotation(EncryptField.class); 127 | if (sensitiveField != null) { 128 | sensitiveFieldMap.put(field.getName(), sensitiveField); 129 | } 130 | } 131 | return sensitiveFieldMap; 132 | } 133 | 134 | @Override 135 | public Object plugin(Object o) { 136 | return Plugin.wrap(o, this); 137 | } 138 | 139 | @Override 140 | public void setProperties(Properties properties) { 141 | // ignore 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 敏感数据加解密以及数据脱敏mybatis插件 2 | 3 | ## 介绍 4 | 5 | 本工具是基于mybatis的插件机制编写的一套敏感数据加解密以及数据脱敏工具。
6 | 7 | 在使用时通过注解指定一个字段是需要加密的字段,该插件会在存储时自动加密存储。 8 | 而查询时会自动解密出明文在程序内部使用。
9 | 在使用时也可以通过注解指定一个字段是需要脱敏的字段,该插件会在入库时将字段脱敏存储。 10 | 内置了一些常用数据的脱敏处理方式。
11 | 12 | 13 | ## 设计目标 14 | 15 | #### 对应用和使用者透明,业务逻辑无感知,通过配置集成,改动代码量小。 16 | #### 加密算法可扩展。 17 | 18 | ## 实现原理 19 | 1,拦截mybatis的StatementHandler 对读写请求进行脱敏和字段的加密。
20 | 2,拦截mybatis的ResultSetHandler,对读请求的响应进行加密字段的解密赋值。
21 | ## 使用方式 22 | 23 | 0,导入依赖 24 | ```xml 25 | 26 | 27 | com.github.chenhaiyangs 28 | mybatis-encrypt-plugin 29 | 1.0.0 30 | 31 | ``` 32 | 1,编写加解密实现类以及配置mybatis的插件,下面在springboot场景下的一个配置案例。 33 | ```java 34 | /** 35 | * 插件配置 36 | */ 37 | @Configuration 38 | public class EncryptPluginConfig { 39 | 40 | //加密方式 41 | @Bean 42 | Encrypt encryptor() throws Exception{ 43 | return new AesSupport("1870577f29b17d6787782f35998c4a79"); 44 | } 45 | 46 | //配置插件 47 | @Bean 48 | ConfigurationCustomizer configurationCustomizer() throws Exception{ 49 | DecryptReadInterceptor decryptReadInterceptor = new DecryptReadInterceptor(encryptor()); 50 | SensitiveAndEncryptWriteInterceptor sensitiveAndEncryptWriteInterceptor = new SensitiveAndEncryptWriteInterceptor(encryptor()); 51 | 52 | return (configuration) -> { 53 | configuration.addInterceptor(decryptReadInterceptor); 54 | configuration.addInterceptor(sensitiveAndEncryptWriteInterceptor); 55 | }; 56 | } 57 | } 58 | ``` 59 | 2,在vo类上添加功能注解使得插件生效: 60 | ```java 61 | @SensitiveEncryptEnabled 62 | @Data 63 | public class UserDTO { 64 | 65 | private Integer id; 66 | /** 67 | * 用户名 68 | */ 69 | @EncryptField 70 | private String userName; 71 | /** 72 | * 脱敏的用户名 73 | */ 74 | @SensitiveField(SensitiveType.CHINESE_NAME) 75 | private String userNameSensitive; 76 | /** 77 | * 值的赋值不从数据库取,而是从userName字段获得。 78 | */ 79 | @SensitiveBinded(bindField = "userName",value = SensitiveType.CHINESE_NAME) 80 | private String userNameOnlyDTO; 81 | /** 82 | * 身份证号 83 | */ 84 | @EncryptField 85 | private String idcard; 86 | /** 87 | * 脱敏的身份证号 88 | */ 89 | @SensitiveField(SensitiveType.ID_CARD) 90 | private String idcardSensitive; 91 | /** 92 | * 一个json串,需要脱敏 93 | * SensitiveJSONField标记json中需要脱敏的字段 94 | */ 95 | @SensitiveJSONField(sensitivelist = { 96 | @SensitiveJSONFieldKey(key = "idcard",type = SensitiveType.ID_CARD), 97 | @SensitiveJSONFieldKey(key = "username",type = SensitiveType.CHINESE_NAME), 98 | }) 99 | private String jsonStr; 100 | 101 | private int age; 102 | 103 | @SensitiveField(SensitiveType.EMAIL) 104 | private String email; 105 | } 106 | ``` 107 | ## 注解详解 108 | #### @SensitiveEncryptEnabled 109 | 110 | 标记在类上,声明此数据库映射的model对象开启数据加密和脱敏功能。 111 | 112 | #### @EncryptField 113 | 114 | 标记在字段上,必须是字符串,声明此字段在和数据库交互前将数据加密。 115 | update,select,insert 都会将指定的字段设置为密文与数据库进行交互。 116 | 在select的结果集里,此字段会自动解密成明文。因此,业务是无感知的。 117 | 118 | #### @SensitiveField(SensitiveType.CHINESE_NAME) 119 | 120 | 标记在字段上,必须是字符串。 121 | 声明此字段在入库或者修改时,会脱敏存储。 122 | SensitiveType是脱敏类型,详见脱敏类型章节。 123 | 124 | 一般考虑如下场景。 125 | 用户的手机号需要在数据库存储为加密的密文,为了查询方便,可能数据库也会有一个脱敏的手机号字段。 126 | 那就可以这样定义两个字段: 127 | 128 | //在数据库加密存储的 129 | @EncryptField 130 | private String phone; 131 | //在数据库脱敏存储的 132 | @SensitiveField(SensitiveType.MOBILE_PHONE) 133 | private String phoneSensitive; 134 | 135 | 而业务代码赋值时,可以赋值两次: 136 | 137 | ...... 138 | user.setPhone("18233586969"); 139 | user.setPhoneSensitive("18233586969"); 140 | ...... 141 | 此时,数据库两个字段,一个会加密,一个会脱敏。 142 | 在查询请求的响应结果集里,phone是明文,phoneSensitive是脱敏的。 143 | #### @SensitiveJSONField 144 | 145 | 标记在json字符串上,声明此json串在入库前会将json中指定的字段脱敏。 146 | 147 | 例如: 148 | @SensitiveJSONField(sensitivelist = { 149 | @SensitiveJSONFieldKey(key = "idcard",type = SensitiveType.ID_CARD), 150 | @SensitiveJSONFieldKey(key = "username",type = SensitiveType.CHINESE_NAME), 151 | }) 152 | private String jsonStr; 153 | 154 | 如果jsonStr原文为 155 | { 156 | "age":18, 157 | "idcard":"130722188284646474", 158 | "username":"吴彦祖", 159 | "city":"北京" 160 | } 161 | 则脱敏后为: 162 | { 163 | "age":18, 164 | "idcard":"130***********6474", 165 | "username":"吴**", 166 | "city":"北京" 167 | 168 | } 169 | 使用场景: 170 | 有时候数据库会存储一些第三方返回的json串,可能会包含敏感信息。 171 | 业务里不需要用到敏感信息的明文,此时可以脱敏存储整个json串。 172 | 173 | #### @SensitiveBinded(bindField = "userName",value = SensitiveType.CHINESE_NAME) 174 | 175 | 此注解适用于如下场景: 176 | 例如,数据库只存了username字段的加密信息,没有冗余脱敏展示的字段。 177 | 我的响应类里希望将数据库的加密的某个字段映射到响应的两个属性上(一个解密的属性,一个脱敏的属性)就可以使用该注解。 178 | 例如,dto里有如下字段: 179 | @EncryptField 180 | private String username 181 | 182 | @SensitiveBinded(bindField = "userName",value = SensitiveType.CHINESE_NAME) 183 | private String userNameOnlyDTO; 184 | 185 | 则当查询出结果时,userNameOnlyDTO会赋值为username解密后再脱敏的值。 186 | 相当于数据库的一个字段的值以不同的形式映射到了对象的两个字段上。 187 | ## 脱敏类型 188 | ```java 189 | public enum SensitiveType { 190 | /** 191 | * 不脱敏 192 | */ 193 | NONE, 194 | /** 195 | * 默认脱敏方式 196 | */ 197 | DEFAUL, 198 | /** 199 | * 中文名 200 | */ 201 | CHINESE_NAME, 202 | /** 203 | * 身份证号 204 | */ 205 | ID_CARD, 206 | /** 207 | * 座机号 208 | */ 209 | FIXED_PHONE, 210 | /** 211 | * 手机号 212 | */ 213 | MOBILE_PHONE, 214 | /** 215 | * 地址 216 | */ 217 | ADDRESS, 218 | /** 219 | * 电子邮件 220 | */ 221 | EMAIL, 222 | /** 223 | * 银行卡 224 | */ 225 | BANK_CARD, 226 | /** 227 | * 公司开户银行联号 228 | */ 229 | CNAPS_CODE, 230 | /** 231 | * 支付签约协议号 232 | */ 233 | PAY_SIGN_NO 234 | } 235 | ``` 236 | ## 注意事项 237 | 238 | #### 测试用例 239 | 在test包下有完整的测试用例。 240 | #### 使用领域对象化的参数和响应 241 | 必须使用javabean类入参声明方式才能使得本插件生效。例如: 242 | ```java 243 | int insert(UserDTO userDTO); 244 | ``` 245 | 使用如下的方式操作mybatis,则本插件无效。 246 | ```java 247 | int insert(Map map); 248 | int insert(String username,String idcard); 249 | ``` 250 | #### sql应该是预编译类型的 251 | 252 | sql语句应该使用如#{userName}这种预编译的方式组织变量,不能使用'${userName}'这种方式。
253 | -------------------------------------------------------------------------------- /src/main/java/com/chenhaiyang/plugin/mybatis/sensitive/interceptor/SensitiveAndEncryptWriteInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.chenhaiyang.plugin.mybatis.sensitive.interceptor; 2 | 3 | import com.chenhaiyang.plugin.mybatis.sensitive.annotation.*; 4 | import com.chenhaiyang.plugin.mybatis.sensitive.type.SensitiveType; 5 | import com.chenhaiyang.plugin.mybatis.sensitive.type.SensitiveTypeRegisty; 6 | import com.chenhaiyang.plugin.mybatis.sensitive.utils.JsonUtils; 7 | import com.chenhaiyang.plugin.mybatis.sensitive.utils.PluginUtils; 8 | import com.chenhaiyang.plugin.mybatis.sensitive.Encrypt; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.apache.ibatis.executor.statement.StatementHandler; 11 | import org.apache.ibatis.mapping.BoundSql; 12 | import org.apache.ibatis.mapping.MappedStatement; 13 | import org.apache.ibatis.mapping.SqlCommandType; 14 | import org.apache.ibatis.plugin.*; 15 | import org.apache.ibatis.reflection.MetaObject; 16 | import org.apache.ibatis.reflection.SystemMetaObject; 17 | import org.apache.ibatis.session.Configuration; 18 | 19 | import java.lang.reflect.Field; 20 | import java.sql.Connection; 21 | import java.util.*; 22 | 23 | /** 24 | * 拦截写请求的插件。插件生效仅支持预编译的sql 25 | * @author ; 26 | */ 27 | @Intercepts({ 28 | @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}), 29 | }) 30 | @Slf4j 31 | public class SensitiveAndEncryptWriteInterceptor implements Interceptor { 32 | 33 | private static final String MAPPEDSTATEMENT="delegate.mappedStatement"; 34 | private static final String BOUND_SQL="delegate.boundSql"; 35 | 36 | private Encrypt encrypt; 37 | 38 | public SensitiveAndEncryptWriteInterceptor(Encrypt encrypt) { 39 | Objects.requireNonNull(encrypt,"encrypt should not be null!"); 40 | this.encrypt = encrypt; 41 | } 42 | 43 | @Override 44 | public Object intercept(Invocation invocation) throws Throwable { 45 | 46 | StatementHandler statementHandler = PluginUtils.realTarget(invocation.getTarget()); 47 | MetaObject metaObject = SystemMetaObject.forObject(statementHandler); 48 | MappedStatement mappedStatement = (MappedStatement)metaObject.getValue(MAPPEDSTATEMENT); 49 | SqlCommandType commandType = mappedStatement.getSqlCommandType(); 50 | 51 | BoundSql boundSql = (BoundSql)metaObject.getValue(BOUND_SQL); 52 | Object params = boundSql.getParameterObject(); 53 | if(params instanceof Map){ 54 | return invocation.proceed(); 55 | } 56 | SensitiveEncryptEnabled sensitiveEncryptEnabled = params != null ? params.getClass().getAnnotation(SensitiveEncryptEnabled.class) : null; 57 | if(sensitiveEncryptEnabled != null && sensitiveEncryptEnabled.value()){ 58 | handleParameters(mappedStatement.getConfiguration(), boundSql,params,commandType); 59 | } 60 | return invocation.proceed(); 61 | } 62 | 63 | private void handleParameters(Configuration configuration, BoundSql boundSql,Object param,SqlCommandType commandType) throws Exception { 64 | 65 | Map newValues = new HashMap<>(16); 66 | MetaObject metaObject = configuration.newMetaObject(param); 67 | 68 | for (Field field : param.getClass().getDeclaredFields()) { 69 | 70 | Object value = metaObject.getValue(field.getName()); 71 | Object newValue = value; 72 | if(value instanceof CharSequence){ 73 | newValue = handleEncryptField(field,newValue); 74 | if(isWriteCommand(commandType) && !SensitiveTypeRegisty.alreadyBeSentisived(newValue)) { 75 | newValue = handleSensitiveField(field, newValue); 76 | newValue = handleSensitiveJSONField(field, newValue); 77 | } 78 | } 79 | if(value!=null && newValue!=null && !value.equals(newValue)) { 80 | newValues.put(field.getName(), newValue); 81 | } 82 | } 83 | for (Map.Entry entry: newValues.entrySet()) { 84 | boundSql.setAdditionalParameter(entry.getKey(),entry.getValue()); 85 | } 86 | 87 | } 88 | 89 | private boolean isWriteCommand(SqlCommandType commandType) { 90 | return SqlCommandType.UPDATE.equals(commandType) || SqlCommandType.INSERT.equals(commandType); 91 | } 92 | 93 | private Object handleEncryptField(Field field, Object value) { 94 | 95 | EncryptField encryptField = field.getAnnotation(EncryptField.class); 96 | Object newValue = value; 97 | if (encryptField != null && value != null) { 98 | newValue = encrypt.encrypt(value.toString()); 99 | } 100 | return newValue; 101 | } 102 | 103 | private Object handleSensitiveField(Field field, Object value) { 104 | SensitiveField sensitiveField = field.getAnnotation(SensitiveField.class); 105 | Object newValue = value; 106 | if (sensitiveField != null && value != null) { 107 | newValue = SensitiveTypeRegisty.get(sensitiveField.value()).handle(value); 108 | } 109 | return newValue; 110 | } 111 | private Object handleSensitiveJSONField(Field field, Object value) { 112 | SensitiveJSONField sensitiveJSONField = field.getAnnotation(SensitiveJSONField.class); 113 | Object newValue = value; 114 | if (sensitiveJSONField != null && value != null) { 115 | newValue = processJsonField(newValue,sensitiveJSONField); 116 | } 117 | return newValue; 118 | } 119 | 120 | /** 121 | * 在json中进行脱敏 122 | * @param newValue new 123 | * @param sensitiveJSONField 脱敏的字段 124 | * @return json 125 | */ 126 | private Object processJsonField(Object newValue,SensitiveJSONField sensitiveJSONField) { 127 | 128 | try{ 129 | Map map = JsonUtils.parseToObjectMap(newValue.toString()); 130 | SensitiveJSONFieldKey[] keys =sensitiveJSONField.sensitivelist(); 131 | for(SensitiveJSONFieldKey jsonFieldKey :keys){ 132 | String key = jsonFieldKey.key(); 133 | SensitiveType sensitiveType = jsonFieldKey.type(); 134 | Object oldData = map.get(key); 135 | if(oldData!=null){ 136 | String newData = SensitiveTypeRegisty.get(sensitiveType).handle(oldData); 137 | map.put(key,newData); 138 | } 139 | } 140 | return JsonUtils.parseMaptoJSONString(map); 141 | }catch (Throwable e){ 142 | //失败以后返回默认值 143 | log.error("脱敏json串时失败,cause : {}",e.getMessage(),e); 144 | return newValue; 145 | } 146 | } 147 | 148 | @Override 149 | public Object plugin(Object o) { 150 | return Plugin.wrap(o, this); 151 | } 152 | 153 | @Override 154 | public void setProperties(Properties properties) { 155 | //do nothing 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.github.chenhaiyangs 8 | mybatis-encrypt-plugin 9 | 1.0.0 10 | 11 | mybatis-encrypt-plugin 12 | https://github.com/chenhaiyangs/mybatis-encrypt-plugin.git 13 | mybatis数据加解密和数据脱敏的插件 14 | 15 | 16 | 17 | ossrh 18 | https://oss.sonatype.org/content/repositories/snapshots 19 | 20 | 21 | ossrh 22 | Maven Central Staging Repository 23 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 24 | 25 | 26 | 27 | 28 | 29 | The Apache License, Version 2.0 30 | http://www.apache.org/licenses/LICENSE-2.0.txt 31 | 32 | 33 | 34 | 35 | chenhaiyang 36 | chenhy_hebei@126.com 37 | 38 | developer 39 | 40 | +8 41 | 42 | 43 | 44 | scm:git:https://github.com/chenhaiyangs/mybatis-encrypt-plugin 45 | scm:git:https://github.com/chenhaiyangs/mybatis-encrypt-plugin 46 | https://github.com/chenhaiyangs/mybatis-encrypt-plugin 47 | v${project.version} 48 | 49 | 50 | 51 | 52 | 53 | junit 54 | junit 55 | 4.10 56 | test 57 | 58 | 59 | 60 | org.springframework.boot 61 | spring-boot-starter-web 62 | 2.0.0.RELEASE 63 | test 64 | 65 | 66 | org.mybatis.spring.boot 67 | mybatis-spring-boot-starter 68 | 2.0.0 69 | test 70 | 71 | 72 | mysql 73 | mysql-connector-java 74 | 5.1.18 75 | test 76 | 77 | 78 | 79 | org.mybatis 80 | mybatis 81 | 3.4.5 82 | provided 83 | 84 | 85 | 86 | org.slf4j 87 | slf4j-api 88 | 1.7.24 89 | 90 | 91 | 92 | org.projectlombok 93 | lombok 94 | 1.16.10 95 | 96 | 97 | 98 | org.bouncycastle 99 | bcprov-jdk15on 100 | 1.58 101 | 102 | 103 | com.alibaba 104 | fastjson 105 | 1.2.31 106 | 107 | 108 | org.apache.commons 109 | commons-lang3 110 | 3.8 111 | 112 | 113 | 114 | 115 | 116 | 117 | src/test/java 118 | 119 | **/*.xml 120 | 121 | 122 | false 123 | 124 | 125 | 126 | 127 | org.apache.maven.plugins 128 | maven-compiler-plugin 129 | 3.1 130 | 131 | 1.8 132 | 1.8 133 | 134 | 135 | 136 | org.apache.maven.plugins 137 | maven-source-plugin 138 | 2.2.1 139 | 140 | 141 | attach-sources 142 | 143 | jar-no-fork 144 | 145 | 146 | 147 | 148 | 149 | org.apache.maven.plugins 150 | maven-javadoc-plugin 151 | 2.9.1 152 | 153 | 154 | attach-javadocs 155 | 156 | jar 157 | 158 | 159 | 160 | 161 | 162 | org.apache.maven.plugins 163 | maven-gpg-plugin 164 | 1.6 165 | 166 | 167 | sign-artifacts 168 | verify 169 | 170 | sign 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | release 180 | 181 | 182 | --------------------------------------------------------------------------------