├── .gitignore ├── README.md ├── encrypt-core ├── pom.xml └── src │ ├── main │ ├── java │ │ └── org │ │ │ └── demo │ │ │ ├── App.java │ │ │ ├── MybatisDemoApplication.java │ │ │ ├── mapper │ │ │ ├── BankCardMapper.java │ │ │ └── BankCardXmlMapper.java │ │ │ └── pojo │ │ │ └── BankCardDO.java │ └── resources │ │ ├── application.properties │ │ ├── data.sql │ │ ├── mapper │ │ └── BankCardXmlMapper.xml │ │ └── schema.sql │ └── test │ └── java │ └── org │ └── demo │ └── MybatisDemoApplicationTests.java ├── encrypt-interface ├── pom.xml └── src │ └── main │ └── java │ └── org │ └── demo │ ├── App.java │ ├── alias │ └── CryptType.java │ └── type │ └── CryptTypeHandler.java └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Example user template template 3 | ### Example user template 4 | 5 | # IntelliJ project files 6 | .idea 7 | *.iml 8 | out 9 | gen 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 通用的数据加解密方案 2 | 3 | 通过自定义的 `typeHandler` 实现数据加密入库,查询结果解密返回。 4 | 5 | ## 使用方式 6 | 7 | 1.引入 `encrypt-interface` 依赖 8 | 9 | 2.修改配置,以下三种方式可以根据项目实际情况选择。 10 | 11 | **单独使用 mybatis** 12 | 13 | 这种场景需要在 **mybatis-config.xml** 配置,mybatis 启动时将会加载该配置文件。 14 | 15 | ```xml 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | ``` 26 | 27 | **使用 Spring 配置 Mybatis Bean** 28 | 29 | 配合 Spring 使用时需要将 `typeHandler` 注入 `SqlSessionFactoryBean` ,配置方式如下: 30 | 31 | ```xml 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | ``` 42 | 43 | **SpringBoot** 44 | 45 | SpringBoot 方式就最简单了,只要引入 `mybatis-starter`,配置文件加入如下配置即可: 46 | 47 | ```properties 48 | ## mybatis 配置 49 | # 类型转换器包路径 50 | mybatis.type-handlers-package=com.xx.xx.x 51 | mybatis.type-aliases-package=com.xx.xx 52 | ``` 53 | 54 | 3.改造相应的sqlmap 55 | 56 | **xml 方式:** 57 | 58 | insert 语句示例: 59 | 60 | ```xml 61 | 62 | INSERT INTO bank_card (card_no, phone,name,id_no) 63 | VALUES 64 | (#{card_no,javaType=crypt}, 65 | #{phone,typeHandler=org.demo.type.CryptTypeHandler}, 66 | #{name,javaType=crypt}, 67 | #{id_no,javaType=crypt}) 68 | 69 | ``` 70 | 71 | 查询语句示例: 72 | 73 | ```xml 74 | 75 | 76 | 77 | 78 | 79 | 80 | 83 | ``` 84 | 85 | 数据库明文、密文共存的情况,查询解密示例如下: 86 | 87 | ```xml 88 | 89 | 92 | ``` 93 | 94 | **muybatis 注解方式示例** 95 | 96 | insert 语句: 97 | 98 | ```java 99 | @Options(useGeneratedKeys = true, keyProperty = "id") 100 | @Insert("INSERT INTO bank_card (card_no, phone,name,id_no) " + 101 | "VALUES (#{card.card_no,javaType=crypt}, #{card.phone,typeHandler=org.demo.type.CryptTypeHandler},#{card.name,javaType=crypt},#{card.id_no,javaType=crypt})") 102 | void insertBankCard(@Param("card") BankCardDO cardDO); 103 | ``` 104 | 105 | 查询语句示例: 106 | 107 | ```java 108 | @Results(id = "bankCard", value = { 109 | // 通过指定 typeHandler 进行解密 110 | @Result(column = "card_no", property = "card_no", typeHandler = CryptTypeHandler.class), 111 | // 通过指定 javaType 进行解密 112 | @Result(column = "phone", property = "phone", typeHandler = CryptTypeHandler.class), 113 | @Result(column = "name", property = "name", typeHandler = CryptTypeHandler.class), 114 | @Result(column = "id_no", property = "id_no", typeHandler = CryptTypeHandler.class), 115 | }) 116 | @Select("select * from bank_card where id=#{id}") 117 | BankCardDO queryById(int id); 118 | ``` 119 | ## 相关测试 120 | 121 | 本工程提供一个测试 demo,使用了内嵌的 H2 数据库,无需使用其他数据库。 122 | 123 | 测试例子比较简单,只要运行 `MybatisDemoApplicationTests` 即可。 -------------------------------------------------------------------------------- /encrypt-core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | mybatis-encrypt 7 | org.demo 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | encrypt-core 13 | 14 | encrypt-core 15 | 16 | 17 | 18 | org.springframework.boot 19 | spring-boot-starter-web 20 | 21 | 22 | org.mybatis.spring.boot 23 | mybatis-spring-boot-starter 24 | 2.1.2 25 | 26 | 27 | 28 | com.h2database 29 | h2 30 | runtime 31 | 32 | 33 | org.projectlombok 34 | lombok 35 | true 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-starter-test 40 | test 41 | 42 | 43 | org.junit.vintage 44 | junit-vintage-engine 45 | 46 | 47 | 48 | 49 | commons-codec 50 | commons-codec 51 | 1.14 52 | 53 | 54 | org.demo 55 | encrypt-interface 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | org.springframework.boot 66 | spring-boot-maven-plugin 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /encrypt-core/src/main/java/org/demo/App.java: -------------------------------------------------------------------------------- 1 | package org.demo; 2 | 3 | /** 4 | * Hello world! 5 | */ 6 | public class App { 7 | public static void main(String[] args) { 8 | // BankCardDO bankCardDO = new BankCardDO(); 9 | // // 将数据加密,放置到新的加密字段 10 | // bankCardDO.setCard_no_encrypt(encrypt(bankCardDO.getCard_no())); 11 | // .... 12 | // /*** 13 | // * mybatis SQL 需要如下改造: 14 | // * select * from bankCard where card_no in(#{card_no},#{card_no_encrypt}) 15 | // */ 16 | // BankCardDO result = bankCardDao.query(bankCardDO); 17 | // /** 18 | // * 判断查询返回的数据是否是密文,判断规则不限于: 19 | // * 判断是否包含中文 20 | // * 判断长度 21 | // * 等等.... 22 | // * 23 | // */ 24 | // if(isEncrypt(bankCardDO.getCard_no())){ 25 | // // 将数据解密替换返回值 26 | // result.setCard_no(decrypt(bankCardDO.getCard_no())); 27 | // } 28 | 29 | 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /encrypt-core/src/main/java/org/demo/MybatisDemoApplication.java: -------------------------------------------------------------------------------- 1 | package org.demo; 2 | 3 | import org.mybatis.spring.annotation.MapperScan; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | @MapperScan(basePackages = "org.demo.mapper") 8 | @SpringBootApplication 9 | public class MybatisDemoApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(MybatisDemoApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /encrypt-core/src/main/java/org/demo/mapper/BankCardMapper.java: -------------------------------------------------------------------------------- 1 | package org.demo.mapper; 2 | 3 | 4 | import org.apache.ibatis.annotations.*; 5 | import org.demo.pojo.BankCardDO; 6 | import org.demo.type.CryptTypeHandler; 7 | 8 | /** 9 | * @author andyXu xu9529@gmail.com 10 | * @date 2020/3/30 11 | */ 12 | @Mapper 13 | public interface BankCardMapper { 14 | 15 | @Results(id = "bankCard", value = { 16 | // 通过指定 typeHandler 进行解密 17 | @Result(column = "card_no", property = "card_no", typeHandler = CryptTypeHandler.class), 18 | // 通过指定 javaType 进行解密 19 | @Result(column = "phone", property = "phone", typeHandler = CryptTypeHandler.class), 20 | @Result(column = "name", property = "name", typeHandler = CryptTypeHandler.class), 21 | @Result(column = "id_no", property = "id_no", typeHandler = CryptTypeHandler.class), 22 | }) 23 | @Select("select * from bank_card where id=#{id}") 24 | BankCardDO queryById(int id); 25 | 26 | // 获取自增 id 27 | @Options(useGeneratedKeys = true, keyProperty = "id") 28 | @Insert("INSERT INTO bank_card (card_no, phone,name,id_no) " + 29 | "VALUES (#{card.card_no,javaType=crypt}, #{card.phone,typeHandler=org.demo.type.CryptTypeHandler},#{card.name,javaType=crypt},#{card.id_no,javaType=crypt})") 30 | void insertBankCard(@Param("card") BankCardDO cardDO); 31 | } 32 | -------------------------------------------------------------------------------- /encrypt-core/src/main/java/org/demo/mapper/BankCardXmlMapper.java: -------------------------------------------------------------------------------- 1 | package org.demo.mapper; 2 | 3 | import org.demo.pojo.BankCardDO; 4 | 5 | /** 6 | * @author andyXu xu9529@gmail.com 7 | * @date 2020/4/1 8 | */ 9 | public interface BankCardXmlMapper { 10 | 11 | BankCardDO queryById(int id); 12 | 13 | BankCardDO queryByPhone(String phone); 14 | 15 | void insertBankCard(BankCardDO cardDO); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /encrypt-core/src/main/java/org/demo/pojo/BankCardDO.java: -------------------------------------------------------------------------------- 1 | package org.demo.pojo; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.util.Date; 9 | 10 | /** 11 | * @author andyXu xu9529@gmail.com 12 | * @date 2020/3/30 13 | */ 14 | @AllArgsConstructor 15 | @NoArgsConstructor 16 | @Builder() 17 | @Data 18 | public class BankCardDO { 19 | 20 | 21 | private int id; 22 | 23 | private Date gmt_create; 24 | 25 | private Date gmt_update; 26 | 27 | private String card_no; 28 | 29 | private String phone; 30 | 31 | private String name; 32 | 33 | private String id_no; 34 | } 35 | -------------------------------------------------------------------------------- /encrypt-core/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | management.endpoints.web.exposure.include=* 2 | spring.output.ansi.enabled=ALWAYS 3 | spring.batch.initialize-schema=embedded 4 | #spring.datasource.schema=classpath:schema.sql 5 | spring.datasource.url=jdbc:h2:mem:testdb 6 | spring.datasource.username=sa 7 | spring.datasource.password= 8 | spring.datasource.hikari.maximumPoolSize=5 9 | spring.datasource.hikari.minimumIdle=5 10 | spring.datasource.hikari.idleTimeout=600000 11 | spring.datasource.hikari.connectionTimeout=30000 12 | spring.datasource.hikari.maxLifetime=1800000 13 | spring.h2.console.settings.web-allow-others=true 14 | spring.h2.console.path=/h2 15 | spring.h2.console.enabled=true 16 | spring.h2.console.settings.trace=true 17 | #debug=true 18 | ## mybatis 配置 19 | 20 | mybatis.type-handlers-package=org.demo.type 21 | mybatis.type-aliases-package=org.demo.alias 22 | 23 | mybatis.mapper-locations=classpath:mapper/*.xml 24 | 25 | logging.level.org.demo.mapper=debug -------------------------------------------------------------------------------- /encrypt-core/src/main/resources/data.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO FOO (ID, BAR) VALUES (1, 'aaa'); 2 | INSERT INTO FOO (ID, BAR) VALUES (2, 'bbb'); 3 | 4 | 5 | INSERT INTO bank_card (card_no, phone,name,id_no) VALUES ('123456789', '12345667','测试','12312312'); 6 | INSERT INTO bank_card (card_no, phone,name,id_no) VALUES ('987654321', '764543332','我是测试卡','13856784351'); -------------------------------------------------------------------------------- /encrypt-core/src/main/resources/mapper/BankCardXmlMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | INSERT INTO bank_card (card_no, phone,name,id_no) 16 | VALUES 17 | (#{card_no,javaType=crypt}, 18 | #{phone,typeHandler=org.demo.type.CryptTypeHandler}, 19 | #{name,javaType=crypt}, 20 | #{id_no,javaType=crypt}) 21 | 22 | 23 | 24 | 25 | 28 | 29 | 32 | -------------------------------------------------------------------------------- /encrypt-core/src/main/resources/schema.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS bank_card; 2 | 3 | CREATE TABLE bank_card ( 4 | id int primary key auto_increment, 5 | gmt_create timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 6 | gmt_update timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 7 | card_no varchar(256) NOT NULL DEFAULT '' COMMENT '卡号', 8 | phone varchar(256) NOT NULL DEFAULT '' COMMENT '手机号', 9 | name varchar(256) NOT NULL DEFAULT '' COMMENT '姓名', 10 | id_no varchar(256) NOT NULL DEFAULT '' COMMENT '证件号' 11 | ); 12 | 13 | CREATE TABLE FOO (ID INT IDENTITY, BAR VARCHAR(64)); -------------------------------------------------------------------------------- /encrypt-core/src/test/java/org/demo/MybatisDemoApplicationTests.java: -------------------------------------------------------------------------------- 1 | package org.demo; 2 | 3 | 4 | import org.demo.mapper.BankCardMapper; 5 | import org.demo.mapper.BankCardXmlMapper; 6 | import org.demo.pojo.BankCardDO; 7 | import org.junit.jupiter.api.Assertions; 8 | import org.junit.jupiter.api.Test; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | 12 | @SpringBootTest 13 | class MybatisDemoApplicationTests { 14 | 15 | @Autowired 16 | BankCardMapper bankCardMapper; 17 | 18 | @Autowired 19 | BankCardXmlMapper bankCardXmlMapper; 20 | 21 | @Test 22 | public void test() { 23 | BankCardDO bankCardDO = BankCardDO 24 | .builder() 25 | .card_no("64321231231") 26 | .name("测试卡") 27 | .id_no("1231231231") 28 | .phone("13567891234").build(); 29 | 30 | bankCardMapper.insertBankCard(bankCardDO); 31 | 32 | // 查询数据 33 | BankCardDO result = bankCardMapper.queryById(bankCardDO.getId()); 34 | 35 | Assertions.assertEquals(bankCardDO.getCard_no(), result.getCard_no()); 36 | Assertions.assertEquals(bankCardDO.getName(), result.getName()); 37 | Assertions.assertEquals(bankCardDO.getId_no(), result.getId_no()); 38 | Assertions.assertEquals(bankCardDO.getPhone(), result.getPhone()); 39 | } 40 | 41 | @Test 42 | public void testXml() { 43 | BankCardDO bankCardDO = BankCardDO 44 | .builder() 45 | .card_no("64321231231") 46 | .name("测试卡") 47 | .id_no("1231231231") 48 | .phone("13567891234").build(); 49 | 50 | bankCardXmlMapper.insertBankCard(bankCardDO); 51 | 52 | // 查询数据 53 | BankCardDO result = bankCardXmlMapper.queryById(bankCardDO.getId()); 54 | 55 | Assertions.assertEquals(bankCardDO.getCard_no(), result.getCard_no()); 56 | Assertions.assertEquals(bankCardDO.getName(), result.getName()); 57 | Assertions.assertEquals(bankCardDO.getId_no(), result.getId_no()); 58 | Assertions.assertEquals(bankCardDO.getPhone(), result.getPhone()); 59 | } 60 | 61 | @Test 62 | public void testQueryByPhone(){ 63 | BankCardDO bankCardDO = BankCardDO 64 | .builder() 65 | .card_no("64321231231") 66 | .name("测试卡") 67 | .id_no("1231231231") 68 | .phone("13567891234").build(); 69 | 70 | bankCardXmlMapper.insertBankCard(bankCardDO); 71 | 72 | // 查询数据 73 | BankCardDO result = bankCardXmlMapper.queryByPhone("13567891234"); 74 | 75 | Assertions.assertEquals(bankCardDO.getCard_no(), result.getCard_no()); 76 | Assertions.assertEquals(bankCardDO.getName(), result.getName()); 77 | Assertions.assertEquals(bankCardDO.getId_no(), result.getId_no()); 78 | Assertions.assertEquals(bankCardDO.getPhone(), result.getPhone()); 79 | 80 | 81 | } 82 | 83 | 84 | 85 | 86 | 87 | } 88 | -------------------------------------------------------------------------------- /encrypt-interface/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | mybatis-encrypt 7 | org.demo 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | org.demo 13 | encrypt-interface 14 | 1.0-SNAPSHOT 15 | 16 | encrypt-interface 17 | 18 | 19 | 20 | org.mybatis 21 | mybatis 22 | 23 | 24 | org.projectlombok 25 | lombok 26 | 27 | 28 | org.springframework 29 | spring-core 30 | 31 | 32 | org.slf4j 33 | slf4j-api 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /encrypt-interface/src/main/java/org/demo/App.java: -------------------------------------------------------------------------------- 1 | package org.demo; 2 | 3 | /** 4 | * Hello world! 5 | * 6 | */ 7 | public class App 8 | { 9 | public static void main( String[] args ) 10 | { 11 | System.out.println( "Hello World!" ); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /encrypt-interface/src/main/java/org/demo/alias/CryptType.java: -------------------------------------------------------------------------------- 1 | package org.demo.alias; 2 | 3 | import org.apache.ibatis.type.Alias; 4 | 5 | /** 6 | * @author andyXu xu9529@gmail.com 7 | * @date 2020/3/30 8 | */ 9 | @Alias("crypt") 10 | public class CryptType { 11 | } 12 | -------------------------------------------------------------------------------- /encrypt-interface/src/main/java/org/demo/type/CryptTypeHandler.java: -------------------------------------------------------------------------------- 1 | package org.demo.type; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.apache.ibatis.type.BaseTypeHandler; 5 | import org.apache.ibatis.type.JdbcType; 6 | import org.apache.ibatis.type.MappedTypes; 7 | import org.demo.alias.CryptType; 8 | import org.springframework.util.Base64Utils; 9 | 10 | import java.nio.charset.Charset; 11 | import java.sql.CallableStatement; 12 | import java.sql.PreparedStatement; 13 | import java.sql.ResultSet; 14 | import java.sql.SQLException; 15 | 16 | /* 17 | 这里加解密方法简单使用 base64,可以相应替换成其他方法即可 18 | */ 19 | @MappedTypes(CryptType.class) 20 | @Slf4j 21 | public class CryptTypeHandler extends BaseTypeHandler { 22 | 23 | 24 | 25 | @Override 26 | public void setNonNullParameter(PreparedStatement preparedStatement, int i, String s, JdbcType jdbcType) throws SQLException { 27 | try { 28 | String encryptStr = Base64Utils.encodeToString(s.getBytes(Charset.defaultCharset())); 29 | preparedStatement.setString(i, encryptStr); 30 | } catch (Exception e) { 31 | log.error("加密失败", e); 32 | // 加密失败使用 原始数据 33 | preparedStatement.setString(i, s); 34 | } 35 | 36 | 37 | } 38 | 39 | @Override 40 | public String getNullableResult(ResultSet resultSet, String columnName) throws SQLException { 41 | return decrypt(resultSet.getString(columnName)); 42 | } 43 | 44 | /** 45 | * 解密相关的参数,如果解密失败 46 | * @param param 47 | * @return 48 | */ 49 | private String decrypt(String param) { 50 | // 判断返回结果是否是密文,减少解密失败的概率 51 | if (!isEncrypt(param)) { 52 | return param; 53 | } 54 | try { 55 | byte[] bytes = Base64Utils.decodeFromString(param); 56 | String result = new String(bytes, Charset.defaultCharset()); 57 | return result; 58 | } catch (Exception e) { 59 | log.error("加密失败", e); 60 | return param; 61 | } 62 | } 63 | 64 | /** 65 | * 判断字符串是否是密文 66 | * @param param 67 | * @return 68 | */ 69 | private boolean isEncrypt(String param) { 70 | // 可以使用字符串长度,是否包含中文判断是否是密文 71 | return true; 72 | } 73 | 74 | 75 | @Override 76 | public String getNullableResult(ResultSet resultSet, int columnIndex) throws SQLException { 77 | return decrypt(resultSet.getString(columnIndex)); 78 | } 79 | 80 | @Override 81 | public String getNullableResult(CallableStatement callableStatement, int columnIndex) throws SQLException { 82 | return decrypt(callableStatement.getString(columnIndex)); 83 | } 84 | 85 | 86 | } 87 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.2.6.RELEASE 9 | 10 | 11 | 4.0.0 12 | 13 | org.demo 14 | mybatis-encrypt 15 | 1.0-SNAPSHOT 16 | 17 | encrypt-interface 18 | encrypt-core 19 | 20 | pom 21 | 22 | 23 | 24 | 1.8 25 | 26 | 27 | 28 | 29 | 30 | org.mybatis 31 | mybatis 32 | 3.5.4 33 | 34 | 35 | org.demo 36 | encrypt-interface 37 | 1.0-SNAPSHOT 38 | 39 | 40 | 41 | 42 | 43 | --------------------------------------------------------------------------------