SpringBoot实现返回值数据脱敏
介绍
-
SpringBoot实现返回数据脱敏
-
有时,敏感数据返回时,需要进行隐藏处理,但是如果一个字段一个字段的进行硬编码处理的话,不仅增加了工作量,而且后期需求变动的时候,更加是地狱般的工作量变更。
-
下面,通过身份证,姓名,密码,手机号等等示例去演示脱敏的流程,当然你也可以在此基础上添加自己的实现方式
原理
- 项目使用的是SpringBoot,所以需要在序列化的时候,进行脱敏处理,springboot内置的序列化工具为jackson
- 通过实现com.fasterxml.jackson.databind.JsonSerializer进行自定义序列化
- 通过重写com.fasterxml.jackson.databind.ser.ContextualSerializer.createContextual获取自定义注解的信息
实现
自定义注解类
@Target(ElementType.FIELD) //作用于字段上
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside // 表示自定义自己的注解Sensitive
@JsonSerialize(using = SensitiveInfoSerialize.class) // 该注解使用序列化的方式
public @interface Sensitive {SensitizedType value();
}
创建脱敏字段类型枚举
public enum SensitizedType {/*** 用户id*/USER_ID,/*** 中文名*/CHINESE_NAME,/*** 身份证号*/ID_CARD,/*** 座机号*/FIXED_PHONE,/*** 手机号*/MOBILE_PHONE,/*** 地址*/ADDRESS,/*** 电子邮件*/EMAIL,/*** 密码*/PASSWORD,/*** 中国大陆车牌,包含普通车辆、新能源车辆*/CAR_LICENSE,/*** 银行卡*/BANK_CARD,/*** IPv4地址*/IPV4,/*** IPv6地址*/IPV6,/*** 定义了一个first_mask的规则,只显示第一个字符。*/FIRST_MASK
}
脱敏工具类
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.StrUtil;/*** @Auther: wu* @Date: 2023/7/11* @Description: com.wu.demo.common.my_sensitive*/
public class SensitizedUtil {public static String desensitized(CharSequence str, SensitizedType desensitizedType) {if (StrUtil.isBlank(str)) {return StrUtil.EMPTY;}String newStr = String.valueOf(str);switch (desensitizedType) {case USER_ID:newStr = String.valueOf(userId());break;case CHINESE_NAME:newStr = chineseName(String.valueOf(str));break;case ID_CARD:newStr = idCardNum(String.valueOf(str), 3, 4);break;case FIXED_PHONE:newStr = fixedPhone(String.valueOf(str));break;case MOBILE_PHONE:newStr = mobilePhone(String.valueOf(str));break;case ADDRESS:newStr = address(String.valueOf(str), 8);break;case EMAIL:newStr = email(String.valueOf(str));break;case PASSWORD:newStr = password(String.valueOf(str));break;case CAR_LICENSE:newStr = carLicense(String.valueOf(str));break;case BANK_CARD:newStr = bankCard(String.valueOf(str));break;case IPV4:newStr = ipv4(String.valueOf(str));break;case IPV6:newStr = ipv6(String.valueOf(str));break;case FIRST_MASK:newStr = firstMask(String.valueOf(str));break;default:}return newStr;}/*** 【用户id】不对外提供userId** @return 脱敏后的主键*/public static Long userId() {return 0L;}/*** 定义了一个first_mask的规则,只显示第一个字符。
* 脱敏前:123456789;脱敏后:1********。** @param str 字符串* @return 脱敏后的字符串*/public static String firstMask(String str) {if (StrUtil.isBlank(str)) {return StrUtil.EMPTY;}return StrUtil.hide(str, 1, str.length());}/*** 【中文姓名】只显示第一个汉字,其他隐藏为2个星号,比如:李**** @param fullName 姓名* @return 脱敏后的姓名*/public static String chineseName(String fullName) {return firstMask(fullName);}/*** 【身份证号】前1位 和后2位** @param idCardNum 身份证* @param front 保留:前面的front位数;从1开始* @param end 保留:后面的end位数;从1开始* @return 脱敏后的身份证*/public static String idCardNum(String idCardNum, int front, int end) {//身份证不能为空if (StrUtil.isBlank(idCardNum)) {return StrUtil.EMPTY;}//需要截取的长度不能大于身份证号长度if ((front + end) > idCardNum.length()) {return StrUtil.EMPTY;}//需要截取的不能小于0if (front < 0 || end < 0) {return StrUtil.EMPTY;}return StrUtil.hide(idCardNum, front, idCardNum.length() - end);}/*** 【固定电话 前四位,后两位** @param num 固定电话* @return 脱敏后的固定电话;*/public static String fixedPhone(String num) {if (StrUtil.isBlank(num)) {return StrUtil.EMPTY;}return StrUtil.hide(num, 4, num.length() - 2);}/*** 【手机号码】前三位,后4位,其他隐藏,比如135****2210** @param num 移动电话;* @return 脱敏后的移动电话;*/public static String mobilePhone(String num) {if (StrUtil.isBlank(num)) {return StrUtil.EMPTY;}return StrUtil.hide(num, 3, num.length() - 4);}/*** 【地址】只显示到地区,不显示详细地址,比如:北京市海淀区****** @param address 家庭住址* @param sensitiveSize 敏感信息长度* @return 脱敏后的家庭地址*/public static String address(String address, int sensitiveSize) {if (StrUtil.isBlank(address)) {return StrUtil.EMPTY;}int length = address.length();return StrUtil.hide(address, length - sensitiveSize, length);}/*** 【电子邮箱】邮箱前缀仅显示第一个字母,前缀其他隐藏,用星号代替,@及后面的地址显示,比如:d**@126.com** @param email 邮箱* @return 脱敏后的邮箱*/public static String email(String email) {if (StrUtil.isBlank(email)) {return StrUtil.EMPTY;}int index = StrUtil.indexOf(email, '@');if (index <= 1) {return email;}return StrUtil.hide(email, 1, index);}/*** 【密码】密码的全部字符都用*代替,比如:******** @param password 密码* @return 脱敏后的密码*/public static String password(String password) {if (StrUtil.isBlank(password)) {return StrUtil.EMPTY;}return StrUtil.repeat('*', password.length());}/*** 【中国车牌】车牌中间用*代替* eg1:null -》 ""* eg1:"" -》 ""* eg3:苏D40000 -》 苏D4***0* eg4:陕A12345D -》 陕A1****D* eg5:京A123 -》 京A123 如果是错误的车牌,不处理** @param carLicense 完整的车牌号* @return 脱敏后的车牌*/public static String carLicense(String carLicense) {if (StrUtil.isBlank(carLicense)) {return StrUtil.EMPTY;}// 普通车牌if (carLicense.length() == 7) {carLicense = StrUtil.hide(carLicense, 3, 6);} else if (carLicense.length() == 8) {// 新能源车牌carLicense = StrUtil.hide(carLicense, 3, 7);}return carLicense;}/*** 银行卡号脱敏* eg: 1101 **** **** **** 3256** @param bankCardNo 银行卡号* @return 脱敏之后的银行卡号* @since 5.6.3*/public static String bankCard(String bankCardNo) {if (StrUtil.isBlank(bankCardNo)) {return bankCardNo;}bankCardNo = StrUtil.trim(bankCardNo);if (bankCardNo.length() < 9) {return bankCardNo;}final int length = bankCardNo.length();final int midLength = length - 8;final StringBuilder buf = new StringBuilder();buf.append(bankCardNo, 0, 4);for (int i = 0; i < midLength; ++i) {if (i % 4 == 0) {buf.append(CharUtil.SPACE);}buf.append('*');}buf.append(CharUtil.SPACE).append(bankCardNo, length - 4, length);return buf.toString();}/*** IPv4脱敏,如:脱敏前:192.0.2.1;脱敏后:192.*.*.*。** @param ipv4 IPv4地址* @return 脱敏后的地址*/public static String ipv4(String ipv4) {return StrUtil.subBefore(ipv4, '.', false) + ".*.*.*";}/*** IPv4脱敏,如:脱敏前:2001:0db8:86a3:08d3:1319:8a2e:0370:7344;脱敏后:2001:*:*:*:*:*:*:*** @param ipv6 IPv4地址* @return 脱敏后的地址*/public static String ipv6(String ipv6) {return StrUtil.subBefore(ipv6, ':', false) + ":*:*:*:*:*:*:*";}
}
上述枚举类和脱敏工具类,我使用了hutool中的代码,如果hutool满足你的需求,可以直接把上述自定义注解类和自定义序列化类使用到的SensitizedType类直接替换为hutool中的cn.hutool.core.util.DesensitizedUtil.DesensitizedType的枚举类,
添加自定义序列化实现类
public class SensitiveInfoSerialize extends JsonSerializer implements ContextualSerializer {private SensitizedType sensitizedType;/*** 步骤一* 方法来源于ContextualSerializer,获取属性上的注解属性,同时返回一个合适的序列化器*/@Overridepublic JsonSerializer> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {// 获取自定义注解Sensitive annotation = beanProperty.getAnnotation(Sensitive.class);// 注解不为空,且标注的字段为Stringif(Objects.nonNull(annotation) && Objects.equals(String.class, beanProperty.getType().getRawClass())){this.sensitizedType = annotation.value();//自定义情况,返回本序列化器,将顺利进入到该类中的serialize方法中return this;}// 注解为空,字段不为String,寻找合适的序列化器进行处理return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);}/*** 步骤二* 方法来源于JsonSerializer:指定返回类型为String类型,serialize()将修改后的数据返回*/@Overridepublic void serialize(String str, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {if(Objects.isNull(sensitizedType)){// 定义策略为空,返回原字符串jsonGenerator.writeString(str);}else {// 定义策略不为空,返回策略处理过的字符串jsonGenerator.writeString(SensitizedUtil.desensitized(str,sensitizedType));}}}
测试验证
在需要的脱敏的实体类字段上加上相应的注解
@Data
public class SensitiveBody {private String name;@Sensitive(SensitizedType.MOBILE_PHONE)private String mobile;@Sensitive(SensitizedType.ID_CARD)private String idCard;
}
@ApiOperation(value = "脱敏测试处理")@GetMapping("sensitiveTest")public AjaxResult sensitiveTest(){SensitiveBody body = new SensitiveBody();body.setMobile("13041064026");body.setIdCard("411126189912355689");body.setName("Tom");return AjaxResult.success(body);}

本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
