Commit 0ae9af04927fa3a161789e58037915537f8d2f5e

Authored by YunaiV
1 parent 3cc7a35c

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

yudao-framework/yudao-spring-boot-starter-mybatis/pom.xml
@@ -58,6 +58,14 @@ @@ -58,6 +58,14 @@
58 <groupId>com.baomidou</groupId> 58 <groupId>com.baomidou</groupId>
59 <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <!-- 多数据源 --> 59 <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <!-- 多数据源 -->
60 </dependency> 60 </dependency>
  61 +
  62 + <!-- 工具类相关 -->
  63 + <dependency>
  64 + <groupId>com.github.ulisesbocchio</groupId>
  65 + <artifactId>jasypt-spring-boot-starter</artifactId> <!-- 加解密 -->
  66 + <optional>true</optional>
  67 + </dependency>
  68 +
61 </dependencies> 69 </dependencies>
62 70
63 </project> 71 </project>
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/type/EncryptTypeHandler.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.mybatis.core.type;
  2 +
  3 +import cn.hutool.core.lang.Assert;
  4 +import cn.hutool.extra.spring.SpringUtil;
  5 +import org.apache.ibatis.type.BaseTypeHandler;
  6 +import org.apache.ibatis.type.JdbcType;
  7 +import org.jasypt.encryption.StringEncryptor;
  8 +
  9 +import java.sql.CallableStatement;
  10 +import java.sql.PreparedStatement;
  11 +import java.sql.ResultSet;
  12 +import java.sql.SQLException;
  13 +
  14 +/**
  15 + * 字段字段的 TypeHandler 实现类,基于 {@link StringEncryptor} 实现
  16 + * 可通过 jasypt.encryptor.password 配置项,设置密钥
  17 + *
  18 + * @author 芋道源码
  19 + */
  20 +public class EncryptTypeHandler extends BaseTypeHandler<String> {
  21 +
  22 + private static StringEncryptor encryptor;
  23 +
  24 + @Override
  25 + public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
  26 + ps.setString(i, getEncryptor().encrypt(parameter));
  27 + }
  28 +
  29 + @Override
  30 + public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
  31 + String value = rs.getString(columnName);
  32 + return getResult(value);
  33 + }
  34 +
  35 + @Override
  36 + public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
  37 + String value = rs.getString(columnIndex);
  38 + return getResult(value);
  39 + }
  40 +
  41 + @Override
  42 + public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
  43 + String value = cs.getString(columnIndex);
  44 + return getResult(value);
  45 + }
  46 +
  47 + private String getResult(String value) {
  48 + if (value == null) {
  49 + return null;
  50 + }
  51 + return getEncryptor().decrypt(value);
  52 + }
  53 +
  54 + private StringEncryptor getEncryptor() {
  55 + if (encryptor != null) {
  56 + return encryptor;
  57 + }
  58 + encryptor = SpringUtil.getBean(StringEncryptor.class);
  59 + Assert.notNull(encryptor, "StringEncryptor 不能为空");
  60 + return encryptor;
  61 + }
  62 +
  63 +}
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/type/StringLiSTTypeHandler.java renamed to yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/type/StringListTypeHandler.java
@@ -21,7 +21,7 @@ import java.util.List; @@ -21,7 +21,7 @@ import java.util.List;
21 */ 21 */
22 @MappedJdbcTypes(JdbcType.VARCHAR) 22 @MappedJdbcTypes(JdbcType.VARCHAR)
23 @MappedTypes(List.class) 23 @MappedTypes(List.class)
24 -public class StringLiSTTypeHandler implements TypeHandler<List<String>> { 24 +public class StringListTypeHandler implements TypeHandler<List<String>> {
25 25
26 private static final String COMMA = ","; 26 private static final String COMMA = ",";
27 27
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/db/DataSourceConfigDO.java
1 package cn.iocoder.yudao.module.infra.dal.dataobject.db; 1 package cn.iocoder.yudao.module.infra.dal.dataobject.db;
2 2
3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; 3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  4 +import cn.iocoder.yudao.framework.mybatis.core.type.EncryptTypeHandler;
4 import com.baomidou.mybatisplus.annotation.KeySequence; 5 import com.baomidou.mybatisplus.annotation.KeySequence;
  6 +import com.baomidou.mybatisplus.annotation.TableField;
5 import com.baomidou.mybatisplus.annotation.TableName; 7 import com.baomidou.mybatisplus.annotation.TableName;
6 import lombok.Data; 8 import lombok.Data;
7 9
@@ -10,7 +12,7 @@ import lombok.Data; @@ -10,7 +12,7 @@ import lombok.Data;
10 * 12 *
11 * @author 芋道源码 13 * @author 芋道源码
12 */ 14 */
13 -@TableName("infra_data_source_config") 15 +@TableName(value = "infra_data_source_config", autoResultMap = true)
14 @KeySequence("infra_data_source_config_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 16 @KeySequence("infra_data_source_config_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
15 @Data 17 @Data
16 public class DataSourceConfigDO extends BaseDO { 18 public class DataSourceConfigDO extends BaseDO {
@@ -40,6 +42,7 @@ public class DataSourceConfigDO extends BaseDO { @@ -40,6 +42,7 @@ public class DataSourceConfigDO extends BaseDO {
40 /** 42 /**
41 * 密码 43 * 密码
42 */ 44 */
  45 + @TableField(typeHandler = EncryptTypeHandler.class)
43 private String password; 46 private String password;
44 47
45 } 48 }
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/db/DataSourceConfigServiceImpl.java
@@ -8,7 +8,6 @@ import cn.iocoder.yudao.module.infra.dal.dataobject.db.DataSourceConfigDO; @@ -8,7 +8,6 @@ import cn.iocoder.yudao.module.infra.dal.dataobject.db.DataSourceConfigDO;
8 import cn.iocoder.yudao.module.infra.dal.mysql.db.DataSourceConfigMapper; 8 import cn.iocoder.yudao.module.infra.dal.mysql.db.DataSourceConfigMapper;
9 import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty; 9 import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
10 import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties; 10 import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
11 -import org.jasypt.encryption.StringEncryptor;  
12 import org.springframework.stereotype.Service; 11 import org.springframework.stereotype.Service;
13 import org.springframework.validation.annotation.Validated; 12 import org.springframework.validation.annotation.Validated;
14 13
@@ -33,9 +32,6 @@ public class DataSourceConfigServiceImpl implements DataSourceConfigService { @@ -33,9 +32,6 @@ public class DataSourceConfigServiceImpl implements DataSourceConfigService {
33 private DataSourceConfigMapper dataSourceConfigMapper; 32 private DataSourceConfigMapper dataSourceConfigMapper;
34 33
35 @Resource 34 @Resource
36 - private StringEncryptor stringEncryptor;  
37 -  
38 - @Resource  
39 private DynamicDataSourceProperties dynamicDataSourceProperties; 35 private DynamicDataSourceProperties dynamicDataSourceProperties;
40 36
41 @Override 37 @Override
@@ -44,7 +40,6 @@ public class DataSourceConfigServiceImpl implements DataSourceConfigService { @@ -44,7 +40,6 @@ public class DataSourceConfigServiceImpl implements DataSourceConfigService {
44 checkConnectionOK(dataSourceConfig); 40 checkConnectionOK(dataSourceConfig);
45 41
46 // 插入 42 // 插入
47 - dataSourceConfig.setPassword(stringEncryptor.encrypt(createReqVO.getPassword()));  
48 dataSourceConfigMapper.insert(dataSourceConfig); 43 dataSourceConfigMapper.insert(dataSourceConfig);
49 // 返回 44 // 返回
50 return dataSourceConfig.getId(); 45 return dataSourceConfig.getId();
@@ -58,7 +53,6 @@ public class DataSourceConfigServiceImpl implements DataSourceConfigService { @@ -58,7 +53,6 @@ public class DataSourceConfigServiceImpl implements DataSourceConfigService {
58 checkConnectionOK(updateObj); 53 checkConnectionOK(updateObj);
59 54
60 // 更新 55 // 更新
61 - updateObj.setPassword(stringEncryptor.encrypt(updateObj.getPassword()));  
62 dataSourceConfigMapper.updateById(updateObj); 56 dataSourceConfigMapper.updateById(updateObj);
63 } 57 }
64 58
@@ -83,12 +77,7 @@ public class DataSourceConfigServiceImpl implements DataSourceConfigService { @@ -83,12 +77,7 @@ public class DataSourceConfigServiceImpl implements DataSourceConfigService {
83 return buildMasterDataSourceConfig(); 77 return buildMasterDataSourceConfig();
84 } 78 }
85 // 从 DB 中读取 79 // 从 DB 中读取
86 - DataSourceConfigDO dataSourceConfig = dataSourceConfigMapper.selectById(id);  
87 - try {  
88 - dataSourceConfig.setPassword(stringEncryptor.decrypt(dataSourceConfig.getPassword()));  
89 - } catch (Exception ignore) { // 解码失败,则不解码  
90 - }  
91 - return dataSourceConfig; 80 + return dataSourceConfigMapper.selectById(id);
92 } 81 }
93 82
94 @Override 83 @Override
yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/db/DataSourceConfigServiceImplTest.java
1 package cn.iocoder.yudao.module.infra.service.db; 1 package cn.iocoder.yudao.module.infra.service.db;
2 2
  3 +import cn.hutool.core.util.ReflectUtil;
  4 +import cn.iocoder.yudao.framework.mybatis.core.type.EncryptTypeHandler;
3 import cn.iocoder.yudao.framework.mybatis.core.util.JdbcUtils; 5 import cn.iocoder.yudao.framework.mybatis.core.util.JdbcUtils;
4 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; 6 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
5 import cn.iocoder.yudao.module.infra.controller.admin.db.vo.DataSourceConfigCreateReqVO; 7 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; @@ -8,8 +10,10 @@ import cn.iocoder.yudao.module.infra.dal.dataobject.db.DataSourceConfigDO;
8 import cn.iocoder.yudao.module.infra.dal.mysql.db.DataSourceConfigMapper; 10 import cn.iocoder.yudao.module.infra.dal.mysql.db.DataSourceConfigMapper;
9 import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties; 11 import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
10 import org.jasypt.encryption.StringEncryptor; 12 import org.jasypt.encryption.StringEncryptor;
  13 +import org.junit.jupiter.api.BeforeEach;
11 import org.junit.jupiter.api.Test; 14 import org.junit.jupiter.api.Test;
12 import org.mockito.MockedStatic; 15 import org.mockito.MockedStatic;
  16 +import org.mockito.stubbing.Answer;
13 import org.springframework.boot.test.mock.mockito.MockBean; 17 import org.springframework.boot.test.mock.mockito.MockBean;
14 import org.springframework.context.annotation.Import; 18 import org.springframework.context.annotation.Import;
15 19
@@ -21,7 +25,10 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId @@ -21,7 +25,10 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId
21 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; 25 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
22 import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.DATA_SOURCE_CONFIG_NOT_EXISTS; 26 import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.DATA_SOURCE_CONFIG_NOT_EXISTS;
23 import static org.junit.jupiter.api.Assertions.*; 27 import static org.junit.jupiter.api.Assertions.*;
24 -import static org.mockito.Mockito.*; 28 +import static org.mockito.ArgumentMatchers.anyString;
  29 +import static org.mockito.ArgumentMatchers.eq;
  30 +import static org.mockito.Mockito.mockStatic;
  31 +import static org.mockito.Mockito.when;
25 32
26 /** 33 /**
27 * {@link DataSourceConfigServiceImpl} 的单元测试类 34 * {@link DataSourceConfigServiceImpl} 的单元测试类
@@ -43,13 +50,20 @@ public class DataSourceConfigServiceImplTest extends BaseDbUnitTest { @@ -43,13 +50,20 @@ public class DataSourceConfigServiceImplTest extends BaseDbUnitTest {
43 @MockBean 50 @MockBean
44 private DynamicDataSourceProperties dynamicDataSourceProperties; 51 private DynamicDataSourceProperties dynamicDataSourceProperties;
45 52
  53 + @BeforeEach
  54 + public void setUp() {
  55 + // mock 一个空实现的 StringEncryptor,避免 EncryptTypeHandler 报错
  56 + ReflectUtil.setFieldValue(EncryptTypeHandler.class, "encryptor", stringEncryptor);
  57 + when(stringEncryptor.encrypt(anyString())).then((Answer<String>) invocation -> invocation.getArgument(0));
  58 + when(stringEncryptor.decrypt(anyString())).then((Answer<String>) invocation -> invocation.getArgument(0));
  59 + }
  60 +
46 @Test 61 @Test
47 public void testCreateDataSourceConfig_success() { 62 public void testCreateDataSourceConfig_success() {
48 try (MockedStatic<JdbcUtils> databaseUtilsMock = mockStatic(JdbcUtils.class)) { 63 try (MockedStatic<JdbcUtils> databaseUtilsMock = mockStatic(JdbcUtils.class)) {
49 // 准备参数 64 // 准备参数
50 DataSourceConfigCreateReqVO reqVO = randomPojo(DataSourceConfigCreateReqVO.class); 65 DataSourceConfigCreateReqVO reqVO = randomPojo(DataSourceConfigCreateReqVO.class);
51 // mock 方法 66 // mock 方法
52 - when(stringEncryptor.encrypt(eq(reqVO.getPassword()))).thenReturn("123456");  
53 databaseUtilsMock.when(() -> JdbcUtils.isConnectionOK(eq(reqVO.getUrl()), 67 databaseUtilsMock.when(() -> JdbcUtils.isConnectionOK(eq(reqVO.getUrl()),
54 eq(reqVO.getUsername()), eq(reqVO.getPassword()))).thenReturn(true); 68 eq(reqVO.getUsername()), eq(reqVO.getPassword()))).thenReturn(true);
55 69
@@ -59,8 +73,7 @@ public class DataSourceConfigServiceImplTest extends BaseDbUnitTest { @@ -59,8 +73,7 @@ public class DataSourceConfigServiceImplTest extends BaseDbUnitTest {
59 assertNotNull(dataSourceConfigId); 73 assertNotNull(dataSourceConfigId);
60 // 校验记录的属性是否正确 74 // 校验记录的属性是否正确
61 DataSourceConfigDO dataSourceConfig = dataSourceConfigMapper.selectById(dataSourceConfigId); 75 DataSourceConfigDO dataSourceConfig = dataSourceConfigMapper.selectById(dataSourceConfigId);
62 - assertPojoEquals(reqVO, dataSourceConfig, "password");  
63 - assertEquals("123456", dataSourceConfig.getPassword()); 76 + assertPojoEquals(reqVO, dataSourceConfig);
64 } 77 }
65 } 78 }
66 79
@@ -75,7 +88,7 @@ public class DataSourceConfigServiceImplTest extends BaseDbUnitTest { @@ -75,7 +88,7 @@ public class DataSourceConfigServiceImplTest extends BaseDbUnitTest {
75 o.setId(dbDataSourceConfig.getId()); // 设置更新的 ID 88 o.setId(dbDataSourceConfig.getId()); // 设置更新的 ID
76 }); 89 });
77 // mock 方法 90 // mock 方法
78 - when(stringEncryptor.encrypt(eq(reqVO.getPassword()))).thenReturn("123456"); 91 +// when(stringEncryptor.encrypt(eq(reqVO.getPassword()))).thenReturn("123456");
79 databaseUtilsMock.when(() -> JdbcUtils.isConnectionOK(eq(reqVO.getUrl()), 92 databaseUtilsMock.when(() -> JdbcUtils.isConnectionOK(eq(reqVO.getUrl()),
80 eq(reqVO.getUsername()), eq(reqVO.getPassword()))).thenReturn(true); 93 eq(reqVO.getUsername()), eq(reqVO.getPassword()))).thenReturn(true);
81 94
@@ -83,8 +96,7 @@ public class DataSourceConfigServiceImplTest extends BaseDbUnitTest { @@ -83,8 +96,7 @@ public class DataSourceConfigServiceImplTest extends BaseDbUnitTest {
83 dataSourceConfigService.updateDataSourceConfig(reqVO); 96 dataSourceConfigService.updateDataSourceConfig(reqVO);
84 // 校验是否更新正确 97 // 校验是否更新正确
85 DataSourceConfigDO dataSourceConfig = dataSourceConfigMapper.selectById(reqVO.getId()); // 获取最新的 98 DataSourceConfigDO dataSourceConfig = dataSourceConfigMapper.selectById(reqVO.getId()); // 获取最新的
86 - assertPojoEquals(reqVO, dataSourceConfig, "password");  
87 - assertEquals("123456", dataSourceConfig.getPassword()); 99 + assertPojoEquals(reqVO, dataSourceConfig);
88 } 100 }
89 } 101 }
90 102
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/sensitiveword/SensitiveWordDO.java
@@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.system.dal.dataobject.sensitiveword; @@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.system.dal.dataobject.sensitiveword;
2 2
3 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 3 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; 4 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
5 -import cn.iocoder.yudao.framework.mybatis.core.type.StringLiSTTypeHandler; 5 +import cn.iocoder.yudao.framework.mybatis.core.type.StringListTypeHandler;
6 import com.baomidou.mybatisplus.annotation.KeySequence; 6 import com.baomidou.mybatisplus.annotation.KeySequence;
7 import com.baomidou.mybatisplus.annotation.TableField; 7 import com.baomidou.mybatisplus.annotation.TableField;
8 import com.baomidou.mybatisplus.annotation.TableId; 8 import com.baomidou.mybatisplus.annotation.TableId;
@@ -46,7 +46,7 @@ public class SensitiveWordDO extends BaseDO { @@ -46,7 +46,7 @@ public class SensitiveWordDO extends BaseDO {
46 * 例如说,tag 有短信、论坛两种,敏感词 "推广" 在短信下是敏感词,在论坛下不是敏感词。 46 * 例如说,tag 有短信、论坛两种,敏感词 "推广" 在短信下是敏感词,在论坛下不是敏感词。
47 * 此时,我们会存储一条敏感词记录,它的 name 为"推广",tag 为短信。 47 * 此时,我们会存储一条敏感词记录,它的 name 为"推广",tag 为短信。
48 */ 48 */
49 - @TableField(typeHandler = StringLiSTTypeHandler.class) 49 + @TableField(typeHandler = StringListTypeHandler.class)
50 private List<String> tags; 50 private List<String> tags;
51 /** 51 /**
52 * 状态 52 * 状态
yudao-server/src/main/java/cn/iocoder/yudao/server/framework/tip/core/TipApplicationRunner.java
@@ -23,7 +23,7 @@ public class TipApplicationRunner implements ApplicationRunner { @@ -23,7 +23,7 @@ public class TipApplicationRunner implements ApplicationRunner {
23 "项目启动成功!\n\t" + 23 "项目启动成功!\n\t" +
24 "接口文档: \t{} \n\t" + 24 "接口文档: \t{} \n\t" +
25 "开发文档: \t{} \n\t" + 25 "开发文档: \t{} \n\t" +
26 - "视频教程: \t{} \n" + 26 + "视频教程: \t{} \n\t" +
27 "源码解析: \t{} \n" + 27 "源码解析: \t{} \n" +
28 "----------------------------------------------------------", 28 "----------------------------------------------------------",
29 "https://mtw.so/6w48hX", 29 "https://mtw.so/6w48hX",