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 58 <groupId>com.baomidou</groupId>
59 59 <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <!-- 多数据源 -->
60 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 69 </dependencies>
62 70  
63 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 21 */
22 22 @MappedJdbcTypes(JdbcType.VARCHAR)
23 23 @MappedTypes(List.class)
24   -public class StringLiSTTypeHandler implements TypeHandler<List<String>> {
  24 +public class StringListTypeHandler implements TypeHandler<List<String>> {
25 25  
26 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 1 package cn.iocoder.yudao.module.infra.dal.dataobject.db;
2 2  
3 3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  4 +import cn.iocoder.yudao.framework.mybatis.core.type.EncryptTypeHandler;
4 5 import com.baomidou.mybatisplus.annotation.KeySequence;
  6 +import com.baomidou.mybatisplus.annotation.TableField;
5 7 import com.baomidou.mybatisplus.annotation.TableName;
6 8 import lombok.Data;
7 9  
... ... @@ -10,7 +12,7 @@ import lombok.Data;
10 12 *
11 13 * @author 芋道源码
12 14 */
13   -@TableName("infra_data_source_config")
  15 +@TableName(value = "infra_data_source_config", autoResultMap = true)
14 16 @KeySequence("infra_data_source_config_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
15 17 @Data
16 18 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 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 8 import cn.iocoder.yudao.module.infra.dal.mysql.db.DataSourceConfigMapper;
9 9 import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
10 10 import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
11   -import org.jasypt.encryption.StringEncryptor;
12 11 import org.springframework.stereotype.Service;
13 12 import org.springframework.validation.annotation.Validated;
14 13  
... ... @@ -33,9 +32,6 @@ public class DataSourceConfigServiceImpl implements DataSourceConfigService {
33 32 private DataSourceConfigMapper dataSourceConfigMapper;
34 33  
35 34 @Resource
36   - private StringEncryptor stringEncryptor;
37   -
38   - @Resource
39 35 private DynamicDataSourceProperties dynamicDataSourceProperties;
40 36  
41 37 @Override
... ... @@ -44,7 +40,6 @@ public class DataSourceConfigServiceImpl implements DataSourceConfigService {
44 40 checkConnectionOK(dataSourceConfig);
45 41  
46 42 // 插入
47   - dataSourceConfig.setPassword(stringEncryptor.encrypt(createReqVO.getPassword()));
48 43 dataSourceConfigMapper.insert(dataSourceConfig);
49 44 // 返回
50 45 return dataSourceConfig.getId();
... ... @@ -58,7 +53,6 @@ public class DataSourceConfigServiceImpl implements DataSourceConfigService {
58 53 checkConnectionOK(updateObj);
59 54  
60 55 // 更新
61   - updateObj.setPassword(stringEncryptor.encrypt(updateObj.getPassword()));
62 56 dataSourceConfigMapper.updateById(updateObj);
63 57 }
64 58  
... ... @@ -83,12 +77,7 @@ public class DataSourceConfigServiceImpl implements DataSourceConfigService {
83 77 return buildMasterDataSourceConfig();
84 78 }
85 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 83 @Override
... ...
yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/db/DataSourceConfigServiceImplTest.java
1 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 5 import cn.iocoder.yudao.framework.mybatis.core.util.JdbcUtils;
4 6 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
5 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 10 import cn.iocoder.yudao.module.infra.dal.mysql.db.DataSourceConfigMapper;
9 11 import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
10 12 import org.jasypt.encryption.StringEncryptor;
  13 +import org.junit.jupiter.api.BeforeEach;
11 14 import org.junit.jupiter.api.Test;
12 15 import org.mockito.MockedStatic;
  16 +import org.mockito.stubbing.Answer;
13 17 import org.springframework.boot.test.mock.mockito.MockBean;
14 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 25 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
22 26 import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.DATA_SOURCE_CONFIG_NOT_EXISTS;
23 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 34 * {@link DataSourceConfigServiceImpl} 的单元测试类
... ... @@ -43,13 +50,20 @@ public class DataSourceConfigServiceImplTest extends BaseDbUnitTest {
43 50 @MockBean
44 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 61 @Test
47 62 public void testCreateDataSourceConfig_success() {
48 63 try (MockedStatic<JdbcUtils> databaseUtilsMock = mockStatic(JdbcUtils.class)) {
49 64 // 准备参数
50 65 DataSourceConfigCreateReqVO reqVO = randomPojo(DataSourceConfigCreateReqVO.class);
51 66 // mock 方法
52   - when(stringEncryptor.encrypt(eq(reqVO.getPassword()))).thenReturn("123456");
53 67 databaseUtilsMock.when(() -> JdbcUtils.isConnectionOK(eq(reqVO.getUrl()),
54 68 eq(reqVO.getUsername()), eq(reqVO.getPassword()))).thenReturn(true);
55 69  
... ... @@ -59,8 +73,7 @@ public class DataSourceConfigServiceImplTest extends BaseDbUnitTest {
59 73 assertNotNull(dataSourceConfigId);
60 74 // 校验记录的属性是否正确
61 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 88 o.setId(dbDataSourceConfig.getId()); // 设置更新的 ID
76 89 });
77 90 // mock 方法
78   - when(stringEncryptor.encrypt(eq(reqVO.getPassword()))).thenReturn("123456");
  91 +// when(stringEncryptor.encrypt(eq(reqVO.getPassword()))).thenReturn("123456");
79 92 databaseUtilsMock.when(() -> JdbcUtils.isConnectionOK(eq(reqVO.getUrl()),
80 93 eq(reqVO.getUsername()), eq(reqVO.getPassword()))).thenReturn(true);
81 94  
... ... @@ -83,8 +96,7 @@ public class DataSourceConfigServiceImplTest extends BaseDbUnitTest {
83 96 dataSourceConfigService.updateDataSourceConfig(reqVO);
84 97 // 校验是否更新正确
85 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 2  
3 3 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4 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 6 import com.baomidou.mybatisplus.annotation.KeySequence;
7 7 import com.baomidou.mybatisplus.annotation.TableField;
8 8 import com.baomidou.mybatisplus.annotation.TableId;
... ... @@ -46,7 +46,7 @@ public class SensitiveWordDO extends BaseDO {
46 46 * 例如说,tag 有短信、论坛两种,敏感词 "推广" 在短信下是敏感词,在论坛下不是敏感词。
47 47 * 此时,我们会存储一条敏感词记录,它的 name 为"推广",tag 为短信。
48 48 */
49   - @TableField(typeHandler = StringLiSTTypeHandler.class)
  49 + @TableField(typeHandler = StringListTypeHandler.class)
50 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 23 "项目启动成功!\n\t" +
24 24 "接口文档: \t{} \n\t" +
25 25 "开发文档: \t{} \n\t" +
26   - "视频教程: \t{} \n" +
  26 + "视频教程: \t{} \n\t" +
27 27 "源码解析: \t{} \n" +
28 28 "----------------------------------------------------------",
29 29 "https://mtw.so/6w48hX",
... ...