From a3be4eb4b84724d179472db49692d74ef7b89f06 Mon Sep 17 00:00:00 2001 From: Eric Date: Fri, 12 Apr 2024 11:14:42 +0800 Subject: [PATCH] =?UTF-8?q?:sparkles:=20DesensitizedUtil=E7=AE=80=E5=8C=96?= =?UTF-8?q?=E4=BD=BF=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../desensitize/AnnotationHandlerHolder.java | 17 +- .../DesensitizationHandlerHolder.java | 11 + .../handler/IPDesensitizationHandler.java | 47 +++ .../handler/RuleDesensitizationHandler.java | 95 +++++ .../handler/SlideDesensitizationHandler.java | 46 ++- .../json/annotation/JsonRuleDesensitize.java | 56 +++ .../json/annotation/JsonSlideDesensitize.java | 5 + .../desensitize/rule/DesensitizeRule.java | 112 ++++++ .../desensitize/util/DesensitizedUtil.java | 348 ++++++++++++++++++ ...itize.handler.SimpleDesensitizationHandler | 1 + .../ballcat/desensite/DesensitisedTest.java | 39 +- .../desensite/DesensitisedUtilTest.java | 62 ++++ .../desensite/DesensitizationUser.java | 13 + .../custom/CustomDesensitisedTest.java | 2 +- 14 files changed, 836 insertions(+), 18 deletions(-) create mode 100644 desensitize/ballcat-desensitize/src/main/java/org/ballcat/desensitize/handler/IPDesensitizationHandler.java create mode 100644 desensitize/ballcat-desensitize/src/main/java/org/ballcat/desensitize/handler/RuleDesensitizationHandler.java create mode 100644 desensitize/ballcat-desensitize/src/main/java/org/ballcat/desensitize/json/annotation/JsonRuleDesensitize.java create mode 100644 desensitize/ballcat-desensitize/src/main/java/org/ballcat/desensitize/rule/DesensitizeRule.java create mode 100644 desensitize/ballcat-desensitize/src/main/java/org/ballcat/desensitize/util/DesensitizedUtil.java create mode 100644 desensitize/ballcat-desensitize/src/test/java/org/ballcat/desensite/DesensitisedUtilTest.java diff --git a/desensitize/ballcat-desensitize/src/main/java/org/ballcat/desensitize/AnnotationHandlerHolder.java b/desensitize/ballcat-desensitize/src/main/java/org/ballcat/desensitize/AnnotationHandlerHolder.java index 4062e5142..fdd3b3388 100644 --- a/desensitize/ballcat-desensitize/src/main/java/org/ballcat/desensitize/AnnotationHandlerHolder.java +++ b/desensitize/ballcat-desensitize/src/main/java/org/ballcat/desensitize/AnnotationHandlerHolder.java @@ -25,9 +25,11 @@ import org.ballcat.desensitize.enums.SlideDesensitizationTypeEnum; import org.ballcat.desensitize.functions.DesensitizeFunction; import org.ballcat.desensitize.handler.RegexDesensitizationHandler; +import org.ballcat.desensitize.handler.RuleDesensitizationHandler; import org.ballcat.desensitize.handler.SimpleDesensitizationHandler; import org.ballcat.desensitize.handler.SlideDesensitizationHandler; import org.ballcat.desensitize.json.annotation.JsonRegexDesensitize; +import org.ballcat.desensitize.json.annotation.JsonRuleDesensitize; import org.ballcat.desensitize.json.annotation.JsonSimpleDesensitize; import org.ballcat.desensitize.json.annotation.JsonSlideDesensitize; @@ -77,9 +79,18 @@ private AnnotationHandlerHolder() { SlideDesensitizationTypeEnum type = an.type(); SlideDesensitizationHandler slideDesensitizationHandler = DesensitizationHandlerHolder .getSlideDesensitizationHandler(); - return SlideDesensitizationTypeEnum.CUSTOM.equals(type) ? slideDesensitizationHandler.handle(value, - an.leftPlainTextLen(), an.rightPlainTextLen(), an.maskString()) - : slideDesensitizationHandler.handle(value, type); + return SlideDesensitizationTypeEnum.CUSTOM.equals(type) + ? slideDesensitizationHandler.handle(value, an.leftPlainTextLen(), an.rightPlainTextLen(), + an.maskString(), an.reverse()) + : slideDesensitizationHandler.handle(value, type, an.reverse()); + }); + + this.annotationHandlers.put(JsonRuleDesensitize.class, (annotation, value) -> { + // 规则类型脱敏处理 + JsonRuleDesensitize an = (JsonRuleDesensitize) annotation; + RuleDesensitizationHandler ruleDesensitizationHandler = DesensitizationHandlerHolder + .getRuleDesensitizationHandler(); + return ruleDesensitizationHandler.handle(value, an.maskChar(), an.reverse(), an.rule()); }); } diff --git a/desensitize/ballcat-desensitize/src/main/java/org/ballcat/desensitize/DesensitizationHandlerHolder.java b/desensitize/ballcat-desensitize/src/main/java/org/ballcat/desensitize/DesensitizationHandlerHolder.java index 3b8fb50d8..259a3553e 100644 --- a/desensitize/ballcat-desensitize/src/main/java/org/ballcat/desensitize/DesensitizationHandlerHolder.java +++ b/desensitize/ballcat-desensitize/src/main/java/org/ballcat/desensitize/DesensitizationHandlerHolder.java @@ -22,6 +22,7 @@ import org.ballcat.desensitize.handler.DesensitizationHandler; import org.ballcat.desensitize.handler.RegexDesensitizationHandler; +import org.ballcat.desensitize.handler.RuleDesensitizationHandler; import org.ballcat.desensitize.handler.SimpleDesensitizationHandler; import org.ballcat.desensitize.handler.SlideDesensitizationHandler; @@ -47,6 +48,8 @@ private DesensitizationHandlerHolder() { this.desensitizationHandlerMap.put(SlideDesensitizationHandler.class, new SlideDesensitizationHandler()); // 正则脱敏处理器 this.desensitizationHandlerMap.put(RegexDesensitizationHandler.class, new RegexDesensitizationHandler()); + // 基于规则脱敏处理器 + this.desensitizationHandlerMap.put(RuleDesensitizationHandler.class, new RuleDesensitizationHandler()); // SPI 加载所有的 Simple脱敏类型处理 ServiceLoader loadedDrivers = ServiceLoader .load(SimpleDesensitizationHandler.class); @@ -79,6 +82,14 @@ public static SlideDesensitizationHandler getSlideDesensitizationHandler() { return (SlideDesensitizationHandler) INSTANCE.desensitizationHandlerMap.get(SlideDesensitizationHandler.class); } + /** + * 获取 RuleDesensitizationHandler + * @return 处理器实例 + */ + public static RuleDesensitizationHandler getRuleDesensitizationHandler() { + return (RuleDesensitizationHandler) INSTANCE.desensitizationHandlerMap.get(RuleDesensitizationHandler.class); + } + /** * 获取指定的 SimpleDesensitizationHandler * @param handlerClass SimpleDesensitizationHandler的实现类 diff --git a/desensitize/ballcat-desensitize/src/main/java/org/ballcat/desensitize/handler/IPDesensitizationHandler.java b/desensitize/ballcat-desensitize/src/main/java/org/ballcat/desensitize/handler/IPDesensitizationHandler.java new file mode 100644 index 000000000..c1bda3feb --- /dev/null +++ b/desensitize/ballcat-desensitize/src/main/java/org/ballcat/desensitize/handler/IPDesensitizationHandler.java @@ -0,0 +1,47 @@ +/* + * Copyright 2023-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.ballcat.desensitize.handler; + +/** + * 【IP】IPv4返回10.*.*.*,IPv6返回2001:*:*:*:*:*:*:* + * + * @author evil0th Create on 2024/4/12 + */ +public class IPDesensitizationHandler implements SimpleDesensitizationHandler { + + /** + * 脱敏处理 + * @param origin 原始IP + * @return 脱敏处理后的IP + */ + @Override + public String handle(String origin) { + if (null == origin) { + return null; + } + int index = origin.indexOf("."); + if (index > 0) { + return origin.substring(0, index) + ".*.*.*"; + } + index = origin.indexOf(":"); + if (index > 0) { + return origin.substring(0, index) + ":*:*:*:*:*:*:*"; + } + return origin; + } + +} diff --git a/desensitize/ballcat-desensitize/src/main/java/org/ballcat/desensitize/handler/RuleDesensitizationHandler.java b/desensitize/ballcat-desensitize/src/main/java/org/ballcat/desensitize/handler/RuleDesensitizationHandler.java new file mode 100644 index 000000000..0206bd04a --- /dev/null +++ b/desensitize/ballcat-desensitize/src/main/java/org/ballcat/desensitize/handler/RuleDesensitizationHandler.java @@ -0,0 +1,95 @@ +/* + * Copyright 2023-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.ballcat.desensitize.handler; + +import org.ballcat.desensitize.rule.DesensitizeRule; + +/** + * 基于规则的替换脱敏处理器,如指定"3-6,8,10-"表示第4,5,6,7,9,11以及11之后的位替换处理 + * + * @author evil0th Create on 2024/4/12 + */ +public class RuleDesensitizationHandler implements DesensitizationHandler { + + /** + * 基于规则的替换字符串 + * + *
+	 *     handle("43012319990101432X", "1", "4-6", "9-")) = "4*01***99*********"
+	 * 
+ * @param input 输入字符串 + * @param rule 规则 + * @return 脱敏字符串 + */ + public String handle(String input, String... rule) { + return handle(input, false, rule); + } + + /** + * 基于规则的替换字符串(支持反转) + * + *
+	 *     handle("43012319990101432X", true, "1", "4-6", "9-")) = "4*01***99*********"
+	 * 
+ * @param input 输入字符串 + * @param rule 规则 + * @param reverse 是否反转规则 + * @return 脱敏字符串 + */ + public String handle(String input, boolean reverse, String... rule) { + return handle(input, '*', reverse, rule); + } + + /** + * 基于规则的替换字符串 + * + *
+	 *     handle("43012319990101432X", '-', false, "1", "4-6", "9-")) = "4-01---99---------"
+	 *     handle("43012319990101432X", '-', true, "1", "4-6", "9-")) = "-3--231--90101432X"
+	 * 
+ * @param input 输入字符串 + * @param rule 规则。{@link DesensitizeRule} + * @param symbol 符号,默认* + * @param reverse 是否反转规则 + * @return 脱敏字符串 + */ + public String handle(String input, char symbol, boolean reverse, String... rule) { + return handle(input, symbol, reverse, DesensitizeRule.analysis(rule)); + } + + /** + * 基于规则的替换字符串 + * @param origin 输入字符串 + * @param rule 规则。{@link DesensitizeRule} + * @param symbol 符号,默认* + * @param reverse 是否反转规则 + * @return 脱敏字符串 + */ + private String handle(String origin, char symbol, boolean reverse, DesensitizeRule rule) { + if (origin == null) { + return null; + } + char[] clearChars = origin.toCharArray(); + for (int i = 0; i < clearChars.length; ++i) { + if (reverse ^ rule.isIn(i)) { + clearChars[i] = symbol; + } + } + return new String(clearChars); + } + +} diff --git a/desensitize/ballcat-desensitize/src/main/java/org/ballcat/desensitize/handler/SlideDesensitizationHandler.java b/desensitize/ballcat-desensitize/src/main/java/org/ballcat/desensitize/handler/SlideDesensitizationHandler.java index 33eb1fca9..79e2adb96 100644 --- a/desensitize/ballcat-desensitize/src/main/java/org/ballcat/desensitize/handler/SlideDesensitizationHandler.java +++ b/desensitize/ballcat-desensitize/src/main/java/org/ballcat/desensitize/handler/SlideDesensitizationHandler.java @@ -34,7 +34,19 @@ public class SlideDesensitizationHandler implements DesensitizationHandler { * @return 脱敏后的字符串 */ public String handle(String origin, int leftPlainTextLen, int rightPlainTextLen) { - return this.handle(origin, leftPlainTextLen, rightPlainTextLen, "*"); + return this.handle(origin, leftPlainTextLen, rightPlainTextLen, false); + } + + /** + * 滑动脱敏 + * @param origin 原文 + * @param leftPlainTextLen 处理完后左边的明文数 + * @param rightPlainTextLen 处理完后右边的明文数 + * @param reverse 是否反转 + * @return 脱敏后的字符串 + */ + public String handle(String origin, int leftPlainTextLen, int rightPlainTextLen, boolean reverse) { + return this.handle(origin, leftPlainTextLen, rightPlainTextLen, "*", reverse); } /** @@ -46,6 +58,20 @@ public String handle(String origin, int leftPlainTextLen, int rightPlainTextLen) * @return 脱敏后的字符串 */ public String handle(String origin, int leftPlainTextLen, int rightPlainTextLen, String maskString) { + return this.handle(origin, leftPlainTextLen, rightPlainTextLen, maskString, false); + } + + /** + * 滑动脱敏 + * @param origin 原文 + * @param leftPlainTextLen 处理完后左边的明文数 + * @param rightPlainTextLen 处理完后右边的明文数 + * @param maskString 原文窗口内每个字符被替换后的字符串 + * @param reverse 是否反转 + * @return 脱敏后的字符串 + */ + public String handle(String origin, int leftPlainTextLen, int rightPlainTextLen, String maskString, + boolean reverse) { if (origin == null) { return null; } @@ -56,10 +82,10 @@ public String handle(String origin, int leftPlainTextLen, int rightPlainTextLen, for (int i = 0; i < length; i++) { // 明文位内则明文显示 if (i < leftPlainTextLen || i > (length - rightPlainTextLen - 1)) { - sb.append(chars[i]); + sb.append(reverse ? maskString : chars[i]); } else { - sb.append(maskString); + sb.append(reverse ? chars[i] : maskString); } } return sb.toString(); @@ -72,7 +98,19 @@ public String handle(String origin, int leftPlainTextLen, int rightPlainTextLen, * @return 脱敏后的字符串 */ public String handle(String value, SlideDesensitizationTypeEnum type) { - return this.handle(value, type.getLeftPlainTextLen(), type.getRightPlainTextLen(), type.getMaskString()); + return this.handle(value, type, false); + } + + /** + * 根据指定枚举类型进行滑动脱敏 + * @param value 原文 + * @param type 滑动脱敏类型 + * @param reverse 是否反转 + * @return 脱敏后的字符串 + */ + public String handle(String value, SlideDesensitizationTypeEnum type, boolean reverse) { + return this.handle(value, type.getLeftPlainTextLen(), type.getRightPlainTextLen(), type.getMaskString(), + reverse); } } diff --git a/desensitize/ballcat-desensitize/src/main/java/org/ballcat/desensitize/json/annotation/JsonRuleDesensitize.java b/desensitize/ballcat-desensitize/src/main/java/org/ballcat/desensitize/json/annotation/JsonRuleDesensitize.java new file mode 100644 index 000000000..726abdab7 --- /dev/null +++ b/desensitize/ballcat-desensitize/src/main/java/org/ballcat/desensitize/json/annotation/JsonRuleDesensitize.java @@ -0,0 +1,56 @@ +/* + * Copyright 2023-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.ballcat.desensitize.json.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.ballcat.desensitize.handler.RuleDesensitizationHandler; +import org.ballcat.desensitize.rule.DesensitizeRule; + +/** + * Jackson Filed 序列化脱敏注解, 对应基于规则脱敏处理器对值进行脱敏处理 + * + * @author evil0th Create on 2024/4/12 + * @see RuleDesensitizationHandler + */ +@Target({ ElementType.FIELD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface JsonRuleDesensitize { + + /** + * 脱敏规则 + * @return type + * @see DesensitizeRule + */ + String[] rule(); + + /** + * 是否反转规则 + */ + boolean reverse() default false; + + /** + * 替换的字符串 + */ + char maskChar() default '*'; + +} diff --git a/desensitize/ballcat-desensitize/src/main/java/org/ballcat/desensitize/json/annotation/JsonSlideDesensitize.java b/desensitize/ballcat-desensitize/src/main/java/org/ballcat/desensitize/json/annotation/JsonSlideDesensitize.java index 90238d339..077269746 100644 --- a/desensitize/ballcat-desensitize/src/main/java/org/ballcat/desensitize/json/annotation/JsonSlideDesensitize.java +++ b/desensitize/ballcat-desensitize/src/main/java/org/ballcat/desensitize/json/annotation/JsonSlideDesensitize.java @@ -59,4 +59,9 @@ */ String maskString() default "*"; + /** + * 是否反转 + */ + boolean reverse() default false; + } diff --git a/desensitize/ballcat-desensitize/src/main/java/org/ballcat/desensitize/rule/DesensitizeRule.java b/desensitize/ballcat-desensitize/src/main/java/org/ballcat/desensitize/rule/DesensitizeRule.java new file mode 100644 index 000000000..7f31414b9 --- /dev/null +++ b/desensitize/ballcat-desensitize/src/main/java/org/ballcat/desensitize/rule/DesensitizeRule.java @@ -0,0 +1,112 @@ +/* + * Copyright 2023-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.ballcat.desensitize.rule; + +import java.util.PriorityQueue; + +/** + * + * 脱敏规则 + *

+ * 1. 下标从0开始
+ * 2. 单个下标:数字
+ * 3. 范围下标:数字1-数字2/数字1-
+ * 4. 多个规则合并:规则1,规则2
+ * 如:3-6,8,10- ,表示第4,5,6,7,9,11以及11之后的位使用加密字符替换 + * + * @author evil0th Create on 2024/4/12 + */ +public final class DesensitizeRule { + + private final PriorityQueue indexQueue; + + private boolean endless; + + private DesensitizeRule() { + this.indexQueue = new PriorityQueue<>((o1, o2) -> o2 - o1); + this.endless = false; + } + + /** + * 解析规则 + * @param rules 规则列表 + * @return rule + */ + public static DesensitizeRule analysis(String... rules) { + DesensitizeRule rule = new DesensitizeRule(); + if (null == rules) { + return rule; + } + for (String ruleStr : rules) { + if (null == ruleStr || ruleStr.isEmpty()) { + continue; + } + String[] groups = ruleStr.split(","); + for (String group : groups) { + if (null == group || group.isEmpty()) { + continue; + } + String[] items = group.split("-"); + if (items.length == 1) { + rule.addIndex(items[0]); + } + else { + rule.addIndex(items[0], items[items.length - 1]); + } + rule.endless |= group.endsWith("-"); + } + } + return rule; + } + + void addIndex(String index) { + addIndex(Integer.parseInt(index)); + } + + void addIndex(String startIndex, String endIndex) { + addIndex(Integer.parseInt(startIndex), Integer.parseInt(endIndex)); + } + + void addIndex(int index) { + if (isIn(index)) { + return; + } + this.indexQueue.add(index); + } + + void addIndex(int startIndex, int endIndex) { + for (int index = startIndex; index <= endIndex; ++index) { + addIndex(index); + } + } + + public boolean isIn(int index) { + if (this.indexQueue.contains(index)) { + return true; + } + if (!this.indexQueue.isEmpty() && this.endless) { + return this.indexQueue.peek() < index; + } + return false; + } + + @Override + public String toString() { + return "Rule{" + "index=" + this.indexQueue + ", endless=" + this.endless + "}"; + } + +} diff --git a/desensitize/ballcat-desensitize/src/main/java/org/ballcat/desensitize/util/DesensitizedUtil.java b/desensitize/ballcat-desensitize/src/main/java/org/ballcat/desensitize/util/DesensitizedUtil.java new file mode 100644 index 000000000..e4c9c67b0 --- /dev/null +++ b/desensitize/ballcat-desensitize/src/main/java/org/ballcat/desensitize/util/DesensitizedUtil.java @@ -0,0 +1,348 @@ +/* + * Copyright 2023-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.ballcat.desensitize.util; + +import org.ballcat.desensitize.DesensitizationHandlerHolder; +import org.ballcat.desensitize.enums.RegexDesensitizationTypeEnum; +import org.ballcat.desensitize.enums.SlideDesensitizationTypeEnum; +import org.ballcat.desensitize.handler.IPDesensitizationHandler; +import org.ballcat.desensitize.handler.RegexDesensitizationHandler; +import org.ballcat.desensitize.handler.RuleDesensitizationHandler; +import org.ballcat.desensitize.handler.SimpleDesensitizationHandler; +import org.ballcat.desensitize.handler.SixAsteriskDesensitizationHandler; +import org.ballcat.desensitize.handler.SlideDesensitizationHandler; + +/** + * 脱敏工具类 + *

+ * + * @author evil0th Create on 2024/4/12 + */ +public final class DesensitizedUtil { + + private DesensitizedUtil() { + } + + /** + * 中文姓名只显示第一个姓和最后一个汉字(单名则只显示最后一个汉字),其他隐藏为星号
+	 *     DesensitizedUtil.maskChineseName("张梦") = "*梦"
+	 *     DesensitizedUtil.maskChineseName("张小梦") = "张*梦"
+	 * 
+ * @param input 待处理的文本 + * @return 屏蔽后的文本 + */ + public static String maskChineseName(String input) { + if (invalidText(input)) { + return input; + } + return maskString(input, input.length() > 2 ? 1 : 0, 1); + } + + /** + * 身份证(18位或者15位)显示前六位, 四位,其他隐藏。
+	 *     DesensitizedUtil.maskIdCard("43012319990101432X") = "430123********432X"
+	 * 
+ * @param input 待处理的文本 + * @return 屏蔽后的文本 + */ + public static String maskIdCard(String input) { + if (invalidText(input)) { + return input; + } + SlideDesensitizationHandler slideHandler = DesensitizationHandlerHolder.getSlideDesensitizationHandler(); + return slideHandler.handle(input, SlideDesensitizationTypeEnum.ID_CARD_NO); + } + + /** + * 移动电话前三位,后四位,其他隐藏,比如
+	 * DesensitizedUtil.maskMobile("13812345678") = "138******10"
+	 * 
+ * @param input 待处理的文本 + * @return 屏蔽后的文本 + */ + public static String maskMobile(String input) { + if (invalidText(input)) { + return input; + } + SlideDesensitizationHandler slideHandler = DesensitizationHandlerHolder.getSlideDesensitizationHandler(); + return slideHandler.handle(input, SlideDesensitizationTypeEnum.PHONE_NUMBER); + } + + /** + * 地址脱敏,只显示到地区,不显示详细地址
+	 * DesensitizedUtil.maskAddress("北京市西城区金城坊街2号") = "北京市西城区******"
+	 * 
+ * @param input 待处理的文本 + * @return 屏蔽后的文本 + */ + public static String maskAddress(String input) { + if (invalidText(input)) { + return input; + } + return maskString(input, 6, 0); + } + + /** + * 电子邮箱脱敏,邮箱前缀最多显示前1字母,前缀其他隐藏,用星号代替,@及后面的地址显示
+	 * DesensitizedUtil.maskMail("test.demo@qq.com") = "t****@qq.com"
+	 * 
+ * @param input 待处理的文本 + * @return 屏蔽后的文本 + */ + public static String maskMail(String input) { + if (invalidText(input)) { + return input; + } + RegexDesensitizationHandler regexHandler = DesensitizationHandlerHolder.getRegexDesensitizationHandler(); + return regexHandler.handle(input, RegexDesensitizationTypeEnum.EMAIL); + } + + /** + * 银行卡号脱敏,显示前六位后四位
+	 * DesensitizedUtil.maskBankAccount("62226000000043211234") = "622260**********1234"
+	 * 
+ * @param input 待处理的文本 + * @return 屏蔽后的文本 + */ + public static String maskBankAccount(String input) { + if (invalidText(input)) { + return input; + } + SlideDesensitizationHandler slideHandler = DesensitizationHandlerHolder.getSlideDesensitizationHandler(); + return slideHandler.handle(input, SlideDesensitizationTypeEnum.BANK_CARD_NO); + } + + /** + * 密码脱敏,用******代替
+	 * DesensitizedUtil.maskPassword(password) = "******"
+	 * 
+ * @param input 待处理的文本 + * @return 屏蔽后的文本 + */ + public static String maskPassword(String input) { + if (invalidText(input)) { + return input; + } + SimpleDesensitizationHandler simpleHandler = DesensitizationHandlerHolder + .getSimpleHandler(SixAsteriskDesensitizationHandler.class); + return simpleHandler.handle(input); + } + + /** + * IPv脱敏,支持IPv4和IPv6
+	 * DesensitizedUtil.maskIP("192.168.2.1") = "192.*.*.*"
+	 * DesensitizedUtil.maskIP("2001:0db8:02de:0000:0000:0000:0000:0e13") = "2001:*:*:*:*:*:*:*"
+	 * DesensitizedUtil.maskIP("2001:db8:2de:0000:0000:0000:0000:e13") = "2001:*:*:*:*:*:*:*"
+	 * DesensitizedUtil.maskIP("2001:db8:2de:0:0:0:0:e13") = "2001:*:*:*:*:*:*:*"
+	 * 
+ * @param input 待处理的文本 + * @return 屏蔽后的文本 + */ + public static String maskIP(String input) { + if (invalidText(input)) { + return input; + } + SimpleDesensitizationHandler simpleHandler = DesensitizationHandlerHolder + .getSimpleHandler(IPDesensitizationHandler.class); + return simpleHandler.handle(input); + } + + /** + * 密文脱敏,前3后2,中间替换为 4个 * + * + *
+	 * DesensitizedUtil.maskKey("0000000123456q34") = "000****34"
+	 * 
+ * @param input 待处理的文本 + * @return 屏蔽后的文本 + */ + public static String maskKey(String input) { + if (invalidText(input)) { + return input; + } + RegexDesensitizationHandler regexHandler = DesensitizationHandlerHolder.getRegexDesensitizationHandler(); + return regexHandler.handle(input, RegexDesensitizationTypeEnum.ENCRYPTED_PASSWORD); + } + + /** + * 替换任意字符串 + * + *
+	 *     DesensitizedUtil.maskString("test.demo@qq.com", RegexDesensitizationTypeEnum.EMAIL) = "t****@qq.com"
+	 * 
+ * @param input 输入字符串 + * @param type {@link RegexDesensitizationTypeEnum} + * @return 屏蔽后的文本 + */ + public static String maskString(String input, RegexDesensitizationTypeEnum type) { + RegexDesensitizationHandler regexHandler = DesensitizationHandlerHolder.getRegexDesensitizationHandler(); + return regexHandler.handle(input, type); + } + + /** + * 替换任意字符串 + * + *
+	 *     DesensitizedUtil.maskString("01089898976", SlideDesensitizationTypeEnum.PHONE_NUMBER) = "010******76"
+	 * 
+ * @param input 输入字符串 + * @param type {@link SlideDesensitizationTypeEnum} + * @return 屏蔽后的文本 + */ + public static String maskString(String input, SlideDesensitizationTypeEnum type) { + return maskString(input, type, false); + } + + /** + * 替换任意字符串 + * + *
+	 *     DesensitizedUtil.maskString("01089898976", SlideDesensitizationTypeEnum.PHONE_NUMBER, true) = "***898989**"
+	 * 
+ * @param input 输入字符串 + * @param type {@link SlideDesensitizationTypeEnum} + * @param reverse 是否反转 + * @return 屏蔽后的文本 + */ + public static String maskString(String input, SlideDesensitizationTypeEnum type, boolean reverse) { + SlideDesensitizationHandler slideHandler = DesensitizationHandlerHolder.getSlideDesensitizationHandler(); + return slideHandler.handle(input, type, reverse); + } + + /** + * 替换任意字符串 + * + *
+	 *     DesensitizedUtil.maskString("Hello World", 2, 3) = "He******rld"
+	 * 
+ * @param input 输入字符串 + * @param head 头部保留长度 + * @param tail 尾部保留长度 + * @return 屏蔽后的文本 + */ + public static String maskString(String input, int head, int tail) { + return maskString(input, head, tail, false); + } + + /** + * 替换任意字符串 + * + *
+	 *     DesensitizedUtil.maskString("Hello World", 2, 3) = "He******rld"
+	 * 
+ * @param input 输入字符串 + * @param head 头部保留长度 + * @param tail 尾部保留长度 + * @param reverse 是否反转 + * @return 屏蔽后的文本 + */ + public static String maskString(String input, int head, int tail, boolean reverse) { + return maskString(input, head, tail, "*", reverse); + } + + /** + * 替换任意字符串
+	 * DesensitizedUtil.maskString("Hello World", 2, 3, "#") = "He######rld"
+	 * 
+ * @param input 输入字符串 + * @param head 头部保留长度 + * @param tail 尾部保留长度 + * @param replacement 替换结果字符 + * @return 屏蔽后的文本 + */ + public static String maskString(String input, int head, int tail, String replacement) { + return maskString(input, head, tail, replacement, false); + } + + /** + * 替换任意字符串
+	 * DesensitizedUtil.maskString("Hello World", 2, 3, "#") = "He######rld"
+	 * 
+ * @param input 输入字符串 + * @param head 头部保留长度 + * @param tail 尾部保留长度 + * @param replacement 替换结果字符 + * @return 屏蔽后的文本 + */ + public static String maskString(String input, int head, int tail, String replacement, boolean reverse) { + if (invalidText(input)) { + return input; + } + if (head + tail >= input.length()) { + return input; + } + SlideDesensitizationHandler slideHandler = DesensitizationHandlerHolder.getSlideDesensitizationHandler(); + return slideHandler.handle(input, head, tail, replacement, reverse); + } + + /** + * 基于规则的替换字符串
+	 *     DesensitizedUtil.maskString("43012319990101432X", "1", "4-6", "9-")) = "4*01***99*********"
+	 * 
+ * @param input 输入字符串 + * @param rule 规则。
+ * @return 脱敏字符串 + */ + public static String maskString(String input, String... rule) { + final RuleDesensitizationHandler ruleHandler = DesensitizationHandlerHolder.getRuleDesensitizationHandler(); + return ruleHandler.handle(input, rule); + } + + /** + * 基于规则的替换字符串
+	 *     DesensitizedUtil.maskString("43012319990101432X", true, "1", "4-6", "9-")) = "4*01***99*********"
+	 * 
+ * @param input 输入字符串 + * @param rule 规则 + * @param reverse 是否反转规则 + * @return 脱敏字符串 + */ + public static String maskString(String input, boolean reverse, String... rule) { + final RuleDesensitizationHandler ruleHandler = DesensitizationHandlerHolder.getRuleDesensitizationHandler(); + return ruleHandler.handle(input, reverse, rule); + } + + /** + * 基于规则的替换字符串
+	 *     DesensitizedUtil.maskString("43012319990101432X", '-', false, "1", "4-6", "9-")) = "4-01---99---------"
+	 *     DesensitizedUtil.maskString("43012319990101432X", '-', true, "1", "4-6", "9-")) = "-3--231--90101432X"
+	 * 
+ * @param input 输入字符串 + * @param rule 规则 + * @param symbol 符号,默认* + * @param reverse 是否反转规则 + * @return 脱敏字符串 + */ + public static String maskString(String input, char symbol, boolean reverse, String... rule) { + final RuleDesensitizationHandler ruleHandler = DesensitizationHandlerHolder.getRuleDesensitizationHandler(); + return ruleHandler.handle(input, symbol, reverse, rule); + } + + /** + * 判断是否无效字符串 + * @param text 字符串 + * @return true-无效字符串 + */ + private static boolean invalidText(String text) { + return null == text || text.isEmpty(); + } + +} diff --git a/desensitize/ballcat-desensitize/src/main/resources/META-INF/services/org.ballcat.desensitize.handler.SimpleDesensitizationHandler b/desensitize/ballcat-desensitize/src/main/resources/META-INF/services/org.ballcat.desensitize.handler.SimpleDesensitizationHandler index 31b9c2f97..f1e41f20c 100644 --- a/desensitize/ballcat-desensitize/src/main/resources/META-INF/services/org.ballcat.desensitize.handler.SimpleDesensitizationHandler +++ b/desensitize/ballcat-desensitize/src/main/resources/META-INF/services/org.ballcat.desensitize.handler.SimpleDesensitizationHandler @@ -1 +1,2 @@ org.ballcat.desensitize.handler.SixAsteriskDesensitizationHandler +org.ballcat.desensitize.handler.IPDesensitizationHandler diff --git a/desensitize/ballcat-desensitize/src/test/java/org/ballcat/desensite/DesensitisedTest.java b/desensitize/ballcat-desensitize/src/test/java/org/ballcat/desensite/DesensitisedTest.java index 1b74d2804..fa27f9d1e 100644 --- a/desensitize/ballcat-desensitize/src/test/java/org/ballcat/desensite/DesensitisedTest.java +++ b/desensitize/ballcat-desensitize/src/test/java/org/ballcat/desensite/DesensitisedTest.java @@ -22,16 +22,17 @@ import org.ballcat.desensitize.enums.RegexDesensitizationTypeEnum; import org.ballcat.desensitize.enums.SlideDesensitizationTypeEnum; import org.ballcat.desensitize.handler.RegexDesensitizationHandler; +import org.ballcat.desensitize.handler.RuleDesensitizationHandler; import org.ballcat.desensitize.handler.SimpleDesensitizationHandler; import org.ballcat.desensitize.handler.SixAsteriskDesensitizationHandler; import org.ballcat.desensitize.handler.SlideDesensitizationHandler; import org.ballcat.desensitize.json.JsonDesensitizeSerializerModifier; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; + /** * @author Hccake 2021/1/23 - * */ @Slf4j class DesensitisedTest { @@ -43,7 +44,7 @@ void testSimple() { .getSimpleHandler(SixAsteriskDesensitizationHandler.class); String origin = "你好吗?"; // 原始字符串 String target = desensitizationHandler.handle(origin); // 替换处理 - Assertions.assertEquals("******", target); + assertEquals("******", target); } @Test @@ -55,11 +56,11 @@ void testRegex() { String regex = "(^.)[^@]*(@.*$)"; // 正则表达式 String replacement = "$1****$2"; // 占位替换表达式 String target1 = desensitizationHandler.handle(origin, regex, replacement); // 替换处理 - Assertions.assertEquals("1****@qq.com", target1); + assertEquals("1****@qq.com", target1); // 内置的正则脱敏类型 String target2 = desensitizationHandler.handle(origin, RegexDesensitizationTypeEnum.EMAIL); - Assertions.assertEquals("1****@qq.com", target2); + assertEquals("1****@qq.com", target2); } @Test @@ -69,10 +70,26 @@ void testSlide() { .getSlideDesensitizationHandler(); String origin = "15805516789"; // 原始字符串 String target1 = desensitizationHandler.handle(origin, 3, 2); // 替换处理 - Assertions.assertEquals("158******89", target1); + assertEquals("158******89", target1); + + String target11 = desensitizationHandler.handle(origin, 3, 2, true); // 替换处理 + assertEquals("***055167**", target11); String target2 = desensitizationHandler.handle(origin, SlideDesensitizationTypeEnum.PHONE_NUMBER); // 替换处理 - Assertions.assertEquals("158******89", target2); + assertEquals("158******89", target2); + } + + @Test + void testRule() { + // 获取基于规则脱敏处理器 + RuleDesensitizationHandler desensitizationHandler = DesensitizationHandlerHolder + .getRuleDesensitizationHandler(); + String origin = "43012319990101432X"; // 原始字符串 + String target1 = desensitizationHandler.handle(origin, "1", "4-6", "9-"); // 替换处理 + assertEquals("4*01***99*********", target1); + + String target2 = desensitizationHandler.handle(origin, true, "1", "4-6", "9-"); // 替换处理 + assertEquals("*3**231**90101432X", target2); } @Test @@ -93,12 +110,14 @@ void testJackson() throws Exception { .setPassword("admina123456") .setPhoneNumber("15800000000") .setTestField("这是测试属性") - .setCustomDesensitize("test"); + .setCustomDesensitize("test") + .setRuleDesensitize("43012319990101432X") + .setRuleReverseDesensitize("43012319990101432X"); String value = objectMapper.writeValueAsString(user); log.info("脱敏后的数据:{}", value); - String expected = "{\"username\":\"xiaoming\",\"password\":\"adm****56\",\"email\":\"c****@foxmail.com\",\"phoneNumber\":\"158******00\",\"testField\":\"TEST-这是测试属性\",\"customDesensitize\":\"test\"}"; - Assertions.assertEquals(expected, value); + String expected = "{\"username\":\"xiaoming\",\"password\":\"adm****56\",\"email\":\"c****@foxmail.com\",\"phoneNumber\":\"158******00\",\"testField\":\"TEST-这是测试属性\",\"customDesensitize\":\"test\",\"ruleDesensitize\":\"4*01***99*********\",\"ruleReverseDesensitize\":\"*3**231**90101432X\"}"; + assertEquals(expected, value); } } diff --git a/desensitize/ballcat-desensitize/src/test/java/org/ballcat/desensite/DesensitisedUtilTest.java b/desensitize/ballcat-desensitize/src/test/java/org/ballcat/desensite/DesensitisedUtilTest.java new file mode 100644 index 000000000..edf6f0523 --- /dev/null +++ b/desensitize/ballcat-desensitize/src/test/java/org/ballcat/desensite/DesensitisedUtilTest.java @@ -0,0 +1,62 @@ +/* + * Copyright 2023-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.ballcat.desensite; + +import java.util.UUID; + +import org.ballcat.desensitize.enums.RegexDesensitizationTypeEnum; +import org.ballcat.desensitize.enums.SlideDesensitizationTypeEnum; +import org.ballcat.desensitize.util.DesensitizedUtil; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author evil0th Create on 2024/4/12 + */ +public class DesensitisedUtilTest { + + @Test + public void test() { + assertEquals("t****@qq.com", + DesensitizedUtil.maskString("test.demo@qq.com", RegexDesensitizationTypeEnum.EMAIL)); + assertEquals("010******76", + DesensitizedUtil.maskString("01089898976", SlideDesensitizationTypeEnum.PHONE_NUMBER)); + assertEquals("***898989**", + DesensitizedUtil.maskString("01089898976", SlideDesensitizationTypeEnum.PHONE_NUMBER, true)); + assertEquals("430123******431", DesensitizedUtil.maskString("430123990101431", 6, 3)); + assertEquals("430123********432X", DesensitizedUtil.maskString("43012319990101432X", 6, 4)); + assertEquals("430123????????432X", DesensitizedUtil.maskString("43012319990101432X", 6, 4, "?")); + assertEquals("张*丰", DesensitizedUtil.maskChineseName("张三丰")); + assertEquals("430123********432X", DesensitizedUtil.maskIdCard("43012319990101432X")); + assertEquals("138******78", DesensitizedUtil.maskMobile("13812345678")); + assertEquals("北京市西城区******", DesensitizedUtil.maskAddress("北京市西城区金城坊街2号")); + assertEquals("t****@qq.com", DesensitizedUtil.maskMail("test.demo@qq.com")); + assertEquals("622260**********1234", DesensitizedUtil.maskBankAccount("62226000000043211234")); + assertEquals("******", DesensitizedUtil.maskPassword(UUID.randomUUID().toString())); + assertEquals("000****34", DesensitizedUtil.maskKey("0000000123456q34")); + assertEquals("192.*.*.*", DesensitizedUtil.maskIP("192.168.2.1")); + assertEquals("2001:*:*:*:*:*:*:*", DesensitizedUtil.maskIP("2001:0db8:02de:0000:0000:0000:0000:0e13")); + assertEquals("2001:*:*:*:*:*:*:*", DesensitizedUtil.maskIP("2001:db8:2de:0:0:0:0:e13")); + assertEquals("4*01***99*********", DesensitizedUtil.maskString("43012319990101432X", "1", "4-6", "9-")); + assertEquals("4-01---99---------", + DesensitizedUtil.maskString("43012319990101432X", '-', false, "1", "4-6", "9-")); + assertEquals("-3--231--90101432X", + DesensitizedUtil.maskString("43012319990101432X", '-', true, "1", "4-6", "9-")); + } + +} diff --git a/desensitize/ballcat-desensitize/src/test/java/org/ballcat/desensite/DesensitizationUser.java b/desensitize/ballcat-desensitize/src/test/java/org/ballcat/desensite/DesensitizationUser.java index f196571c9..d23c9c631 100644 --- a/desensitize/ballcat-desensitize/src/test/java/org/ballcat/desensite/DesensitizationUser.java +++ b/desensitize/ballcat-desensitize/src/test/java/org/ballcat/desensite/DesensitizationUser.java @@ -22,6 +22,7 @@ import org.ballcat.desensitize.enums.RegexDesensitizationTypeEnum; import org.ballcat.desensitize.enums.SlideDesensitizationTypeEnum; import org.ballcat.desensitize.json.annotation.JsonRegexDesensitize; +import org.ballcat.desensitize.json.annotation.JsonRuleDesensitize; import org.ballcat.desensitize.json.annotation.JsonSimpleDesensitize; import org.ballcat.desensitize.json.annotation.JsonSlideDesensitize; @@ -68,4 +69,16 @@ public class DesensitizationUser { @CustomerDesensitize(type = "自定义注解示例") private String customDesensitize; + /** + * 测试规则脱敏 + */ + @JsonRuleDesensitize(rule = { "1", "4-6", "9-" }) + private String ruleDesensitize; + + /** + * 测试规则脱敏(反转) + */ + @JsonRuleDesensitize(rule = { "1", "4-6", "9-" }, reverse = true) + private String ruleReverseDesensitize; + } diff --git a/desensitize/ballcat-desensitize/src/test/java/org/ballcat/desensite/custom/CustomDesensitisedTest.java b/desensitize/ballcat-desensitize/src/test/java/org/ballcat/desensite/custom/CustomDesensitisedTest.java index 49ec3222f..7276a2b7d 100644 --- a/desensitize/ballcat-desensitize/src/test/java/org/ballcat/desensite/custom/CustomDesensitisedTest.java +++ b/desensitize/ballcat-desensitize/src/test/java/org/ballcat/desensite/custom/CustomDesensitisedTest.java @@ -63,7 +63,7 @@ void desensitizedExtend() throws Exception { .setCustomDesensitize("自定义属性"); String value = objectMapper.writeValueAsString(user); log.info("脱敏后的数据:{}", value); - String expected = "{\"username\":\"xiaoming\",\"password\":\"adm****56\",\"email\":\"c****@foxmail.com\",\"phoneNumber\":\"158******00\",\"testField\":\"TEST-这是测试属性\",\"customDesensitize\":\"customer rule自定义属性\"}"; + String expected = "{\"username\":\"xiaoming\",\"password\":\"adm****56\",\"email\":\"c****@foxmail.com\",\"phoneNumber\":\"158******00\",\"testField\":\"TEST-这是测试属性\",\"customDesensitize\":\"customer rule自定义属性\",\"ruleDesensitize\":null,\"ruleReverseDesensitize\":null}"; Assertions.assertEquals(expected, value); }