当前位置: 首页 > news >正文

Java注解+com.fasterxml.jackson信息脱敏

前言

什么是数据脱敏

数据脱敏,指对某些敏感信息通过脱敏规则进行数据的变形,实现敏感隐私数据的可靠保护。这样就可以在开发、测试和其它非生产环境以及外包环境中安全地使用脱敏后的真实数据集。

应用场景

我们在开发程序过程中,需要对一些敏感信息比如证件号码、手机号码、地址、银行卡号等进行脱敏处理后进行显示,来保证数据的安全性,同时保证了数据库安全。

脱敏实现

数据脱敏类型枚举
package com.kcwx.common.utils.desensitize;/*** 数据脱敏类型* */
public enum DesensitizeType {/**手机号码*/PHONE,/**身份证号码*/ID_CARD,/**地址*/ADDRESS,/**邮箱*/EMAIL,/**银行卡*/BANK_CARD,/**社会信用代码*/CREDITCODE,/**组织机构代码*/ORGCODE;
}
数据脱敏规则
package com.kcwx.common.utils;import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;/*** 脱敏验证* */
public class DesensitizeValidator {/***省份地址验证* */private static final List<String> VALID_REGIONS = Arrays.asList("11", "12", "13", "14", "15", "21", "22", "23","31", "32", "33", "34", "35", "36", "37", "41", "42", "43", "44", "45", "46", "50", "51", "52","53", "54", "61", "62", "63", "64", "65", "71","81", "82", "91");/*** 三大运营商最新号段正则(2025年更新)* */private static final String PHONE_REGEX = "^1(3[0-9]|4[5-9]|5[0-9]|6[2567]|7[0-8]|8[0-9]|9[0-9])\\d{8}$";/*** 座机号正则(支持带区号和不带区号格式)* */private static final String LANDLINE_REGEX = "^(0\\d{2,3}-?)?[1-9]\\d{6,7}$";/*** 18位统一社会信用代码正则(字母/数字组合)* */private static final String CREDIT_CODE_REGEX = "^([0-9A-HJ-NPQRTUWXY]{2}\\d{6}[0-9A-HJ-NPQRTUWXY]{10}|[1-9]\\d{14})$";/*** 验证18位身份证号* */public static  boolean validate18(String idCard){// 1. 基础格式校验(长度+数字校验)if (idCard == null || idCard.length() != 18) {return false;}// 处理X校验位(不区分大小写)String normalized = idCard.replace('x', 'X');// 校验前17位是否为数字if (!Pattern.matches("^\\d{17}", normalized.substring(0, 17))) { return false;}// 2. 行政区划代码校验(2025年最新版)String regionCode = normalized.substring(0, 2);if (!VALID_REGIONS.contains(regionCode)) {return false;}// 3. 出生日期校验(支持到2099年)String birthStr = normalized.substring(6, 14);try {int year = Integer.parseInt(birthStr.substring(0, 4));int month = Integer.parseInt(birthStr.substring(4, 6));int day = Integer.parseInt(birthStr.substring(6, 8));if (year < 1900 || year > 2099){// 未来身份证需考虑扩展return false;}//简单日期校验(实际项目建议使用java.time.LocalDate)if (month < 1 || month > 12 || day < 1 || day > 31) { return false;}} catch (NumberFormatException e) {return false;}// 4. 校验码计算int[] weights = {7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2};char[] checkCode = {'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'};int sum = 0;for (int i = 0; i < 17; i++) {sum += weights[i] * Character.getNumericValue(normalized.charAt(i));}int mod = sum % 11;char expected = checkCode[mod];char actual = normalized.charAt(17);return expected == actual;}/*** 验证15位身份证号* */public static boolean isValid15(String idCard){// 1. 基础格式验证if (idCard == null || idCard.length() != 15) {return false;}try{long n = Long.parseLong(idCard);if (n < Math.pow(10, 14)) {return false;}} catch (NumberFormatException e) {return false;}// 2. 行政区划验证String regionCode = idCard.substring(0, 2);if (!VALID_REGIONS.contains(regionCode)) {return false;}// 3. 日期验证(兼容历史数据)String birthStr = "19" +idCard.substring(6, 12);try{SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");sdf.setLenient(false); // 严格模式sdf.parse(birthStr);} catch (Exception e) {return false;}return true;}/*** 获取脱敏后的身份证号* */public static String getDsIDCard(String idCard){if(validate18(idCard)||isValid15(idCard)) {if (idCard.length() == 18) {// 18位身份证脱敏正则(保留前6位和后4位)return idCard.replaceAll("(\\d{6})\\d{8}(\\w{4})", "$1********$2");} else {// 15位身份证脱敏正则(保留前6位和后3位)return idCard.replaceAll("(\\d{6})\\d{6}(\\w{3})", "$1********$2");}}return idCard;}/*** 电话号码验证* */public static boolean validPhone(String phone){return (phone.length()==11&&phone.matches(PHONE_REGEX));}/*** 座机电话号码验证* */public static boolean validFixPhone(String phone){return phone.matches(LANDLINE_REGEX);}/*** 获取脱敏后的电话号码* */public static String getDsPhone(String phone){if(validFixPhone( phone)) {//座机号脱敏(保留前3位和后2位)if (phone.contains("-")) {// 处理带区号的情况String[] parts = phone.split("-");return parts[0] + "-" + parts[1].replaceAll("(\\d{3})\\d{2}(\\d{2})", "$1**$2");} else {return phone.replaceAll("(\\d{3})\\d{2}(\\d{2})", "$1****$2");}}else if(validPhone(phone)){//手机号码脱敏,保留前三位和后四位return phone.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");}return phone;}/*** 统一社会信用代码验证* */public static boolean validCreditCode(String creditCode){return creditCode.matches(CREDIT_CODE_REGEX);}/*** 获取脱敏后的统一社会信用代码* */public static String getDsCreditCode(String creditCode){if(validCreditCode(creditCode)){//统一社会信用代码脱敏(保留前6位和后4位)return creditCode.replaceAll("(?<=^.{6}).*(?=.{4}$)", "********");}return creditCode;}/*** 获取脱敏后的组织机构代码* */public static String getDsOrgCode(String orgCode){if(StringUtils.isNotEmpty(orgCode)&&orgCode.length()>=10) {//组织机构代码脱敏(保留前4位和后6位)return orgCode.replaceAll("(?<=^.{4}).*(?=.{6}$)", "******");}return orgCode;}
}
脱敏注解
package com.kcwx.common.utils.desensitize;import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 字段脱敏注解* */
@Target(ElementType.FIELD) // 仅适用于字段
@Retention(RetentionPolicy.RUNTIME) // 运行时保留
@JacksonAnnotationsInside
@JsonSerialize(using = DesensitizeSerializer.class)
public @interface Desensitize {/**脱敏类型,默认为手机号*/DesensitizeType desensitizeType() default DesensitizeType.PHONE;
}
json序列化字段脱敏
package com.kcwx.common.utils.desensitize;import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import com.fasterxml.jackson.databind.BeanProperty;
import com.kcwx.common.utils.IDCardValidator;
import com.kcwx.common.utils.StringUtils;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;import java.io.IOException;
import java.util.Objects;/*** json序列化字段脱敏* */
@NoArgsConstructor
@AllArgsConstructor
public class DesensitizeSerializer extends JsonSerializer<String> implements ContextualSerializer {private DesensitizeType desensitizeType;@Overridepublic void serialize(String value, JsonGenerator gen, SerializerProvider serializerProvider) throws IOException {if (StringUtils.isNotEmpty(value)) {switch (desensitizeType){case PHONE:gen.writeString(DesensitizeValidator.getDsPhone(value));break;case ID_CARD:gen.writeString(DesensitizeValidator.getDsIDCard(value));break;case CREDITCODE:gen.writeString(DesensitizeValidator.getDsCreditCode(value));break;case ORGCODE:gen.writeString(DesensitizeValidator.getDsOrgCode(value));break;default:gen.writeString(value);}}else{gen.writeString("");}}@Overridepublic JsonSerializer<?> createContextual( SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {if (beanProperty != null) {if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) {//获取Desensitize注解Desensitize sensitive = beanProperty.getAnnotation(Desensitize.class);if (sensitive != null) {this.desensitizeType = sensitive.desensitizeType();return this;}}//返回默认的序列化器return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);}//返回默认的序列化器return serializerProvider.findNullValueSerializer(null);}
}
实例

在需要脱敏的字段使用@Desensitize

import com.kcwx.common.utils.desensitize.Desensitize;
import com.kcwx.common.utils.desensitize.DesensitizeType;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;@Data
public class DsOpdInfoAll {/**机构名称*/@ApiModelProperty("机构名称")private String deptName;/**社会信用代码*/@ApiModelProperty("社会信用代码")@Desensitize(desensitizeType = DesensitizeType.CREDITCODE)private String creditCode;/**机构编码*/@ApiModelProperty("机构编码")@Desensitize(desensitizeType = DesensitizeType.ORGCODE)private String orgCode;
}

接口返回效果


文章转载自:

http://exJzCR1v.xysdy.cn
http://ZK4todWp.xysdy.cn
http://GeTQwVP6.xysdy.cn
http://mRkYBjIA.xysdy.cn
http://6udJFdHE.xysdy.cn
http://GOQ0KTnh.xysdy.cn
http://7wjuDaDJ.xysdy.cn
http://TdQqqwqm.xysdy.cn
http://6JHEQYWp.xysdy.cn
http://JO88mUlK.xysdy.cn
http://4YG67EU4.xysdy.cn
http://fenZ0nbl.xysdy.cn
http://nWucddUE.xysdy.cn
http://LGlIoFeQ.xysdy.cn
http://in5tRSEC.xysdy.cn
http://25Hpn3Oh.xysdy.cn
http://4AdKGVw2.xysdy.cn
http://q3IK3eNo.xysdy.cn
http://jl1u5nYh.xysdy.cn
http://87RLMy9a.xysdy.cn
http://lJg91ulD.xysdy.cn
http://bty0xwnv.xysdy.cn
http://Jqcvm7RO.xysdy.cn
http://EeRtKOIz.xysdy.cn
http://h2fKq8oe.xysdy.cn
http://2UCXZU85.xysdy.cn
http://C2D3zY7F.xysdy.cn
http://wFhbcyZN.xysdy.cn
http://IphN9Ecd.xysdy.cn
http://2wEdUAXN.xysdy.cn
http://www.dtcms.com/a/387650.html

相关文章:

  • Docker 镜像瘦身实战:从 1.2GB 压缩到 200MB 的优化过程——Node.js 前端 SSR 场景的“node_modules 大屠杀”
  • 外网穿透到内网---访问公网IP映射到内网IP---frp使用
  • Google Veo 3 实战指南:三步告别AI视频“PPT感”
  • NVR接入录像回放平台EasyCVR视频融合平台语音对讲配置指南
  • 【Android】进程间如何通信
  • 从代码源码角度 解读 open-vla 算法架构
  • javaweb Tomcat及运行/HTTP
  • 深入解析 HTTP 状态码
  • PHP 常用函数及用法
  • WordPress 网站邮件通知功能实现指南:以 WP Mail SMTP 插件与 QQ 邮箱为例
  • 【CF】Day144——杂题 (交互 + 思维 | 整除分块)
  • Unity 实验功能实现:天敌捕食猎物(含对象池 + 点击交互)
  • 【docker】——docker国内可用的源
  • React Zustand存储token报错解决方案
  • I/O 多路复用器(select、poll、epoll)与 Reactor 模式详解
  • pytorch自定义算子转tensorrt
  • Springboots上传文件的同时传递参数用对象接收
  • Next.js 中表单处理与校验:React Hook Form 实战
  • 国标GB28181视频平台EasyGBS如何解决安防视频融合与级联管理的核心痛点?
  • Web 页面 SEO 审计自动化 - 基于 n8n 和 Firecrawl
  • arcgis文件导出显示导出对象错误
  • PPT中将图片按比例裁剪
  • React + Zustand 状态管理
  • 复位开关芯片 EY412-A07E50国产低功耗延时芯片方案超低功耗
  • 动态规划-详解回文串系列问题
  • C语言基础学习(五)——进制
  • 如何在C#中将 Excel 文件(XLS/XLSX)转换为 PDF
  • 【Error】django-debug-toolbar不显示:Failed to load module script
  • Windows 版本 WDK 版本 Windows SDK Visual Studio各版本对应关系
  • WPF 快速布局技巧