
《个保法》和《数安法》跑完一轮审计后,敏感字段不落密文或掩码基本过不了关。合规压力下来了,但业务还得跑,不同场景对脱敏的诉求压根不是一回事。
开发测试环境最怕生产数据泄露,通常直接做静态替换或泛化,数据最好跟生产物理隔离。数据分析那边要的是数据分布特征,不能搞成乱码影响聚合,一般得上同态加密或保序哈希,保证统计准就行。客服和运营查单子最折腾,既要快速定位用户,又不能全量暴露明文,只能靠动态掩码,权限一收就变 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 接口即可,实现 init、encrypt、decrypt 和 getType 方法,然后在 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/
