数据脱敏和敏感词过滤

摘要:接口在返回一些敏感隐私数据时,是需要进行脱敏处理,通常的手段是使用 * 隐藏一部分数据。

目录

[TOC]

数据脱敏

数据脱敏:指对某些敏感信息通过脱敏规则进行数据的变形,实现敏感隐私数据的可靠保护。

  • 例如,对于身份证号码,可以使用掩码算法(masking)将前几位数字保留,其他位用 “X” 或 “*” 代替;
  • 对于姓名,可以使用伪造算法(pseudonymization),将真实姓名替换成随机生成的假名。

常用脱敏规则

常用脱敏规则是为了保护敏感数据的安全性,在处理和存储敏感数据时对其进行变换或修改。

下面是几种常见的脱敏规则:

  • 替换(常用):将敏感数据中的特定字符或字符序列替换为其他字符。例如,将信用卡号中的中间几位数字替换为星号(*)或其他字符。
  • 删除:将敏感数据中的部分内容随机删除。比如,将电话号码的随机 3 位数字进行删除。
  • 重排:将原始数据中的某些字符或字段的顺序打乱。例如,将身份证号码的随机位交错互换。
  • 加噪:在数据中注入一些误差或者噪音,达到对数据脱敏的效果。例如,在敏感数据中添加一些随机生成的字符。
  • 加密(常用):使用加密算法将敏感数据转换为密文。例如,将银行卡号用 MD5 或 SHA-256 等哈希函数进行散列。常见加密算法总结可以参考这篇文章:https://javaguide.cn/system-design/security/encryption-algorithms.html

常用脱敏工具

  1. Hutool:一个 Java 基础工具类,对文件、流、加密解密、转码、正则、线程、XML 等 JDK 方法进行封装,组成各种 Util 工具类,同时提供以下组件。Hutool 脱敏是通过 * 来代替敏感信息的,具体实现是在 StrUtil.hide 方法中。
  2. Apache ShardingSphere:是一套开源的分布式数据库中间件解决方案组成的生态圈,由 Sharding-JDBC、Sharding-Proxy 和 Sharding-Sidecar(计划中)这 3 款相互独立的产品组成。 均提供标准化的数据分片、分布式事务和数据库治理功能 。
    1. 下面存在一个数据脱敏模块,此模块集成的常用的数据脱敏的功能。其基本原理是对用户输入的 SQL 进行解析拦截,并依靠用户的脱敏配置进行 SQL 的改写,从而实现对原文字段的加密及加密字段的解密。最终实现对用户无感的加解密存储、查询。
    2. 通过 Apache ShardingSphere 可以自动化&透明化数据脱敏过程,用户无需关注脱敏中间实现细节。并且,提供了多种内置、第三方(AKS)的脱敏策略,用户仅需简单配置即可使用。
  3. FastJSON:是一个很常用的 Spring Web Restful 接口序列化的工具。实现数据脱敏的方式主要有两种:
    • 基于注解 @JSONField 实现:需要自定义一个用于脱敏的序列化的类,然后在需要脱敏的字段上通过 @JSONField 中的 serializeUsing 指定为我们自定义的序列化类型即可。
    • 基于序列化过滤器:需要实现 ValueFilter 接口,重写 process 方法完成自定义脱敏,然后在 JSON 转换时使用自定义的转换策略。
  4. Mybatis-Mate:是为 MyBatis-Plus 提供的企业级模块,旨在更敏捷优雅处理数据。不过,使用之前需要配置授权码(付费)。Mybatis-Mate 支持敏感词脱敏,内置手机号、邮箱、银行卡号等 9 种常用脱敏规则。
  5. MyBatis-Flex:类似于 MybatisPlus,MyBatis-Flex 也是一个 MyBatis 增强框架。MyBatis-Flex 同样提供了数据脱敏功能,并且是可以免费使用的。MyBatis-Flex 提供了 @ColumnMask() 注解,以及内置的 9 种脱敏规则,开箱即用:

数据脱敏组件

接口在返回一些敏感隐私数据时,是需要进行脱敏处理,通常的手段是使用 * 隐藏一部分数据。例如说:

类型 原始数据 脱敏数据
手机 13248765917 132**5917
身份证 530321199204074611 530321****11
银行卡 9988002866797031 998800****31

脱敏组件

脱敏组件,由 yudao-spring-boot-starter-webdesensitize (opens new window)包实现,基于 Jackson 拓展,只需要在字段上添加脱敏注解,即可实现对该字段进行脱敏。

使用步骤如下:

在字段上添加脱敏注解。如下所示:

1
2
3
4
5
6
7
@Data
public static class DesensitizeDemo {

    @MobileDesensitize // 手机号的脱敏注解
    private String phoneNumber;

}

内置脱敏注解

根据不同的脱敏处理方式,项目内置了两类脱敏注解:正则脱敏、滑块脱敏。

regex 正则脱敏

@RegexDesensitize 注解

正则脱敏注解 @RegexDesensitize:根据正则表达式,将原始数据进行替换处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
public @interface RegexDesensitize {

    /**
     * 匹配的正则表达式(默认匹配所有)
     */
    String regex() default "^[\s\S]*$";

    /**
     * 替换规则,会将匹配到的字符串全部替换成 replacer
     */
    String replacer() default "******";

}

例如说:regex=123; replacer=****** 表示将 123 替换为 ******

  • 原始字符串 123456789
  • 脱敏后字符串 **456789
其它正则脱敏注解

项目内置了其它基于正则脱敏的常用注解,无需手动填写 regexreplacer 属性,更加方便。例如说:

1
2
3
4
5
6
7
@Data
public static class DesensitizeDemo {
    
    @EmailDesensitize
    private String email;

}

所有注解如下:

注解 原始数据 脱敏数据
@EmailDesensitize example@gmail.com e**@gmail.com

slider 滑块脱敏

@SliderDesensitize 注解

滑块脱敏注解 @SliderDesensitize:根据设置的左右明文字符长度,中间部分全部替换为 *

例如说:prefixKeep=3; suffixKeep=4; replacer=* 表示前 3 后 4 保持明文,中间都替换成 *

  • 原始字符串 13248765917
  • 脱敏后字符串 132**5917
其它滑块脱敏注解

项目内置了其它基于滑块脱敏的常用注解,无需手动填写 prefixKeepsuffixKeepreplacer 属性,更加方便。例如说:

1
2
3
4
5
6
7
@Data
public static class DesensitizeDemo {
    
    @MobileDesensitize
    private String mobile;

}

所有注解如下:

注解 原始数据 脱敏数据
@MobileDesensitize 13248765917 132**5917
@FixedPhoneDesensitize 01086551122 0108*****22
@BankCardDesensitize 9988002866797031 998800****31
@PasswordDesensitize 123456 **
@CarLicenseDesensitize 粤A66666 粤A6***6
@ChineseNameDesensitize 刘子豪 刘**
@IdCardDesensitize 530321199204074611 530321****11

自定义脱敏注解

如果内置的注解无法满足你的需求,只需要自定义一个脱敏注解,并实现它的脱敏处理器即可。

例如说,我们要实现一个新的脱敏处理方法,将编号使用 MD5 或 SHA256 计算后返回。步骤如下:

① 创建 @DigestDesensitize 注解,使用 @DesensitizeBy 标记它使用的处理器。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy;
import cn.iocoder.yudao.framework.desensitize.core.handler.DigestHandler;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;

import java.lang.annotation.*;

@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@DesensitizeBy(handler = DigestHandler.class) // 使用 @DesensitizeBy 设置它的处理器
public @interface DigestDesensitize {

    /**
     * 摘要算法,例如说:MD5、SHA256
     */
    String algorithm() default "md5";

}

② 创建 DigestHandler 类,实现 DesensitizationHandler 接口,将编号使用 MD5 或 SHA256 处理。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
import cn.hutool.crypto.digest.DigestUtil;
import cn.iocoder.yudao.framework.desensitize.core.annotation.DigestDesensitize;
import cn.iocoder.yudao.framework.desensitize.core.base.handler.DesensitizationHandler;

public class DigestHandler implements DesensitizationHandler<DigestDesensitize> {

    @Override
    public String desensitize(String origin, DigestDesensitize annotation) {
        String algorithm = annotation.algorithm();
        return DigestUtil.digester(algorithm).digestHex(origin);
    }

}

友情提示:

① 如果自定义的是基于正则脱敏的注解,可选择继承 AbstractRegexDesensitizationHandler 处理器。

① 如果自定义的是基于滑块脱敏的注解,可选择继承 AbstractSliderDesensitizationHandler 处理器。

③ 在需要使用的字段上,添加 @DigestDesensitize 注解。示例代码如下:

1
2
3
4
5
6
7
@Data
public static class DesensitizeDemo {
    
    @DigestDesensitize
    private String email;

}

脱敏工具类

Hutool 提供了 DesensitizedUtil (opens new window)脱敏工具类,支持用户 ID、 中文名、身份证、座机号、手机号、 地址、电子邮件、 密码、车牌、银行卡号的脱敏处理。

使用方式,代码如下:

1
2
3
4
5
6
7
8
9
10
DesensitizedUtil.desensitized("100", DesensitizedUtils.DesensitizedType.USER_ID)) =  "0"
DesensitizedUtil.desensitized("段正淳", DesensitizedUtils.DesensitizedType.CHINESE_NAME)) = "段**"
DesensitizedUtil.desensitized("51343620000320711X", DesensitizedUtils.DesensitizedType.ID_CARD)) = "5***************1X"
DesensitizedUtil.desensitized("09157518479", DesensitizedUtils.DesensitizedType.FIXED_PHONE)) = "0915*****79"
DesensitizedUtil.desensitized("18049531999", DesensitizedUtils.DesensitizedType.MOBILE_PHONE)) = "180****1999"
DesensitizedUtil.desensitized("北京市海淀区马连洼街道289号", DesensitizedUtils.DesensitizedType.ADDRESS)) = "北京市海淀区马********"
DesensitizedUtil.desensitized("duandazhi-jack@gmail.com.cn", DesensitizedUtils.DesensitizedType.EMAIL)) = "d*************@gmail.com.cn"
DesensitizedUtil.desensitized("1234567890", DesensitizedUtils.DesensitizedType.PASSWORD)) = "**********"
DesensitizedUtil.desensitized("苏D40000", DesensitizedUtils.DesensitizedType.CAR_LICENSE)) = "苏D4***0"
DesensitizedUtil.desensitized("11011111222233333256", DesensitizedUtils.DesensitizedType.BANK_CARD)) = "1101 **** **** **** 3256"

适合场景,逻辑里需要直接对某个变量进行脱敏处理,然后打印 logger 日志,或者存储到数据库中。

基于角色判断

例如说:只有超管可以查看完整手机号,其它用户只能查看脱敏后的手机号。

1
2
3
4
// XXXVO.java

@EmailDesensitize(disable = "@ss.hasRole('super_admin')")
private String mobile;

基于权限判断

再例如说:只有拥有 user:info:query-mobile 权限的用户,才能查看完整手机号。

1
2
3
// XXXVO.java

@EmailDesensitize(disable = "!@ss.hasPermission('user:info:query-mobile')")

敏感词过滤

系统需要对用户输入的文本进行敏感词过滤如色情、政治、暴力相关的词汇。

敏感词过滤用的使用比较多的 Trie 树算法DFA 算法

算法

Trie 树

Trie 树 也称为字典树、单词查找树,哈希树的一种变种,通常被用于字符串匹配,用来解决在一组字符串集合中快速查找某个字符串的问题。像浏览器搜索的关键词提示就可以基于 Trie 树来做的。

Trie 树的核心原理其实很简单,就是通过公共前缀来提高字符串匹配效率。

AC 自动机

Aho-Corasick(AC)自动机是一种建立在 Trie 树上的一种改进算法,是一种多模式匹配算法,由贝尔实验室的研究人员 Alfred V. Aho 和 Margaret J.Corasick 发明。

AC 自动机算法使用 Trie 树来存放模式串的前缀,通过失败匹配指针(失配指针)来处理匹配失败的跳转。关于 AC 自动机的详细介绍,可以查看这篇文章:[地铁十分钟 AC 自动机](https://zhuanlan.zhihu.com/p/146369212)。

如果使用上面提到的 DAT 来表示 AC 自动机 ,就可以兼顾两者的优点,得到一种高效的多模式匹配算法。

DFA

DFA(Deterministic Finite Automata)即确定有穷自动机,与之对应的是 NFA(Non-Deterministic Finite Automata,不确定有穷自动机)。

Hutool 提供了 DFA 算法的实现:

Hutool 的 DFA 算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
WordTree wordTree = new WordTree();
wordTree.addWord("大");
wordTree.addWord("大憨憨");
wordTree.addWord("憨憨");
String text = "那人真是个大憨憨!";
// 获得第一个匹配的关键字
String matchStr = wordTree.match(text);
System.out.println(matchStr);
// 标准匹配,匹配到最短关键词,并跳过已经匹配的关键词
List<String> matchStrList = wordTree.matchAll(text, -1, false, false);
System.out.println(matchStrList);
//匹配到最长关键词,跳过已经匹配的关键词
List<String> matchStrList2 = wordTree.matchAll(text, -1, false, true);
System.out.println(matchStrList2);

敏感词 API 接口

SensitiveWordApi (opens new window)提供了敏感词的 API 接口,可以在任意地方使用。方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public interface SensitiveWordApi {

    /**
     * 获得文本所包含的不合法的敏感词数组
     *
     * @param text 文本
     * @param tags 标签数组
     * @return 不合法的敏感词数组
     */
    List<String> validateText(String text, List<String> tags);

    /**
     * 判断文本是否包含敏感词
     *
     * @param text 文本
     * @param tags 表述数组
     * @return 是否包含
     */
    boolean isTextValid(String text, List<String> tags);

}

使用步骤如下:

① 在需要使用的 yudao-module-*-server 模块的 pom.xml 中,引入 yudao-module-system-api 依赖。代码如下:

1
2
3
4
5
<dependency>
    <groupId>cn.iocoder.cloud</groupId>
    <artifactId>yudao-module-system-api</artifactId>
    <version>${revision}</version>
</dependency>

② 在该 yudao-module-*-server 模块的 RpcConfiguration (opens new window)配置类,注入 SensitiveWordApi 接口。代码如下:

1
2
3
4
@Configuration(proxyBeanMethods = false)
@EnableFeignClients(clients = {SensitiveWordApi.class.class})
public class RpcConfiguration {
}

注入 SensitiveWordApi Bean,调用对应的方法即可。例如说:

1
2
3
4
5
6
7
8
9
10
11
12
@Service
public class DemoService {

    @Resource
    private SensitiveWordApi sensitiveWordApi;

    public void demo() {
        sensitiveWordApi.validateText("你是白痴吗", Collections.singletonList("测试"));
        sensitiveWordApi.isTextValid("你是白痴吗", Collections.singletonList("蔬菜"));
    }

}

##

0%