赞
踩
SpringBoot实现返回数据脱敏
有时,敏感数据返回时,需要进行隐藏处理,但是如果一个字段一个字段的进行硬编码处理的话,不仅增加了工作量,而且后期需求变动的时候,更加是地狱般的工作量变更。
下面,通过身份证,姓名,密码,手机号等等示例去演示脱敏的流程,当然你也可以在此基础上添加自己的实现方式
自定义注解类
- @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的规则,只显示第一个字符。<br>
- * 脱敏前: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;
- }
- //需要截取的不能小于0
- if (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<String> implements ContextualSerializer {
-
- private SensitizedType sensitizedType;
-
- /**
- * 步骤一
- * 方法来源于ContextualSerializer,获取属性上的注解属性,同时返回一个合适的序列化器
- */
- @Override
- public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
- // 获取自定义注解
- Sensitive annotation = beanProperty.getAnnotation(Sensitive.class);
- // 注解不为空,且标注的字段为String
- if(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>:指定返回类型为String类型,serialize()将修改后的数据返回
- */
- @Override
- public 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);
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。