# mybatis-data-security
**Repository Path**: JustryDeng/mybatis-data-security
## Basic Information
- **Project Name**: mybatis-data-security
- **Description**: 基于mybatis插件实现数据库数据自动加解密
- **Primary Language**: Java
- **License**: Apache-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 10
- **Forks**: 10
- **Created**: 2021-07-18
- **Last Updated**: 2024-10-17
## Categories & Tags
**Categories**: Uncategorized
**Tags**: 工具
## README
# mybatis-data-security
#### 介绍
mybatis-data-security提供了一种基于注解实现数据库数据加解密的功能支持。
#### 原理
编写mybatis interceptor实现,对业务代码无侵入,在入库前加密,出库前解密。
#### 前置条件
- 项目采用mybatis作为持久层框架,支持mybatis-plus
#### 功能特性
- 项目启动时分析并校验加解密信息,若使用不当则快速失败
- 支持直接对String类型的变量进行加解密
- 支持对POJO中String类型的字段进行加解密
- 支持自定义加解密实现
#### 注意事项
目前而言,加密后的密文/解密后的明文都会回写给原来的对象;但是有的时候,我们不希望加解密直接操作原来的对象,而是希望加解密时操作原对象的clone对象时,就可以通过实现`com.ideaaedi.mybatis.data.security.support.PojoCloneable`接口来达到这个目的。
注:PojoCloneable的实现类最好不要重写equals与hashcode方法, 否则,可能导致某些场景(如:两个对象的id为null,其余所有的字段值都一样时,同时批量新增这两个对象时)下,部分自增id回填失败,详见源码说明`com.ideaaedi.mybatis.data.security.support.PojoCloneable#handleNullPropertyByClonePojo`
一般的,在下述场景下比较需要这种能力:
-
入库加密时,加密后的密文不要回写到原来的对象中 —— 因为在某些业务场景下,入库后,后面的程序逻辑还需要取对应的明文值而不是密文值。
-
出库解密时,解密后的明文不要回写到原来的对象中 —— 因为在某些业务场景下,出库后,后面的一些程序逻辑需要修改查询出来的对象的属性值,
你这里修改后;当别人使用相同的sql查询时,因为某些缓存机制的存在,就可能导致别人查出来的对象就是你现在在操作着的对象,所以这个对象
的值比起数据库数据来说,是"失真"了的
#### 使用说明
1. 引入相关依赖
mybatis项目
```xml
com.idea-aedi
mybatis-data-security
1.4.3
```
mybatis-plus项目
```xml
com.idea-aedi
mybatis-data-security
1.4.3-mp3.5.1
```
注:适配时,是基于mybatis-plus3.5.1版本适配的,其它版本的mybatis-plus也可以用
注:适配时,只适配了常用功能的加解密
2. 使用注解`com.ideaaedi.mybatis.data.security.annotation.EnableMybatisDataSecurity`,启用mybatis-data-security功能
```java
@SpringBootApplication
@EnableMybatisDataSecurity
public class YourApplication {
// ...
}
```
3. 实现`com.ideaaedi.mybatis.data.security.support.EncryptExecutor`,定制加解密逻辑
```java
import com.ideaaedi.mybatis.data.security.annotation.Encrypt;
import com.ideaaedi.mybatis.data.security.support.EncryptExecutor;
import com.ideaaedi.mybatis.data.security.util.AesUtil;
import org.springframework.stereotype.Component;
/**
* 实现自己的加解密器 - 示例
*
* @author JustryDeng
* @since 2021/2/11 19:08:05
*/
@Component
public class MyEncryptExecutor implements EncryptExecutor {
@Override
public String encryptParameter(String paramName, String paramValue, Encrypt annotation) {
return AesUtil.encrypt(paramValue);
}
@Override
public String encryptField(String fieldName, String fieldValue, Encrypt annotation, Object pojo) {
return AesUtil.encrypt(fieldValue);
}
@Override
public String decryptField(String fieldName, String fieldValue, Encrypt annotation, Object pojo) {
return AesUtil.decrypt(fieldValue);
}
}
```
4. 使用注解`com.ideaaedi.mybatis.data.security.annotation.Encrypt`控制要加解密的字段
**`提示:`** 请着重观察下述示例中的入参出参。
- 示例一:直接对String类型的变量加密
- case 1:
```java
@Insert("INSERT INTO employee (`id`,`name`) VALUES(#{id}, #{name})");
int insertThree(@Param("id")int id, @Param("name") @Encrypt String name);
```
- case 2:
```java
@MapKey("id")
@Select("SELECT `id`,`name`,`age`,`gender`,`motto`,`birthday`,`hobby` FROM `employee` WHERE name = #{name}");
Map selectMapByName(@Encrypt @Param("name") String name);
```
- 示例二:对POJO中String类型的字段加解密
- pojo示例:
```java
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Employee {
/** 用户id */
private Integer id;
/** 名字 */
@Encrypt
private String name;
/** 年龄 */
private Integer age;
/** 性别 */
private String gender;
/** 座右铭 */
@Encrypt
private String motto;
/** 生日 */
private String birthday;
/** 爱好 */
@Encrypt
private String hobby;
}
```
- case 1: 单POJO入参
```java
@Insert("INSERT INTO employee (`id`,`name`, `age`, `gender`, `motto`, `birthday`, `hobby`) VALUES(#{u.id},#{u.name},#{u.age},#{u.gender},#{u.motto},#{u.birthday},#{u.hobby})")
@Options(useGeneratedKeys = true, keyColumn = "id", keyProperty = "u.id")
int insertOne(@Param("u") Employee employee);
```
- case 2: 单POJO入参
```java
@Insert("INSERT INTO employee (`id`, `name`, `age`, `gender`, `motto`, `birthday`, `hobby`) VALUES(#{id},#{name},#{age},#{gender},#{motto},#{birthday},#{hobby})")
@Options(useGeneratedKeys = true, keyColumn = "id", keyProperty = "id")
int insertTwo(Employee employee);
```
- case 3: POJO放在MAP中入参
```java
@Insert("INSERT INTO employee (`id`, `name`, `age`) VALUES(#{u.id},#{u.name},#{u.age})")
int insertEight(Map paramsMap);
```
- case 4: POJO放在MAP中入参
```java
@Insert("INSERT INTO employee (`id`,`name`, `age`) VALUES(#{p.u.id},#{p.u.name},#{p.u.age})")
int insertNine(@Param("p") Map paramsMap);
```
- case 5: POJO放在COLLECTION中入参
```java
@InsertProvider(type = AbcMapperProvider.class, method = "insertTenProvider")
int insertTen(List employeeList);
```
- case 6: POJO放在COLLECTION中入参
```java
@InsertProvider(type = AbcMapperProvider.class, method = "insertElevenProvider")
int insertEleven(@Param("list") List employeeList);
```
- case 7: POJO放在ARRAY中入参
```java
@InsertProvider(type = AbcMapperProvider.class, method = "insertTwelveProvider")
Integer insertTwelve(@Param("myArray") Employee[] employeeArray);
```
- case 8: 单POJO出参
```java
@Select("SELECT `id`,`name`,`age`,`gender`,`motto`,`birthday`,`hobby` FROM `employee` WHERE id = #{id}")
Employee selectOneById(@Param("id") Integer id);
```
- case 9: POJO放在COLLECTION中出参
```java
@Select("SELECT `id`,`name`,`age`,`gender`,`motto`,`birthday`,`hobby` FROM `employee` ")
List selectAll();
```
- case 10: POJO放在ARRAY中出参
```java
@Select("SELECT `id`,`name`,`age`,`gender`,`motto`,`birthday`,`hobby` FROM `employee` ")
Employee[] selectAllAsArray();
```
- case 11: POJO放在MAP中出参
```java
@Select("SELECT `id`,`name`,`age`,`gender`,`motto`,`birthday`,`hobby` FROM `employee` WHERE name = #{name}")
Map selectMapByName(@Encrypt @Param("name") String name);
```
- case 12: ...
```java
// ...
```