├── .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 |
--------------------------------------------------------------------------------