Spring Boot 集成 ShardingSphere-Encrypt 实现字段级实时脱敏

《个保法》和《数安法》跑完一轮审计后,敏感字段不落密文或掩码基本过不了关。合规压力下来了,但业务还得跑,不同场景对脱敏的诉求压根不是一回事。

开发测试环境最怕生产数据泄露,通常直接做静态替换或泛化,数据最好跟生产物理隔离。数据分析那边要的是数据分布特征,不能搞成乱码影响聚合,一般得上同态加密或保序哈希,保证统计准就行。客服和运营查单子最折腾,既要快速定位用户,又不能全量暴露明文,只能靠动态掩码,权限一收就变 138****5678。以前图省事,在 Service 层或者 DTO 转换里硬编码 mask() 方法,规则一改全得重新发版,维护成本极高。企业级方案必须把脱敏逻辑从业务代码里彻底抽出来,做到存储和展示分离,规则能热更新,这才叫可持续。


1. 方案选型:为什么最后落到 JDBC 代理层?

踩了几个坑之后,基本能排除掉其他三条路。在网关或 API 层做响应体拦截确实对业务代码无感,但解析全量 JSON 性能损耗大,跨表查询、分页场景或者流式接口很容易翻车,而且一旦下游要拿原始值做计算,网关层根本兜不住。

用 MyBatis 拦截器或者 JPA Converter 侵入性太强。改分页插件、动态 SQL 或者复杂联表时,经常因为拦截时机不对导致结果集错位,适配成本随着框架升级只会越来越高。

数据库层用视图或触发器做列加密,DBA 通常不乐意放权。计算压力全压在 DB 上,扩缩容麻烦,审计日志也容易被加密逻辑干扰,排查问题像盲盒。

最后我们选了 JDBC 驱动层拦截,也就是直接嵌 ShardingSphere-Encrypt。它工作在 Spring Boot 应用内部,SQL 解析、路由改写、结果归并全自动完成,业务代码一行不用动。对 MyBatis、JPA、JdbcTemplate 完全透明,动态策略也能跟着会话上下文走。这是目前平衡开发效率、合规要求和运维成本的最优解。


2. ShardingSphere-Encrypt 底层是怎么转起来的

它不是给字段加个注解就完事了,底层是一整套 SQL 透明代理引擎。算法走的是标准 SPI(org.apache.shardingsphere.encrypt.api.spi.EncryptAlgorithm),支持标准对称/非对称加密、辅助查询算法,还有专门做展示的 MaskAlgorithm(不存密文,只改返回结果)。你可以自己实现国密 SM4、AES-GCM 或者业务特定的混淆逻辑,插上就能用。

核心流程拆解下来就这几步:SQL 过来先被语法分析器切成 AST,引擎识别到配置了脱敏规则的列,直接改写 SQL。比如原本查 SELECT phone FROM t_user WHERE phone = ?,如果配了 assistedQueryColumn,会被重写成查哈希或密文列。执行完拿到结果集后,在 Merge 阶段根据当前会话的安全上下文(比如当前用户角色是客服还是超管、来源 IP 是否可信),自动决定是返回明文、密文还是走掩码逻辑打星号。整个过程在 DataSource 和 Connection 之间完成,业务层根本感知不到中间层的存在。


3. Spring Boot 接入与配置细节

依赖直接用 shardingsphere-jdbc 5.4.x 版本。Spring Boot 这边不用自己造 DataSource Bean,直接让 ShardingSphere 接管数据源配置。

xml 复制代码
<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>shardingsphere-jdbc</artifactId>
    <version>5.4.1</version>
</dependency>

应用启动时通过 YAML 告诉 ShardingSphere 怎么接管:

yaml 复制代码
spring:
  datasource:
    # 直接指向 ShardingSphere 的配置路径,由它内部构建 DataSource
    url: jdbc:shardingsphere:classpath:encrypt-config.yaml
    driver-class-name: org.apache.shardingsphere.driver.ShardingSphereDriver

核心规则定义在 encrypt-config.yaml

yaml 复制代码
dataSources:
  primary_ds:
    url: jdbc:mysql://127.0.0.1:3306/biz_db
    username: root
    password: xxx
    connectionTimeoutMilliseconds: 30000

rules:
  - !ENCRYPT
    tables:
      t_user:
        columns:
          phone:
            cipherColumn: phone_cipher
            plainColumn: phone_plain
            assistedQueryColumn: phone_hash
            encryptorName: aes_encryptor
            maskGeneratorName: phone_mask
          id_card:
            cipherColumn: id_card_cipher
            encryptorName: sm4_encryptor
    encryptors:
      aes_encryptor:
        type: AES
        props:
          aes-key-value: "yourBase64Key"
      sm4_encryptor:
        type: SM4
    maskGenerators:
      phone_mask:
        type: MASK
        props:
          mask-before: 3
          mask-after: 4
          replace-char: "*"

plainColumn 现在主要留作平滑迁移过渡,日常查询默认走密文。辅助列 assisted_query_column 别瞎配,只给高频检索字段开,否则索引膨胀严重。

自定义算法实现 EncryptAlgorithm 接口即可,实现 initencryptdecryptgetType 方法,然后在 META-INF/services/org.apache.shardingsphere.encrypt.api.spi.EncryptAlgorithm 文件里注册全限定类名。多数据源场景直接在 dataSources 块里并列写,ShardingSphere 内部自己管路由和连接池。监控方面,接上 Spring Boot Actuator 后,重点关注加解密耗时分布和 SQL 改写命中率,配合 Prometheus 抓指标,上线前摸清瓶颈在哪。


4. 实战中容易踩的坑

模糊查询兼容性 :密文直接 LIKE 是废的。官方推 assistedQueryColumn,等值查询用哈希匹配没问题,但前缀模糊匹配很恶心。实际落地时,我们要么把手机号按段拆分存多个辅助字段,要么干脆把检索层拆到 Elasticsearch,MySQL 只负责存密文和精确回查。辅助列会吃额外的存储和索引空间,低频字段千万别开。

联合索引优化:如果联合索引里带了脱敏列,引擎会自动改写索引键。建议联合索引的第一列别搞脱敏,不然索引前缀选择性暴跌,优化器容易走全表扫描。能走覆盖索引的就别回表,每次回表都多一次解密开销,积少成多就是性能瓶颈。

分库分表联动 :Encrypt 和 Sharding 能混用,但执行顺序是写死的:先分片路由,再加密改写。分片键绝对不能加密,否则路由直接瘫痪。按 user_id 分表,按 phone 加密存储这种组合很常见,只要注意路由条件里别混入密文列就行。

动态策略切换:靠 Nacos/Apollo 推配置,通过 ShardingSphere 的 Governance API 触发规则热更新。新建立的连接池会立刻加载新规则,旧连接不受影响。生产上要求不停机发布,这套流程跑过只要配置格式校验严格,基本无缝切换。注意别在业务高峰期推送大段规则变更,避免配置中心抖动。


5. 性能与安全怎么取舍

算法选型别迷信非对称。RSA 开销太大,只适合密钥分发。生产上基本就 AES-256-GCM 和国密 SM4-CBC 二选一。在 JDK 17 + Spring Boot 3.x 环境下,批量 10 万条插入压测下来,单次加解密耗时能压在 0.05ms 以内,整体 QPS 掉大概 3%~5%,P99 延迟多 2~5ms。对绝大多数业务线来说,这个损耗完全在可接受范围内。

缓存是重灾区。Redis 里千万别直接扔明文对象,Key 最好用辅助列的 Hash 值生成。如果必须缓存完整用户信息,序列化前先把敏感字段截断或打码。TTL 务必设短,控制在 5 分钟以内,权限变更或角色降权时直接清缓存,别留越权访问的口子。本地缓存同理,Caffeine 的淘汰策略要跟上脱敏策略的刷新频率。


6. 历史数据迁移与生产避坑

存量数据上密文最头疼。停机全量跑 UPDATE 风险太大,我们一般用"双写+异步洗数据+灰度切读"的三步法。第一阶段应用层双写,明文列和密文列同时落库(ShardingSphere 配置 plainColumn 会自动保留);第二阶段起个 Flink 或 DataX 任务,慢慢把历史明文洗成密文,边洗边做校验和比对;第三阶段通过配置中心把读请求强制指向密文列,观察一周业务指标和慢查询无异常后,再异步清理明文列。

规则冲突很常见。多表同名列必须按 库.表.列 维度精确绑定,别用全局通配。JOIN 查询时,如果两边表都有脱敏字段,ShardingSphere 会各自改写 SQL,这时候 SQL 里的列别名最好写死,不然结果集映射容易错位。MyBatis 的 resultMap 不用动,但别在里面手写类型转换或字段过滤,全交给代理层处理,否则会出现双重脱敏或明文泄露。

审计日志这块必须上心。开 SQL 日志一定要过滤掉参数里的敏感值打印,不然等于自己造泄露源。业务审计单独建表,记录操作人角色、查询目标、时间戳和是否走了掩码。应用日志最好配个 Logback/Log4j2 的正则过滤器,把 phone=138xxxx 这种模式自动拦截打码,安全日志本身也得脱敏,别让日志系统变成突破口。


7. 写在最后

数据脱敏现在不是锦上添花的加分项,是架构的底线。把这套东西下沉到 JDBC 代理层,业务开发确实能少写一堆胶水代码,但前期配置梳理、历史数据迁移和规则治理的成本得有人扛。隐私保护左移是趋势,别等出了事再打补丁。

现阶段,"JDBC 透明代理 + 配置中心热更 + 灰度迁移机制"依然是落地最稳、维护成本最低的组合。随着隐私计算和联邦查询的成熟,未来可能连密文都不用解就能做跨域分析,但眼下先把地基打牢,把合规红线和系统性能这杆秤端平,比什么花哨的架构都实在。立项初期就把脱敏规范写进设计文档里,安全是设计出来的,不是上线后缝补出来的。


🎁 福利时间

如果你正在备战面试或者想要学习其他知识,给大家推荐一个宝藏知识库,作者整理了一些列 Java 程序员需要掌握的核心知识,有需要的自取不谢。

知识库地址:https://farerboy.com/


相关推荐
Zane19941 小时前
只改一个方向的引用,循环引用就能立刻被回收?一文讲透 weakref 弱引用
后端·python
长大19881 小时前
窗口函数用不好反而更慢?SQL Server中OVER子句的4个性能陷阱
后端
大黄评测1 小时前
MERGE语句有Bug?SQL Server官方不推荐的背后真相与替代方案
后端
长大19881 小时前
TempDB 爆满导致系统卡死?SQL Server TempDB 瓶颈诊断与根治方案
后端
lichenyang4531 小时前
从「房间」到「实时通知」:用 NestJS + Socket.IO 实现团队邀请的完整工程实践
前端·后端
大黄评测1 小时前
为什么你的SQL查询慢?这7个执行计划陷阱90%的人都踩过
后端
沙盘客1 小时前
AFSIM 示例解读(09)· 传感器全家桶 sensor_demos(下):ESM / SAR / 被动测向
c++·经验分享·后端
叫我Paul就好1 小时前
Spring 为何没有在 Java之外的地方存在?
java·后端·spring
掘金酱2 小时前
TRAE Work 实战帮征文 | 获奖名单公示
前端·人工智能·后端