فهرست منبع

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

YunaiV 2 سال پیش
والد
کامیت
0ae9af0492

+ 8 - 0
yudao-framework/yudao-spring-boot-starter-mybatis/pom.xml

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

+ 63 - 0
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/type/EncryptTypeHandler.java

@@ -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;
+    }
+
+}

+ 1 - 1
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/type/StringLiSTTypeHandler.java → 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;
  */
 @MappedJdbcTypes(JdbcType.VARCHAR)
 @MappedTypes(List.class)
-public class StringLiSTTypeHandler implements TypeHandler<List<String>> {
+public class StringListTypeHandler implements TypeHandler<List<String>> {
 
     private static final String COMMA = ",";
 

+ 4 - 1
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/db/DataSourceConfigDO.java

@@ -1,7 +1,9 @@
 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.type.EncryptTypeHandler;
 import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.Data;
 
@@ -10,7 +12,7 @@ import lombok.Data;
  *
  * @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 等数据库,可不写。
 @Data
 public class DataSourceConfigDO extends BaseDO {
@@ -40,6 +42,7 @@ public class DataSourceConfigDO extends BaseDO {
     /**
      * 密码
      */
+    @TableField(typeHandler = EncryptTypeHandler.class)
     private String password;
 
 }

+ 1 - 12
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;
 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.DynamicDataSourceProperties;
-import org.jasypt.encryption.StringEncryptor;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
@@ -32,9 +31,6 @@ public class DataSourceConfigServiceImpl implements DataSourceConfigService {
     @Resource
     private DataSourceConfigMapper dataSourceConfigMapper;
 
-    @Resource
-    private StringEncryptor stringEncryptor;
-
     @Resource
     private DynamicDataSourceProperties dynamicDataSourceProperties;
 
@@ -44,7 +40,6 @@ public class DataSourceConfigServiceImpl implements DataSourceConfigService {
         checkConnectionOK(dataSourceConfig);
 
         // 插入
-        dataSourceConfig.setPassword(stringEncryptor.encrypt(createReqVO.getPassword()));
         dataSourceConfigMapper.insert(dataSourceConfig);
         // 返回
         return dataSourceConfig.getId();
@@ -58,7 +53,6 @@ public class DataSourceConfigServiceImpl implements DataSourceConfigService {
         checkConnectionOK(updateObj);
 
         // 更新
-        updateObj.setPassword(stringEncryptor.encrypt(updateObj.getPassword()));
         dataSourceConfigMapper.updateById(updateObj);
     }
 
@@ -83,12 +77,7 @@ public class DataSourceConfigServiceImpl implements DataSourceConfigService {
             return buildMasterDataSourceConfig();
         }
         // 从 DB 中读取
-        DataSourceConfigDO dataSourceConfig = dataSourceConfigMapper.selectById(id);
-        try {
-            dataSourceConfig.setPassword(stringEncryptor.decrypt(dataSourceConfig.getPassword()));
-        } catch (Exception ignore) { // 解码失败,则不解码
-        }
-        return dataSourceConfig;
+        return dataSourceConfigMapper.selectById(id);
     }
 
     @Override

+ 19 - 7
yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/db/DataSourceConfigServiceImplTest.java

@@ -1,5 +1,7 @@
 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.test.core.ut.BaseDbUnitTest;
 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 com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
 import org.jasypt.encryption.StringEncryptor;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.mockito.MockedStatic;
+import org.mockito.stubbing.Answer;
 import org.springframework.boot.test.mock.mockito.MockBean;
 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.module.infra.enums.ErrorCodeConstants.DATA_SOURCE_CONFIG_NOT_EXISTS;
 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} 的单元测试类
@@ -43,13 +50,20 @@ public class DataSourceConfigServiceImplTest extends BaseDbUnitTest {
     @MockBean
     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
     public void testCreateDataSourceConfig_success() {
         try (MockedStatic<JdbcUtils> databaseUtilsMock = mockStatic(JdbcUtils.class)) {
             // 准备参数
             DataSourceConfigCreateReqVO reqVO = randomPojo(DataSourceConfigCreateReqVO.class);
             // mock 方法
-            when(stringEncryptor.encrypt(eq(reqVO.getPassword()))).thenReturn("123456");
             databaseUtilsMock.when(() -> JdbcUtils.isConnectionOK(eq(reqVO.getUrl()),
                     eq(reqVO.getUsername()), eq(reqVO.getPassword()))).thenReturn(true);
 
@@ -59,8 +73,7 @@ public class DataSourceConfigServiceImplTest extends BaseDbUnitTest {
             assertNotNull(dataSourceConfigId);
             // 校验记录的属性是否正确
             DataSourceConfigDO dataSourceConfig = dataSourceConfigMapper.selectById(dataSourceConfigId);
-            assertPojoEquals(reqVO, dataSourceConfig, "password");
-            assertEquals("123456", dataSourceConfig.getPassword());
+            assertPojoEquals(reqVO, dataSourceConfig);
         }
     }
 
@@ -75,7 +88,7 @@ public class DataSourceConfigServiceImplTest extends BaseDbUnitTest {
                 o.setId(dbDataSourceConfig.getId()); // 设置更新的 ID
             });
             // mock 方法
-            when(stringEncryptor.encrypt(eq(reqVO.getPassword()))).thenReturn("123456");
+//            when(stringEncryptor.encrypt(eq(reqVO.getPassword()))).thenReturn("123456");
             databaseUtilsMock.when(() -> JdbcUtils.isConnectionOK(eq(reqVO.getUrl()),
                     eq(reqVO.getUsername()), eq(reqVO.getPassword()))).thenReturn(true);
 
@@ -83,8 +96,7 @@ public class DataSourceConfigServiceImplTest extends BaseDbUnitTest {
             dataSourceConfigService.updateDataSourceConfig(reqVO);
             // 校验是否更新正确
             DataSourceConfigDO dataSourceConfig = dataSourceConfigMapper.selectById(reqVO.getId()); // 获取最新的
-            assertPojoEquals(reqVO, dataSourceConfig, "password");
-            assertEquals("123456", dataSourceConfig.getPassword());
+            assertPojoEquals(reqVO, dataSourceConfig);
         }
     }
 

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

+ 1 - 1
yudao-server/src/main/java/cn/iocoder/yudao/server/framework/tip/core/TipApplicationRunner.java

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