记录自己sharding+springbootV2+mybatisplus加密

需求背景

有一个业务线有敏感字段未加密,特对此字段加密。通过方案选型最终确定使用sharding加密

结合自身需求盘点所需改动点

  1. 当前服务是手动注入的,了解到sharding加密需要使用sharding数据源 所以决定改写成自动注入数据源等
  2. 数据源改造完成后只需要配置sharding相关配置即可实现

改造过程:

之前是这样注入的:

方案一

1.删除相关手动注入的代码

2.新增依赖


<dependency>
	<groupId>org.apache.shardingsphere</groupId>
	<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
	<version>4.1.1</version>
</dependency>

<--项目原来的依赖-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
</dependency>

3.启动类上新加:

配置的是和mapper.xml影射的interface **mapper的packages

如何寻找呢?=》xml文件中:namespace的package

@MapperScan(basePackages = {
                "com.****.dao",
                "com.****.dao1"
        })

4.通过配置一些参数和sharding交流

spring.shardingsphere.datasource.name=ds

spring.shardingsphere.datasource.ds.type=org.apache.commons.dbcp2.BasicDataSource
spring.shardingsphere.datasource.ds.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.ds.url=jdbc:mysql://127.0.0.1:3306/encrypt?serverTimezone=UTC&useSSL=false
spring.shardingsphere.datasource.ds.username=root
spring.shardingsphere.datasource.ds.password=
spring.shardingsphere.datasource.ds.max-total=100

spring.shardingsphere.encrypt.encryptors.encryptor_aes.type=aes
spring.shardingsphere.encrypt.encryptors.encryptor_aes.props.aes.key.value=123456
spring.shardingsphere.encrypt.tables.t_order.columns.user_id.plainColumn=user_decrypt
spring.shardingsphere.encrypt.tables.t_order.columns.user_id.cipherColumn=user_encrypt
spring.shardingsphere.encrypt.tables.t_order.columns.user_id.assistedQueryColumn=user_assisted
spring.shardingsphere.encrypt.tables.t_order.columns.user_id.encryptor=encryptor_aes

spring.shardingsphere.props.sql.show=true
spring.shardingsphere.props.query.with.cipher.column=true
 4.1 yml or apollo等一切你配置的地方
  • spring.shardingsphere.datasource.<data-source-name>.type= #数据库连接池类名称
  • spring.shardingsphere.datasource.<data-source-name>.driver-class-name= #数据库驱动类名
  • spring.shardingsphere.datasource.<data-source-name>.url= #数据库url连接
  • spring.shardingsphere.datasource.<data-source-name>.username= #数据库用户名
  • spring.shardingsphere.datasource.<data-source-name>.password= #数据库密码
  • spring.shardingsphere.datasource.<data-source-name>.xxx= #数据库连接池的其它属性
  • spring.shardingsphere.encrypt.encryptors.encryptor_aes.type=#加密类型
  • spring.shardingsphere.encrypt.encryptors.encryptor_aes.props.aes.key.value=#加密秘钥
  • spring.shardingsphere.encrypt.tables.<table-name>.columns.<col_name>.plainColumn=#明文列
  • spring.shardingsphere.encrypt.tables.<table-name>.columns.<col_name>.cipherColumn=#加密列
  • spring.shardingsphere.encrypt.tables.<table-name>.columns.<col_name>.encryptor=#加密算法
  • spring.shardingsphere.props.sql.show=#是否显示sql  true/false
  • spring.shardingsphere.props.sql.simple=#是否显示简单的重写后sql  true/false
  • spring.shardingsphere.props.query.with.cipher.column=#是否使用脱敏列查询 true/false
 4.2 sharding中针对以下数据类型没有处理
  • java.time.LocalDateTime

  • java.time.LocalDate

  • java.time.LocalTime

配置yml =>你的自定以的时间类型处理的类的package

mybatis-plus:
  type-handlers-package: com.**.db.config

需要自定义对应类型的处理器 


@Component
@MappedTypes(LocalDateTime.class)
@MappedJdbcTypes(value = JdbcType.DATE, includeNullJdbcType = true)
public class LocalDateTimeTypeHandler extends BaseTypeHandler<LocalDateTime> {

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, LocalDateTime parameter, JdbcType jdbcType)
            throws SQLException {
        ps.setTimestamp(i, Timestamp.valueOf(parameter));
    }

    @Override
    public LocalDateTime getNullableResult(ResultSet rs, String columnName) throws SQLException {
        Timestamp timestamp = rs.getTimestamp(columnName);
        return getLocalDateTime(timestamp);
    }

    @Override
    public LocalDateTime getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        Timestamp timestamp = rs.getTimestamp(columnIndex);
        return getLocalDateTime(timestamp);
    }

    @Override
    public LocalDateTime getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        Timestamp timestamp = cs.getTimestamp(columnIndex);
        return getLocalDateTime(timestamp);
    }

    private static LocalDateTime getLocalDateTime(Timestamp timestamp) {
        if (timestamp != null) {
            return LocalDateTime.ofInstant(timestamp.toInstant(), ZoneId.systemDefault());
        }
        return null;
    }
}


@Component
@MappedTypes(LocalDate.class)
@MappedJdbcTypes(value = JdbcType.DATE, includeNullJdbcType = true)
public class LocalDateTypeHandler extends BaseTypeHandler<LocalDate> {

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, LocalDate parameter, JdbcType jdbcType)
            throws SQLException {
        ps.setDate(i, Date.valueOf(parameter));
    }

    @Override
    public LocalDate getNullableResult(ResultSet rs, String columnName) throws SQLException {
        Date date = rs.getDate(columnName);
        return getLocalDate(date);
    }

    @Override
    public LocalDate getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        Date date = rs.getDate(columnIndex);
        return getLocalDate(date);
    }

    @Override
    public LocalDate getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        Date date = cs.getDate(columnIndex);
        return getLocalDate(date);
    }

    private static LocalDate getLocalDate(Date date) {
        if (date != null) {
            return date.toLocalDate();
        }
        return null;
    }
}


@Component
@MappedTypes(LocalTime.class)
@MappedJdbcTypes(value = JdbcType.DATE, includeNullJdbcType = true)
public class LocalTimeTypeHandler extends BaseTypeHandler<LocalTime> {

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, LocalTime parameter, JdbcType jdbcType)
            throws SQLException {
        ps.setTime(i, Time.valueOf(parameter));
    }

    @Override
    public LocalTime getNullableResult(ResultSet rs, String columnName) throws SQLException {
        Time time = rs.getTime(columnName);
        return getLocalTime(time);
    }

    @Override
    public LocalTime getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        Time time = rs.getTime(columnIndex);
        return getLocalTime(time);
    }

    @Override
    public LocalTime getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        Time time = cs.getTime(columnIndex);
        return getLocalTime(time);
    }

    private static LocalTime getLocalTime(Time time) {
        if (time != null) {
            return time.toLocalTime();
        }
        return null;
    }
}

通过配置一些参数和mybatisplus交流

mybatis-plus:
  mapper-locations: classpath*:mapper/*Mapper.xml
  type-aliases-packages: com.**.entity,com.**.entity1
  configuration:
    map-underscore-to-camel-case: true
  •  mapper-locations: classpath*:mapper/*Mapper.xml
    • 此处配置的是xml文件路径
    • 如何寻找呢?我的xml文件在这里
  •   type-aliases-packages: com.**.entity,com.**.entity1
    • **mapper.xml文件中和数据库中表列相对于的实体类的packages
    • 如何寻找呢?可以参考下=》type的package
  • configuration:
        map-underscore-to-camel-case: true
    • 驼峰映射开关

mybatis默认是属性名和数据库字段名一一对应的,

即 数据库表列:user_name => 实体类属性:user_name

但是java中一般使用驼峰命名 

数据库表列:user_name  => 实体类属性:userName

  •  type-handlers-package: com.**.db.config

我遇到的一些问题

报错“Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required”

刚开始引入的是 druid-spring-boot-starter ,排查发现应该引入 druid =》当时查文档查到的,具体原因忘记记录了(⊙︿⊙) 具体为什么我们引入了这个 没有太仔细排查 欢迎大家指点迷津


<--项目原来依赖starter更换为下面的-->
<!--        <dependency>-->
<!--            <groupId>com.alibaba</groupId>-->
<!--            <artifactId>druid-spring-boot-starter</artifactId>-->
<!--        </dependency>-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.8</version>
</dependency>
报错“java.lang.IllegalStateException: No typehandler found for property addTime”

添加配置参考4.2

报错“Ambiguous mapping. Cannot map”

启动类添加注解:@EnableFeignClients

没有报错,数据可以顺利插入了,但是我发现我的加密列没有写数据

搜索了一圈各个大佬们说 加密重写sql主要流程在这里:org.apache.shardingsphere.underlying.pluggble.prepare.BasePrepareEngine#prepare

搜索了下发现我没有这个类,排查发现:

我实际依赖的是4.0.1版本的sharding-jdbc-spring-boot-starter,该版本没有

  <dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
    <version>4.1.1</version>
  </dependency>

然后就开始里漫长的debug模式

首先我发现我每次创建数据源的时候,创建的都是这个:org.apache.shardingsphere.shardingjdbc.spring.boot.SpringBootConfiguration#shardingDataSource;不是创建的这个:org.apache.shardingsphere.shardingjdbc.spring.boot.SpringBootConfiguration#encryptDataSource

然后我就开始研究这个方法 

然后我发现他有一个执行条件:EncryptRuleCondition

debug的时候发现我没有在这个条件里获取不到这两个配置

我的配置是Apollo配置的,本地启动的时候 对于依赖的Apollo本地拉取不到,所以我这个配置不成功后续加上他果然也成功了 哈哈  到这里为止我的本次改造算是成功了 

后来上线发现不是上述那样,之所以拉不到远端Apollo配置,是因为没有配置springboot的Apollo配置

apollo.bootstrap.enabled: true
apollo.bootstrap.namespaces: application

启动类上配置的是spring的一个注解,不是springboot的

 方案二

Spring Boot配置 :: ShardingSphere

最近在搞这个的专项,突然看到文档里 其实还可以通过Java配置实现数据脱敏,当时为了省事,直接吧所有的都按照配置搞了,没有深入研究原来java配置数据源相关的东西,这次 特意研究了下

很简单的说

只需通过文档在手动java配置数据源的时候,把数据脱敏的规则加载进去。

由于项目中存在sharing多表,所以在这个sharding-datasource构造器里完善加密信息:


@Primary
@Bean("shardingDataSource")
public DataSource shardingDataSource(@Qualifier("originDataSource") DataSource dataSource) throws SQLException {
    ShardingRuleConfiguration shardingRuleConfiguration = new ShardingRuleConfiguration();
    TableRuleConfiguration tableRuleConfiguration = new TableRuleConfiguration("sharding_tmp_table",
            "ds0.original_table1,ds0.original_table2");
    ComplexShardingStrategyConfiguration complexShardingStrategyConfiguration =
            new ComplexShardingStrategyConfiguration("sharding_col", orderOilShardingAlgorithm);
    tableRuleConfiguration.setTableShardingStrategyConfig(complexShardingStrategyConfiguration);

    shardingRuleConfiguration.getTableRuleConfigs().addAll(Arrays.asList(tableRuleConfiguration));
    shardingRuleConfiguration.getBindingTableGroups().addAll(Arrays.asList("sharding_tmp_table"));
    Map<String, DataSource> dataSourceMap = Maps.newHashMap();
    dataSourceMap.put("ds0", dataSource);
    Properties props = new Properties();
    props.setProperty("sql.show", "true");
    props.setProperty("query.with.cipher.column",  "true");
    shardingRuleConfiguration.setEncryptRuleConfig(getEncryptRuleConfiguration(props));
    return ShardingDataSourceFactory.createDataSource(dataSourceMap, shardingRuleConfiguration, props);
}

private EncryptRuleConfiguration getEncryptRuleConfiguration(Properties props) {
    props.setProperty("aes.key.value", "加密关键字");
    EncryptorRuleConfiguration encryptorConfig = new EncryptorRuleConfiguration("PREFIXAES", props);
    EncryptColumnRuleConfiguration columnConfig =
            new EncryptColumnRuleConfiguration("明文列", "加密列", 
                    "", "加密算法");
    EncryptTableRuleConfiguration tableConfig = 
            new EncryptTableRuleConfiguration(Collections.singletonMap("明文列", columnConfig));
    EncryptRuleConfiguration encryptRuleConfig = new EncryptRuleConfiguration();
    encryptRuleConfig.getEncryptors().put("加密算法", encryptorConfig);
    encryptRuleConfig.getTables().put("加密表", tableConfig);
    return encryptRuleConfig;
}

分享的一些sharding上线后遇到的一些运行时exception:

一、mybatis中sql字符串用单引号,不能使用双引号

如果使用双引号,由于sharding语法校验严格,会解析为字段,导致sql解析错误

二、ON DUPLICATE KEY UPDATE语句中不能使用#{},${}

引入sharding数据源后,如果使用mysql 提供的 主键冲突时 进行更新操作的语句 ON DUPLICATE KEY UPDATE

因为数据库被代理,所以在 ON DUPLICATE KEY UPDATE语句中不能使用#{},${}, 需要使用VALUES方法。

三、不支持case when,having,union

  1. 可解析至第一个含表的子查询,在下层嵌套再次找到子查询抛错

子查询不支持聚合函数,不支持含shema的SQL:shardingsphere对sql的访问都是在同一个逻辑schema上

  1. 不支持CASE WHEN、HAVING、UNION (ALL)、有限支持子查询

  2. insert语句中,VALUES语句不支持运算表达式

  3. 不支持INSERT .. SELECT

  4. 不支持包含schema的语句

  5. 脱敏字段无法支持比较操作,如:大于小于、ORDER BY、BETWEEN、LIKE等。

  6. 脱敏字段无法支持计算操作,如:AVG、SUM以及计算表达式 。

四、Mysql like会导致无法使用脱敏字段

如果对脱敏字段使用like,会导致sharding不会对where条件内的原文字段进行替换为脱敏字段,依然使用原文字段擦汗寻

五、Mysql 方法函数不能省略()

之前会在一些查询语句中 where条件使用函数匹配段时间数据 

例如:select * from tab where (current_timestamp - create_time) > 30

例如:select * from tab where (current_timestamp() - create_time) > 30

如果sql中的方法函数省略(),会导致sql解析异常

六、使用between,in等关键字时不能使用DATE_FORMAT函数

  1. 使用in,between时候,如果使用DATE_FORMAT函数,会导致sql解析异常

  2. 使用DATE_FORMAT函数时候支持=,>,<等简单比较操作

七、sharding 在insert时候 原有sql有字段值直接写死的情况

  1. sharding 在insert时候如果原有sql有字段值直接写死的情况,在编译的时候会导致sql注入的值位置错误,字段位置对应不上的情况

  2. 解决方法:1. 写死值改为参数注入 2.把加密字段的放在最前面,写死为常量的字段在其后面

八、数据库时间转换LocalDateTime,LocalDate,LocalTime错误

由于目前引入sharding版本,不支持jsr310日期标准,需要通过引入jar形式解决日期类型转换问题(也可参考4.2)

  1. 引入sharding后,mybatisPlus时间类型转为LocalDateTime,LocalDate,LocalTime时会转换失败,可以引入mybatis-typehandlers-jsr310解决,但是需要在mybatis-plus-boot-starter前引入

【mybatis-typehandlers-jsr310】和【mybatis】在org.apache.ibatis.type路径下的类重复,需保证优先加载前者的类。感兴趣的可以研究下类加载顺序的控制,暴力解决可以把mybatis-plus-boot-starter版本降到2.3.3,但需关注会不会引发其他问题

<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-typehandlers-jsr310</artifactId> <version>1.0.2</version> </dependency>

九、使用sharding同时配置分库分表和脱敏时,配置方式和单独脱敏不一样

  • 需要配置encryptors的type,不需要配aes.key.value,sdk升级>1.2.11,参考如下:(详细可参考官方文档 方案二)

    spring.shardingsphere.sharding.encrypt-rule.encryptors.encryptor_prefixaes.type:加密类型
    spring.shardingsphere.sharding.encrypt-rule.tables.cebbank_coupon.columns.phone.plainColumn: ,明文列 spring.shardingsphere.sharding.encrypt-rule.tables.cebbank_coupon.columns.phone.cipherColumn: 加密列 spring.shardingsphere.sharding.encrypt-rule.tables.cebbank_coupon.columns.phone.encryptor: 加密器

十、不支持语法:now() between start and end

  • 可修改为 start > now() and end < now()

十一、启动报错:StringToDuration

  • 不支持duration对象的配置解析,可升级guava至最新版本

十二、sharding与DruidDataSource的filter有兼容性问题,可能导致NPE

  • 酌情删除相关filter配置

你还可以参考以下官方文档:

https://shardingsphere.apache.org/document/legacy/4.x/document/cn/features/orchestration/encrypt/

https://shardingsphere.apache.org/document/legacy/4.x/document/cn/manual/sharding-jdbc/usage/encrypt

  • 26
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值