java 序列化-注解-脱敏
需求:
对于敏感字段需要加密脱敏操作;
实现方案:
通过序列化跟注解实现对指定的字段进行脱敏
序列化脱敏效果:
代码实现:
controller
package com.controller;
import com.pojo.Student;
import com.pojo.StudentVO;
import com.service.student.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author syh
* @date 2023/5/614:07
* @details 详情
*/
@RestController
@RequestMapping("/syh")
public class StudentController {
@Autowired
private StudentService studentService;
@GetMapping("/{id}")
public StudentVO searchStuByIdCard(@PathVariable("id") String id) {
Student student = studentService.searchStuByIdCard(id);
StudentVO studentVO = new StudentVO();
studentVO.setName("好吃不贵");
studentVO.setStudent(student);
System.out.println("studentVO = " + studentVO);
return studentVO;
}
}
service
package com.service.student;
import com.baomidou.mybatisplus.extension.service.IService;
import com.pojo.Student;
import com.service.dealer.Dealer;
/**
* @author syh
* @date 2023/5/614:14
* @details 详情
*/
public interface StudentService extends IService<Student> , Dealer {
public Student searchStuByIdCard(String idCard);
}
package com.service.student.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.mapper.StudentMapper;
import com.pojo.Student;
import com.pojo.Tasks;
import com.service.task.TasksTypeEnum;
import com.service.student.StudentService;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.util.List;
import java.util.UUID;
/**
* @author syh
* @date 2023/5/614:16
* @details 详情
*/
@Service
public class StudentServiceImpl extends ServiceImpl<StudentMapper, Student> implements StudentService {
@Autowired
private StudentMapper studentMapper;
@Override
public Student searchStuByIdCard(String id) {
LambdaQueryWrapper<Student> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Student::getId, id);
List<Student> students = studentMapper.selectList(wrapper);
return students == null ? null : students.get(0);
}
}
po
package com.pojo;
import com.annotation.DesensitizedAnnotation;
import com.baomidou.mybatisplus.annotation.TableField;
import com.serializer.DesensitizeTypeEnum;
import lombok.Data;
import java.beans.Transient;
import java.io.Serializable;
/**
* @author syh
* @date 2023/4/2619:36
* @details 详情
*/
@Data
public class Student implements Serializable {
private int id;
@DesensitizedAnnotation(DesensitizeTypeEnum.CHINESE_NAME)
private String name;
private Integer age;
private String address;
@DesensitizedAnnotation(DesensitizeTypeEnum.ID_CARD)
private String idCard;
}
package com.pojo;
import com.annotation.DesensitizedAnnotation;
import com.serializer.DesensitizeTypeEnum;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
/**
* @author syh
* @date 2023/6/14 14:20
* @details 类描述
*/
@Data
public class StudentVO {
@DesensitizedAnnotation(DesensitizeTypeEnum.CHINESE_NAME)
private String name;
private Student student;
}
enum
package com.serializer;
/**
* @author syh
* @date 2023/6/13 14:50
* @details 类描述
*/
public enum DesensitizeTypeEnum {
/**
* 中文名字
*/
CHINESE_NAME,
/**
* 身份证
*/
ID_CARD;
}
Annotation 注解
package com.annotation;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.serializer.DesensitizationAnnotationSerializer;
import com.serializer.DesensitizeTypeEnum;
import java.lang.annotation.*;
/**
* @author syh
* @date 2023/6/13 17:12
* @details 脱敏注解
*/
@Inherited //用于指定注解是否可以被继承。如果一个注解被@Inherited标记,那么当一个类继承了标有该注解的父类时,子类也会继承该注解。
@Documented //生成的java文档中有注解的解释
@Retention(RetentionPolicy.RUNTIME) //运行时
@Target(ElementType.FIELD) //注解使用雨成员变量上
@JacksonAnnotationsInside
@JsonSerialize(using = DesensitizationAnnotationSerializer.class)
public @interface DesensitizedAnnotation {
/**
* 脱敏类型
*/
DesensitizeTypeEnum value();
}
Serializer 序列化
package com.serializer;
import com.annotation.DesensitizedAnnotation;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
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.util.MaskUtil;
import java.io.IOException;
import java.util.Objects;
/**
* @author syh
* @date 2023/6/13 13:46
* @details 类描述
* JsonSerializer<String> 泛型String 针对string序列化 重写serializer
* ContextualSerializer 重写type的创建方法,获取指定的type以及注解的value 给到不同的脱敏方式
* 泛型的选择,可以使用object 通过对注解value的判断调整做到类型对应脱敏的方式;enum枚举是一个很好的选择;
*/
public class DesensitizationAnnotationSerializer extends JsonSerializer<String> implements ContextualSerializer {
/**
* 脱敏注解
*/
private DesensitizeTypeEnum desensitizeTypeEnum;
public DesensitizationAnnotationSerializer() {
}
public DesensitizationAnnotationSerializer(DesensitizeTypeEnum desensitizeTypeEnum) {
this.desensitizeTypeEnum = desensitizeTypeEnum;
}
/**
* 序列化脱敏
*
* @param value Value to serialize; can <b>not</b> be null.
* @param jsonGenerator Generator used to output resulting Json content
* @param serializers Provider that can be used to get serializers for
* serializing Objects value contains, if any.
* @throws IOException
*/
@Override
public void serialize(String value, JsonGenerator jsonGenerator, SerializerProvider serializers) throws IOException {
if (StringUtils.isNotBlank(value)) {
//脱敏 //根据注解选择脱敏的方式
jsonGenerator.writeString(MaskUtil.mask(value, desensitizeTypeEnum));
}
}
/**
* 重新返回序列化的方式
*
* @param prov Serializer provider to use for accessing config, other serializers
* @param property Method or field that represents the property
* (and is used to access value to serialize).
* Should be available; but there may be cases where caller cannot provide it and
* null is passed instead (in which case impls usually pass 'this' serializer as is)
* @return
* @throws JsonMappingException
*/
@Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
if (ObjectUtils.isNotEmpty(property)) {
if (Objects.equals(property.getType().getRawClass(), String.class)) {//可以不做判断,序列化只针对String(建议判断)
//获取注解
DesensitizedAnnotation annotation = property.getAnnotation(DesensitizedAnnotation.class);
if (ObjectUtils.isNotEmpty(annotation)) {
//获取枚举脱敏规则
DesensitizeTypeEnum result = annotation.value();
//指定序列化方式
return new DesensitizationAnnotationSerializer(result);
}
}
return prov.findContentValueSerializer(property.getType(), property);
}
return prov.findNullValueSerializer(property);
}
}
util 脱敏工具类
package com.util;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.serializer.DesensitizeTypeEnum;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.baomidou.mybatisplus.core.toolkit.StringPool.STAR;
/**
* @author syh
* @date 2023/6/13 14:45
* @details 脱敏工具类
*/
public class MaskUtil {
/**
* 返回脱敏方式
*
* @param value
* @param desensitizeTypeEnum
* @return
*/
public static String mask(String value, DesensitizeTypeEnum desensitizeTypeEnum) {
if (StringUtils.isBlank(value)) {
return StringUtils.EMPTY;
}
switch (desensitizeTypeEnum) {
case CHINESE_NAME:
return maskName(value);
case ID_CARD:
return maskIdCard(value);
}
return null;
}
/**
* 证件脱敏(身份证)
*
* @param value
* @return
*/
private static String maskIdCard(String value) {
return maskString(value, 4, 4);
}
/**
* 名字脱敏
*
* @param name
* @return
*/
private static String maskName(String name) {
return maskString(name,1,0);
}
/**
* 公共脱敏
*
* @param value
* @param start 前几位不脱名
* @param end 后几位不脱敏
* @return
*/
private static String maskString(String value, Integer start, Integer end) {
if (StringUtils.isBlank(value)) {
return StringUtils.EMPTY;
}
Integer length = value.length() - start - end;
if (length <= 0) {
return value;
}
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(value.substring(0, start));
for (int i = 0; i < length; i++) {
stringBuilder.append(STAR);
}
//截取后几位
stringBuilder.append(value.substring(value.length() - end));
return stringBuilder.toString();
}
/**
* 正则 碰撞脱敏
* @param value
* @param start 保留前几位
* @param end 保留后几位
* @return
*/
private static String maskStringByPattern(String value, Integer start, Integer end){
//正则规则(\S{3}{.*}\S{3})
String regex="(\\S{"+start+"})(.*)(\\S{"+end+"})";
//创建正则
Pattern pattern = Pattern.compile(regex);
//正则碰撞
Matcher matcher = pattern.matcher(value);
//碰撞结果
boolean result = matcher.find();
if(result){
String group = matcher.group(2);
StringBuilder stringBuilder=new StringBuilder();
stringBuilder.append(matcher.group(1));
for (int i = 0; i < group.length(); i++) {
stringBuilder.append(STAR);
}
stringBuilder.append(matcher.group(3));
return stringBuilder.toString();
}
return value;
}
public static void main(String[] args) {
//String s = maskString("123456789", 3, 0);
String s = maskStringByPattern("123456789", 3, 0);
System.out.println(s);
}
}