1.定义注解
创建Spring Boot项目添加以下依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>5.7.22</version>
</dependency>
</dependencies>
然后定义Sensitive 注解,@JacksonAnnotationsInside的作用是将@JacksonAnnotation标注的注解作为一个组合注解,这里使用@JacksonAnnotationsInside在@Sensitive 注解中组合@JsonSerialize注解,这样就可以使用JsonSerialize的功能,并且可以拓展自定义属性和隐式得指定自定义序列化器SensitiveSerializer了。
@Target({ElementType.FIELD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = SensitiveSerializer.class)
public @interface Sensitive {
/**
* 脱敏的类型,默认手机号
* @return
*/
Type value();
/**
* CUSTOM_HIDE/CUSTOM_OVERLAY 时生效
* 开始位置(包含)
* @return
*/
int startInclude() default 0;
/**
* CUSTOM_HIDE/CUSTOM_OVERLAY 时生效
* 结束位置(不包含)
* @return
*/
int endExclude() default 0;
/**
* CUSTOM_OVERLAY 时生效,*重复的次数
* @return
*/
int overlayRepeat() default 4;
/**
* Enumeration used with {@link Sensitive}
*/
public enum Type {
/**
* 手机号
*/
MOBILE,
/**
* 中文名
*/
CHINESE_NAME,
/**
* 身份证号
*/
ID_CARD,
/**
* 座机号
*/
FIXED_PHONE,
/**
* 地址
*/
ADDRESS,
/**
* 电子邮件
*/
EMAIL,
/**
* 银行卡
*/
BANK_CARD,
/**
* 自定义,有多少个字符替换成多少个*
* e.g: startInclude=3,endExclude=7,隐藏第3个到第7个的字符
*/
CUSTOM_HIDE,
/**
*保留方式隐藏
* e.g: startInclude=3,endExclude=4 ,保留前面3个和后面的4个
*/
CUSTOM_RETAIN_HIDE,
/**
* 自定义,只替换成指定个*
*/
CUSTOM_OVERLAY,
}
}
2. 脱敏工具类
创建脱敏工具类SensitiveUtil ,统一封装数据脱敏的方法。
public class SensitiveUtil {
/**
* [手机号码] 前3位后4位明码,中间4位掩码用****显示,如138****0000
* @param mobile 手机号码
* @return
*/
public static String handlerMobile(String mobile) {
if(StringUtil.isEmpty(mobile)){
return null;
}
return hide(mobile,3,mobile.length() - 4);
}
/**
* [手机号码] 只显示后四位, 如:*8856
* @param phone 手机号码
* @return
*/
public static String handlerPhone(String phone) {
if(StringUtil.isEmpty(phone)){
return null;
}
return overlay(phone,StringPool.ASTERISK,1,0,phone.length() - 4);
}
/**
* [身份证号] 前6位后4位明码,中间掩码用***显示,如511623********0537
* @param idNum
* @return
*/
public static String handlerIdCard(String idNum) {
if(StringUtil.isEmpty(idNum)){
return null;
}
return hide(idNum,6,idNum.length() - 4);
}
/**
* [银行卡] 前6位后4位明码,中间部分****代替,如622848*********5579
* @param cardNum
* @return
*/
public static String handlerBankCard(String cardNum) {
if(StringUtil.isEmpty(cardNum)){
return null;
}
return hide(cardNum,6,cardNum.length() - 4);
}
/**
* [地址] 只显示到地区,不显示详细地址;我们要对个人信息增强保护<例子:广东省广州市天河区****>
* @param address
*/
public static String handlerAddress(String address) {
if(StringUtil.isEmpty(address)){
return null;
}
return overlay(address,StringPool.ASTERISK,4,9,address.length());
}
/**
* [用户名] 只显示第一位 <例子:黄**>
* @param username
* @return
*/
public static String handlerUsername(String username) {
if(StringUtil.isEmpty(username)){
return null;
}
return hide(username,1,username.length());
}
/**
* [固定电话] 后四位,其他隐藏<例子:****1234>
*/
public static String handlerFixedPhone(final String num) {
if(StringUtil.isEmpty(num)){
return null;
}
return overlay(num, StringPool.ASTERISK,4, 0,num.length()-4);
}
/**
* [电子邮箱] 邮箱前缀仅显示第一个字母,前缀其他隐藏,用星号代替,@及后面的地址显示<例子:g**@163.com>
* @param email
* @return
*/
public static String handlerEmail(final String email) {
if(StringUtil.isEmpty(email)){
return null;
}
final int index = StringUtil.indexOf(email, StringPool.AT_CHAR);
if (index <= 1) {
return email;
} else {
return hide(email, 1, index);
}
}
/**
* 用另一个字符串覆盖一个字符串的一部分
* @param str 需要替换的字符串
* @param overlay 将被替换成的字符串
* @param overlayRepeat overlay重复的次数
* @param start 开始位置
* @param end 结束位置
* @return
*/
public static String overlay(String str, String overlay,int overlayRepeat, int start, int end) {
if (StringUtil.isEmpty(str)) {
return StringUtil.str(str);
}
if (StringUtil.isEmpty(overlay)) {
overlay = StringPool.EMPTY;
}
final int len = str.length();
if (start < 0) {
start = 0;
}
if (start > len) {
start = len;
}
if (end < 0) {
end = 0;
}
if (end > len) {
end = len;
}
if (start > end) {
final int temp = start;
start = end;
end = temp;
}
return str.substring(0, start) + StringUtil.repeat(overlay, overlayRepeat) + str.substring(end);
}
/**
* 替换指定字符串的指定区间内字符为"*"
*
* @param str 字符串
* @param startInclude 开始位置(包含)
* @param endExclude 结束位置(不包含)
* @return 替换后的字符串
*/
public static String hide(String str, int startInclude, int endExclude) {
return hide(str, startInclude, endExclude,StringPool.ASTERISK_CHAR);
}
/**
* 替换指定字符串的指定区间内字符为"*"
*
* @param str 字符串
* @param startInclude 开始位置(包含)
* @param endExclude 结束位置(不包含)
* @param replacedChar
* @return 替换后的字符串
*/
public static String hide(String str, int startInclude, int endExclude, char replacedChar) {
if (StringUtil.isEmpty(str)) {
return StringUtil.str(str);
}
final int strLength = str.length();
if (startInclude > strLength) {
return StringUtil.str(str);
}
if (endExclude > strLength) {
endExclude = strLength;
}
if (startInclude > endExclude) {
// 如果起始位置大于结束位置,不替换
return StringUtil.str(str);
}
final char[] chars = new char[strLength];
for (int i = 0; i < strLength; i++) {
if (i >= startInclude && i < endExclude) {
chars[i] = replacedChar;
} else {
chars[i] = str.charAt(i);
}
}
return new String(chars);
}
}
3. 自定义Jackson序列化器
参考Jackson自带的StringSerializer,继承JsonSerializer,重写serialize方法,自定义Jackson序列化器SensitiveSerializer针对String类型数据进行数据脱敏处理。
public class SensitiveSerializer extends JsonSerializer<String> implements ContextualSerializer {
private Sensitive.Type type;
private int startInclude;
private int endExclude;
private int overlayRepeat;
public SensitiveSerializer() {}
public SensitiveSerializer(final Sensitive sensitive) {
this.type = sensitive.value();
this.startInclude = sensitive.startInclude();
this.endExclude = sensitive.endExclude();
this.overlayRepeat = sensitive.overlayRepeat();
}
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
if(ObjectUtil.isEmpty(value)){
gen.writeString(value);
return;
}else {
switch (this.type) {
case MOBILE:
gen.writeString(SensitiveUtil.handlerMobile(value));
break;
case ID_CARD:
gen.writeString(SensitiveUtil.handlerIdCard(value));
break;
case BANK_CARD:
gen.writeString(SensitiveUtil.handlerBankCard(value));
break;
case CHINESE_NAME:
gen.writeString(SensitiveUtil.handlerUsername(value));
break;
case FIXED_PHONE:
gen.writeString(SensitiveUtil.handlerFixedPhone(value));
break;
case ADDRESS:
gen.writeString(SensitiveUtil.handlerAddress(value));
break;
case EMAIL:
gen.writeString(SensitiveUtil.handlerEmail(value));
break;
case CUSTOM_HIDE:
gen.writeString(SensitiveUtil.hide(value,startInclude,endExclude));
break;
case CUSTOM_RETAIN_HIDE:
gen.writeString(SensitiveUtil.hide(value,startInclude,(value.length()-endExclude)));
break;
case CUSTOM_OVERLAY:
gen.writeString(SensitiveUtil.overlay(value, StringPool.ASTERISK,overlayRepeat,startInclude,endExclude));
break;
default:
gen.writeString(value);
}
}
}
@Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
if(Objects.isNull(property)){
return prov.getDefaultNullValueSerializer();
}
if(Objects.equals(property.getType().getRawClass(), String.class)){
Sensitive sensitive = property.getAnnotation(Sensitive.class);
if (Objects.isNull(sensitive)) {
sensitive = property.getContextAnnotation(Sensitive.class);
}
if (Objects.nonNull(sensitive)) {
return new SensitiveSerializer(sensitive);
}
}
return prov.findValueSerializer(property.getType(), property);
//Sensitive sensitive = property.getAnnotation(Sensitive.class);
//type = sensitive.value();
//return this;
}
}
4. 测试使用
创建实体类Student
public class Student {
@Sensitive(value = Sensitive.Type.CHINESE_NAME)
private String name;
@Sensitive(value = Sensitive.Type.ID_CARD)
private String idCard;
@Sensitive(value = Sensitive.Type.BANK_CARD)
private String bankCard;
@Sensitive(value = Sensitive.Type.FIXED_PHONE)
private String fixedPhone;
@Sensitive(value = Sensitive.Type.ADDRESS)
private String address;
@Sensitive(value = Sensitive.Type.EMAIL)
private String email;
@Sensitive(value = Sensitive.Type.CUSTOM_RETAIN_HIDE,startInclude = 3,endExclude = 10)
private String remark;
public Student() {
}
public String getBankCard() {
return bankCard;
}
public void setBankCard(String bankCard) {
this.bankCard = bankCard;
}
public String getFixedPhone() {
return fixedPhone;
}
public void setFixedPhone(String fixedPhone) {
this.fixedPhone = fixedPhone;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
public Student(String name, String idCard, String bankCard, String fixedPhone, String address, String email, String remark) {
this.name = name;
this.idCard = idCard;
this.bankCard = bankCard;
this.fixedPhone = fixedPhone;
this.address = address;
this.email = email;
this.remark = remark;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getIdCard() {
return idCard;
}
public void setIdCard(String idCard) {
this.idCard = idCard;
}
}
定义接口/test/student
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping("/student")
public Student getStudent(){
Student student = new Student();
student.setName("张三三");
student.setIdCard("44082199612054343");
student.setBankCard("62173300255879654448");
student.setFixedPhone("3110026");
student.setAddress("广东省广州市天河区");
student.setEmail("1258398545@qq.com");
student.setRemark("sadhaonsdoasnodnaonodsn是大祭司大祭司你");
return student;
}
}
浏览器请求接口/test/student,查看接口返回的数据