增加 MyBatis Plus 的 EncryptTypeHandler 类型处理器,实现字段的加密解密

This commit is contained in:
YunaiV 2022-05-19 02:17:44 +08:00
parent 3cc7a35ccc
commit 0ae9af0492
8 changed files with 99 additions and 24 deletions

View File

@ -58,6 +58,14 @@
<groupId>com.baomidou</groupId> <groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId> <!-- 多数据源 --> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <!-- 多数据源 -->
</dependency> </dependency>
<!-- 工具类相关 -->
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId> <!-- 加解密 -->
<optional>true</optional>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -0,0 +1,63 @@
package cn.iocoder.yudao.framework.mybatis.core.type;
import cn.hutool.core.lang.Assert;
import cn.hutool.extra.spring.SpringUtil;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.jasypt.encryption.StringEncryptor;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* 字段字段的 TypeHandler 实现类基于 {@link StringEncryptor} 实现
* 可通过 jasypt.encryptor.password 配置项设置密钥
*
* @author 芋道源码
*/
public class EncryptTypeHandler extends BaseTypeHandler<String> {
private static StringEncryptor encryptor;
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, getEncryptor().encrypt(parameter));
}
@Override
public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
String value = rs.getString(columnName);
return getResult(value);
}
@Override
public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
String value = rs.getString(columnIndex);
return getResult(value);
}
@Override
public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
String value = cs.getString(columnIndex);
return getResult(value);
}
private String getResult(String value) {
if (value == null) {
return null;
}
return getEncryptor().decrypt(value);
}
private StringEncryptor getEncryptor() {
if (encryptor != null) {
return encryptor;
}
encryptor = SpringUtil.getBean(StringEncryptor.class);
Assert.notNull(encryptor, "StringEncryptor 不能为空");
return encryptor;
}
}

View File

@ -21,7 +21,7 @@ import java.util.List;
*/ */
@MappedJdbcTypes(JdbcType.VARCHAR) @MappedJdbcTypes(JdbcType.VARCHAR)
@MappedTypes(List.class) @MappedTypes(List.class)
public class StringLiSTTypeHandler implements TypeHandler<List<String>> { public class StringListTypeHandler implements TypeHandler<List<String>> {
private static final String COMMA = ","; private static final String COMMA = ",";

View File

@ -1,7 +1,9 @@
package cn.iocoder.yudao.module.infra.dal.dataobject.db; package cn.iocoder.yudao.module.infra.dal.dataobject.db;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.mybatis.core.type.EncryptTypeHandler;
import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data; import lombok.Data;
@ -10,7 +12,7 @@ import lombok.Data;
* *
* @author 芋道源码 * @author 芋道源码
*/ */
@TableName("infra_data_source_config") @TableName(value = "infra_data_source_config", autoResultMap = true)
@KeySequence("infra_data_source_config_seq") // 用于 OraclePostgreSQLKingbaseDB2H2 数据库的主键自增如果是 MySQL 等数据库可不写 @KeySequence("infra_data_source_config_seq") // 用于 OraclePostgreSQLKingbaseDB2H2 数据库的主键自增如果是 MySQL 等数据库可不写
@Data @Data
public class DataSourceConfigDO extends BaseDO { public class DataSourceConfigDO extends BaseDO {
@ -40,6 +42,7 @@ public class DataSourceConfigDO extends BaseDO {
/** /**
* 密码 * 密码
*/ */
@TableField(typeHandler = EncryptTypeHandler.class)
private String password; private String password;
} }

View File

@ -8,7 +8,6 @@ import cn.iocoder.yudao.module.infra.dal.dataobject.db.DataSourceConfigDO;
import cn.iocoder.yudao.module.infra.dal.mysql.db.DataSourceConfigMapper; import cn.iocoder.yudao.module.infra.dal.mysql.db.DataSourceConfigMapper;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty; import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties; import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
import org.jasypt.encryption.StringEncryptor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
@ -32,9 +31,6 @@ public class DataSourceConfigServiceImpl implements DataSourceConfigService {
@Resource @Resource
private DataSourceConfigMapper dataSourceConfigMapper; private DataSourceConfigMapper dataSourceConfigMapper;
@Resource
private StringEncryptor stringEncryptor;
@Resource @Resource
private DynamicDataSourceProperties dynamicDataSourceProperties; private DynamicDataSourceProperties dynamicDataSourceProperties;
@ -44,7 +40,6 @@ public class DataSourceConfigServiceImpl implements DataSourceConfigService {
checkConnectionOK(dataSourceConfig); checkConnectionOK(dataSourceConfig);
// 插入 // 插入
dataSourceConfig.setPassword(stringEncryptor.encrypt(createReqVO.getPassword()));
dataSourceConfigMapper.insert(dataSourceConfig); dataSourceConfigMapper.insert(dataSourceConfig);
// 返回 // 返回
return dataSourceConfig.getId(); return dataSourceConfig.getId();
@ -58,7 +53,6 @@ public class DataSourceConfigServiceImpl implements DataSourceConfigService {
checkConnectionOK(updateObj); checkConnectionOK(updateObj);
// 更新 // 更新
updateObj.setPassword(stringEncryptor.encrypt(updateObj.getPassword()));
dataSourceConfigMapper.updateById(updateObj); dataSourceConfigMapper.updateById(updateObj);
} }
@ -83,12 +77,7 @@ public class DataSourceConfigServiceImpl implements DataSourceConfigService {
return buildMasterDataSourceConfig(); return buildMasterDataSourceConfig();
} }
// DB 中读取 // DB 中读取
DataSourceConfigDO dataSourceConfig = dataSourceConfigMapper.selectById(id); return dataSourceConfigMapper.selectById(id);
try {
dataSourceConfig.setPassword(stringEncryptor.decrypt(dataSourceConfig.getPassword()));
} catch (Exception ignore) { // 解码失败则不解码
}
return dataSourceConfig;
} }
@Override @Override

View File

@ -1,5 +1,7 @@
package cn.iocoder.yudao.module.infra.service.db; package cn.iocoder.yudao.module.infra.service.db;
import cn.hutool.core.util.ReflectUtil;
import cn.iocoder.yudao.framework.mybatis.core.type.EncryptTypeHandler;
import cn.iocoder.yudao.framework.mybatis.core.util.JdbcUtils; import cn.iocoder.yudao.framework.mybatis.core.util.JdbcUtils;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.module.infra.controller.admin.db.vo.DataSourceConfigCreateReqVO; import cn.iocoder.yudao.module.infra.controller.admin.db.vo.DataSourceConfigCreateReqVO;
@ -8,8 +10,10 @@ import cn.iocoder.yudao.module.infra.dal.dataobject.db.DataSourceConfigDO;
import cn.iocoder.yudao.module.infra.dal.mysql.db.DataSourceConfigMapper; import cn.iocoder.yudao.module.infra.dal.mysql.db.DataSourceConfigMapper;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties; import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
import org.jasypt.encryption.StringEncryptor; import org.jasypt.encryption.StringEncryptor;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic; import org.mockito.MockedStatic;
import org.mockito.stubbing.Answer;
import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
@ -21,7 +25,10 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.DATA_SOURCE_CONFIG_NOT_EXISTS; import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.DATA_SOURCE_CONFIG_NOT_EXISTS;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*; import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
/** /**
* {@link DataSourceConfigServiceImpl} 的单元测试类 * {@link DataSourceConfigServiceImpl} 的单元测试类
@ -43,13 +50,20 @@ public class DataSourceConfigServiceImplTest extends BaseDbUnitTest {
@MockBean @MockBean
private DynamicDataSourceProperties dynamicDataSourceProperties; private DynamicDataSourceProperties dynamicDataSourceProperties;
@BeforeEach
public void setUp() {
// mock 一个空实现的 StringEncryptor避免 EncryptTypeHandler 报错
ReflectUtil.setFieldValue(EncryptTypeHandler.class, "encryptor", stringEncryptor);
when(stringEncryptor.encrypt(anyString())).then((Answer<String>) invocation -> invocation.getArgument(0));
when(stringEncryptor.decrypt(anyString())).then((Answer<String>) invocation -> invocation.getArgument(0));
}
@Test @Test
public void testCreateDataSourceConfig_success() { public void testCreateDataSourceConfig_success() {
try (MockedStatic<JdbcUtils> databaseUtilsMock = mockStatic(JdbcUtils.class)) { try (MockedStatic<JdbcUtils> databaseUtilsMock = mockStatic(JdbcUtils.class)) {
// 准备参数 // 准备参数
DataSourceConfigCreateReqVO reqVO = randomPojo(DataSourceConfigCreateReqVO.class); DataSourceConfigCreateReqVO reqVO = randomPojo(DataSourceConfigCreateReqVO.class);
// mock 方法 // mock 方法
when(stringEncryptor.encrypt(eq(reqVO.getPassword()))).thenReturn("123456");
databaseUtilsMock.when(() -> JdbcUtils.isConnectionOK(eq(reqVO.getUrl()), databaseUtilsMock.when(() -> JdbcUtils.isConnectionOK(eq(reqVO.getUrl()),
eq(reqVO.getUsername()), eq(reqVO.getPassword()))).thenReturn(true); eq(reqVO.getUsername()), eq(reqVO.getPassword()))).thenReturn(true);
@ -59,8 +73,7 @@ public class DataSourceConfigServiceImplTest extends BaseDbUnitTest {
assertNotNull(dataSourceConfigId); assertNotNull(dataSourceConfigId);
// 校验记录的属性是否正确 // 校验记录的属性是否正确
DataSourceConfigDO dataSourceConfig = dataSourceConfigMapper.selectById(dataSourceConfigId); DataSourceConfigDO dataSourceConfig = dataSourceConfigMapper.selectById(dataSourceConfigId);
assertPojoEquals(reqVO, dataSourceConfig, "password"); assertPojoEquals(reqVO, dataSourceConfig);
assertEquals("123456", dataSourceConfig.getPassword());
} }
} }
@ -75,7 +88,7 @@ public class DataSourceConfigServiceImplTest extends BaseDbUnitTest {
o.setId(dbDataSourceConfig.getId()); // 设置更新的 ID o.setId(dbDataSourceConfig.getId()); // 设置更新的 ID
}); });
// mock 方法 // mock 方法
when(stringEncryptor.encrypt(eq(reqVO.getPassword()))).thenReturn("123456"); // when(stringEncryptor.encrypt(eq(reqVO.getPassword()))).thenReturn("123456");
databaseUtilsMock.when(() -> JdbcUtils.isConnectionOK(eq(reqVO.getUrl()), databaseUtilsMock.when(() -> JdbcUtils.isConnectionOK(eq(reqVO.getUrl()),
eq(reqVO.getUsername()), eq(reqVO.getPassword()))).thenReturn(true); eq(reqVO.getUsername()), eq(reqVO.getPassword()))).thenReturn(true);
@ -83,8 +96,7 @@ public class DataSourceConfigServiceImplTest extends BaseDbUnitTest {
dataSourceConfigService.updateDataSourceConfig(reqVO); dataSourceConfigService.updateDataSourceConfig(reqVO);
// 校验是否更新正确 // 校验是否更新正确
DataSourceConfigDO dataSourceConfig = dataSourceConfigMapper.selectById(reqVO.getId()); // 获取最新的 DataSourceConfigDO dataSourceConfig = dataSourceConfigMapper.selectById(reqVO.getId()); // 获取最新的
assertPojoEquals(reqVO, dataSourceConfig, "password"); assertPojoEquals(reqVO, dataSourceConfig);
assertEquals("123456", dataSourceConfig.getPassword());
} }
} }

View File

@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.system.dal.dataobject.sensitiveword;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.mybatis.core.type.StringLiSTTypeHandler; import cn.iocoder.yudao.framework.mybatis.core.type.StringListTypeHandler;
import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
@ -46,7 +46,7 @@ public class SensitiveWordDO extends BaseDO {
* 例如说tag 有短信论坛两种敏感词 "推广" 在短信下是敏感词在论坛下不是敏感词 * 例如说tag 有短信论坛两种敏感词 "推广" 在短信下是敏感词在论坛下不是敏感词
* 此时我们会存储一条敏感词记录它的 name "推广"tag 为短信 * 此时我们会存储一条敏感词记录它的 name "推广"tag 为短信
*/ */
@TableField(typeHandler = StringLiSTTypeHandler.class) @TableField(typeHandler = StringListTypeHandler.class)
private List<String> tags; private List<String> tags;
/** /**
* 状态 * 状态

View File

@ -23,7 +23,7 @@ public class TipApplicationRunner implements ApplicationRunner {
"项目启动成功!\n\t" + "项目启动成功!\n\t" +
"接口文档: \t{} \n\t" + "接口文档: \t{} \n\t" +
"开发文档: \t{} \n\t" + "开发文档: \t{} \n\t" +
"视频教程: \t{} \n" + "视频教程: \t{} \n\t" +
"源码解析: \t{} \n" + "源码解析: \t{} \n" +
"----------------------------------------------------------", "----------------------------------------------------------",
"https://mtw.so/6w48hX", "https://mtw.so/6w48hX",