数据脱敏
这篇解决什么
接口 JSON 里常带着手机号、邮箱、身份证。读完能在 RespVO 上挂 @MobileDesensitize / @IdCardDesensitize,改滑块或正则规则,以及按角色、权限跳过脱敏。
默认端口 48080,管理端前缀 /admin-api。脱敏只改写出的字符串,不改库。
示意图:Controller 仍拿明文 VO;Jackson 写 JSON 时才替换。
只挡响应,不改库
注解走 @JsonSerialize,只在序列化时生效。AdminUserDO、数据库、日志里的原值不会被改。ExcelUtils.write 直接读字段,也不走这条链路,导出仍是明文。
组件位置
| 名称 | 说明 | 仓库路径 |
|---|---|---|
@DesensitizeBy | 元注解。指定处理器,并挂上 Jackson 序列化器 | ruoyi-office/yudao-framework/yudao-spring-boot-starter-web/.../desensitize/core/base/annotation/DesensitizeBy.java |
StringDesensitizeSerializer | 读字段上的脱敏注解,调用 handler | 同模块 .../base/serializer/StringDesensitizeSerializer.java |
DesensitizationHandler | desensitize(origin, annotation);disable 为 true 则原样返回 | .../base/handler/DesensitizationHandler.java |
| 内置注解 | 滑块、正则两套,字段上直接用 | .../desensitize/core/slider/annotation/、.../regex/annotation/ |
| 单测样例 | JsonUtils.toJsonString 后再反序列化,核对替换结果 | .../src/test/java/.../desensitize/core/DesensitizeTest.java |
@DesensitizeBy 本身带 @JacksonAnnotationsInside 和 @JsonSerialize(using = StringDesensitizeSerializer.class)。业务注解再套一层即可。
挂到用户 VO
UserController 的 /get、/page 返回 UserRespVO。类里已有 mobile、email,加上注解后,管理端列表和详情的 JSON 就会挡住明文:
@Schema(description = "用户邮箱", example = "admin@ruoyioffice.com")
@ExcelProperty("用户邮箱")
@EmailDesensitize
private String email;
@Schema(description = "手机号码", example = "15601691300")
@ExcelProperty("手机号码")
@MobileDesensitize
private String mobile;| 名称 | 说明 | 仓库路径 |
|---|---|---|
UserRespVO | 用户出参,含手机、邮箱 | ruoyi-office/yudao-module-system/.../controller/admin/user/vo/user/UserRespVO.java |
UserController | @RequestMapping("/system/user") | ruoyi-office/yudao-module-system/.../controller/admin/user/UserController.java |
EmployeeRespVO | 员工出参,含身份证、手机 | ruoyi-office/yudao-module-hrm/.../controller/admin/employee/vo/EmployeeRespVO.java |
身份证不在用户 VO 上。人力资源 EmployeeRespVO.idCard 挂 @IdCardDesensitize 即可。
先用内置注解
手机、邮箱、身份证、银行卡、车牌、中文名、密码都有现成注解。默认前后缀够用时,不必再写 handler。
滑块脱敏
@SliderDesensitize 按 prefixKeep、suffixKeep 留明文,中间用 replacer 填满。字符串不够长时,整段替换。
13248765917 配前 3 后 4、replacer=*,写出 132****5917。
| 名称 | 说明 | 仓库路径 |
|---|---|---|
@SliderDesensitize | 通用滑块。可改前后缀和替换符 | .../slider/annotation/SliderDesensitize.java |
@MobileDesensitize | 默认前 3 后 4。13248765917 → 132****5917 | MobileDesensitize.java |
@IdCardDesensitize | 默认前 6 后 2。530321199204074611 → 530321**********11 | IdCardDesensitize.java |
@FixedPhoneDesensitize | 默认前 4 后 2。01086551122 → 0108*****22 | FixedPhoneDesensitize.java |
@BankCardDesensitize | 默认前 6 后 2。9988002866797031 → 998800********31 | BankCardDesensitize.java |
@PasswordDesensitize | 前后缀默认 0,整段替换。123456 → ****** | PasswordDesensitize.java |
@CarLicenseDesensitize | 默认前 3 后 1。粤A66666 → 粤A6***6 | CarLicenseDesensitize.java |
@ChineseNameDesensitize | 默认前 1 后 0。刘子豪 → 刘** | ChineseNameDesensitize.java |
AbstractSliderDesensitizationHandler | 读注解属性,拼中间替换段 | .../slider/handler/AbstractSliderDesensitizationHandler.java |
前后缀不够用时,直接改注解参数:
@SliderDesensitize(prefixKeep = 3, suffixKeep = 3)
private String slider2; // ABCDEFG → ABC*EFGprefixKeep = 10 且原文更短时,整段变成 *******。替换符也可以改成 #。
正则脱敏
@RegexDesensitize 对原文做 replaceAll(regex, replacer)。默认 regex 匹配整段,replacer 为 ******。
regex=123、replacer=****** 时,123456789 写成 ******456789。
| 名称 | 说明 | 仓库路径 |
|---|---|---|
@RegexDesensitize | 通用正则。可改 regex、replacer | .../regex/annotation/RegexDesensitize.java |
@EmailDesensitize | 默认 (^.)[^@]*(@.*$),替换 $1****$2。example@gmail.com → e****@gmail.com | EmailDesensitize.java |
AbstractRegexDesensitizationHandler | 先算 disable,再 replaceAll | .../regex/handler/AbstractRegexDesensitizationHandler.java |
邮箱已经有专用注解,一般不用自己写正则。
按角色或权限跳过
每条内置注解都有 disable,值是 Spring EL。表达式为 true 时返回原文。ss Bean 就是权限篇里的 SecurityFrameworkService。
超管看完整手机号:
@MobileDesensitize(disable = "@ss.hasRole('super_admin')")
private String mobile;按权限标识放开:
@MobileDesensitize(disable = "@ss.hasPermission('system:user:query')")
private String mobile;| 名称 | 说明 | 仓库路径 |
|---|---|---|
disable | 空串不解析,继续脱敏 | 各 *Desensitize 注解 |
@ss.hasRole | 角色 code,超管是 super_admin | RoleCodeEnum.SUPER_ADMIN |
@ss.hasPermission | 菜单权限标识 | 例如 system:user:query |
SpringExpressionUtils | 用 Bean 工厂解析 EL | ruoyi-office/yudao-framework/yudao-common/.../SpringExpressionUtils.java |
disable 为 true 表示「这次不脱敏」。要挡住某人,不要把表达式写反。
自定义注解
内置规则盖不住时,自己写一个字段注解,再用 @DesensitizeBy 指定处理器。
@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@DesensitizeBy(handler = AddressHandler.class)
public @interface Address {
String replacer() default "*";
}处理器实现 DesensitizationHandler<Address>,或继承滑块 / 正则抽象类,复用 disable 和拼接逻辑。测试包里的 Address / AddressHandler 是完整样例。
| 名称 | 说明 | 仓库路径 |
|---|---|---|
@DesensitizeBy | 声明 handler,并接入 Jackson | 见上文 |
Address | 自定义注解示例 | .../src/test/java/.../desensitize/core/annotation/Address.java |
AddressHandler | 对应处理器 | .../src/test/java/.../desensitize/core/handler/AddressHandler.java |
业务注解放到对应模块即可,不必塞进 Web Starter。
代码里直接脱敏
接口出参走注解。日志、第三方报文要当场挡一段字符串,用 Hutool DesensitizedUtil。社交模块上传小程序发货信息时,收件人手机号就是这样处理的:
DesensitizedUtil.mobilePhone(reqDTO.getReceiverContact())| 名称 | 说明 | 仓库路径 |
|---|---|---|
DesensitizedUtil | 手机、身份证、邮箱、银行卡等工具方法 | Hutool |
| 发货收件人 | 调用前先挡手机号 | ruoyi-office/yudao-module-system/.../service/social/SocialClientServiceImpl.java |
这段调用写在 Service 里,和 Jackson 注解无关。不要把它写回 DO 再 update,否则库里会变成掩码。
配置与操作
在 RespVO 字段加 @MobileDesensitize 等,只影响 JSON 输出。不要把掩码写回数据库。
开启见 框架层。
