mirror of
https://gitee.com/huangge1199_admin/vue-pro.git
synced 2024-11-26 17:21:53 +08:00
增加 MyBatis Plus 的 EncryptTypeHandler 类型处理器,实现字段的加密解密
This commit is contained in:
parent
3cc7a35ccc
commit
0ae9af0492
@ -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>
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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 = ",";
|
||||||
|
|
@ -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") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
@KeySequence("infra_data_source_config_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 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;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
/**
|
/**
|
||||||
* 状态
|
* 状态
|
||||||
|
@ -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",
|
||||||
|
Loading…
Reference in New Issue
Block a user