最初的想法是通过在实体类的getter方法操作,让它从数据库返回值过后运用自己的脱敏策略重新赋值
不过这种方法有弊端,如果在后端还要使用值的话,拿到的值不是数据库的真是的数据,所以只能在springmvc返回前端的时候操作(如果从数据库后获取值不操作的话,可以直接在getter方法上面写)
下面采用自定义注解和拦截器的方式
引入fastjson依赖
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.56</version>
</dependency>
枚举管理需要脱敏字段类型
/**
* @Description 敏感类型
* @Date 2020-08-26 12:36
* @Created by zfy
*/
public enum SensitiveTypeEnum {
PLATE_NUM,//车牌号
ID_CARD,//身份证号
PHONE;//手机号
}
脱敏工具类,操作各返回的形式
public class DesensitizationUtils {
public static String hiddenPhone(String phone) {
//手机号 隐藏中间四位
if (StringUtils.isBlank(phone)) {
return "";
}
return phone.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
}
public static String hiddenPlateNum(String plateNum) {
//车牌号 隐藏中间位
if (StringUtils.isBlank(plateNum)) {
return "";
}
return plateNum.substring(0, 3) + "*" + plateNum.substring(4, plateNum.length());
}
public static String hiddenIdCard(String idCard) {
//身份证展示 前6位和后6位
if (StringUtils.isBlank(idCard)) {
return "";
}
return StringUtils.left(idCard, 6).concat(StringUtils.removeStart(StringUtils.leftPad(StringUtils.right(idCard, 4), StringUtils.length(idCard), "*"), "***"));
}
}
使用自定义注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({
ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
public @interface Desensitization {
//脱敏类型
SensitiveTypeEnum type();
}
创建格式化类实现Formatter接口
public class DesensitizationFormatter implements Formatter<String> {
private SensitiveTypeEnum typeEnum;
public SensitiveTypeEnum getTypeEnum() {
return typeEnum;
}
public void setTypeEnum(SensitiveTypeEnum typeEnum) {
this.typeEnum = typeEnum;
}
@Override
public String parse(String valueStr, Locale locale) throws ParseException {
if (StringUtils.isNotBlank(valueStr)) {
switch (typeEnum) {
case ID_CARD:
valueStr = DesensitizationUtils.hiddenIdCard(valueStr);
break;
case PHONE:
valueStr = DesensitizationUtils.hiddenPhone(valueStr);
break;
case PLATE_NUM:
valueStr = DesensitizationUtils.hiddenPlateNum(valueStr);
break;
default:
}
}
return valueStr;
}
@Override
public String print(String s, Locale locale) {
return s;
}
}
实现AnnotationFormatterFactory接口
public class DesensitizationAnnotationFormatterFactory implements AnnotationFormatterFactory<Desensitization> {
@Override
public Set<Class<?>> getFieldTypes() {
Set<Class<?>> hashSet = new HashSet<>();
hashSet.add(String.class);
return hashSet;
}
@Override
public Printer<?> getPrinter(Desensitization annotation, Class<?> fieldType) {
return getFormatter(annotation);
}
@Override
public Parser<?> getParser(Desensitization annotation, Class<?> fieldType) {
return getFormatter(annotation);
}
private DesensitizationFormatter getFormatter(Desensitization desensitization) {
DesensitizationFormatter formatter = new DesensitizationFormatter();
formatter.setTypeEnum(desensitization.type());
return formatter;
}
}
springmvc配置文件
@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addFormatterForFieldAnnotation(new DesensitizationAnnotationFormatterFactory());
}
}
实体类
@Data
@TableName("t_user")
public class User {
@TableId
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long id;
@TableField(strategy = FieldStrategy.NOT_EMPTY)
private String username;
@TableField(strategy = FieldStrategy.NOT_EMPTY)
@Desensitization(type= SensitiveTypeEnum.PLATE_NUM)
private String plateNum;
@TableField(strategy = FieldStrategy.NOT_EMPTY)
@Desensitization(type= SensitiveTypeEnum.PHONE)
private String phone;
@TableField(strategy = FieldStrategy.NOT_EMPTY)
@Desensitization(type= SensitiveTypeEnum.ID_CARD)
private String idCard;
@TableField(strategy = FieldStrategy.NOT_EMPTY)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
}
Controller
@RestController
@RequestMapping("/api/user")
public class UserController {
@Autowired
private UserService userService;
@GetMapping
private List<User> list() {
return userService.list();
}
@PostMapping
public String add(@RequestBody User user) {
user.setCreateTime(new Date());
userService.save(user);
return "ok";
}
}
Postman调用
添加一个拦截器并配置到消息转换器中
public class ValueDesensitizeFilter implements ValueFilter {
@Override
public Object process(Object object, String name, Object value) {
if (null == value || !(value instanceof String) || ((String) value).length() == 0) {
return value;
}
try {
Field field = object.getClass().getDeclaredField(name);
Desensitization desensitization;
if (String.class != field.getType() || (desensitization = field.getAnnotation(Desensitization.class)) == null) {
return value;
}
String valueStr = (String) value;
SensitiveTypeEnum type = desensitization.type();
switch (type) {
case ID_CARD:
return DesensitizationUtils.hiddenIdCard(valueStr);
case PHONE:
return DesensitizationUtils.hiddenPhone(valueStr);
case PLATE_NUM:
return DesensitizationUtils.hiddenPlateNum(valueStr);
default:
}
} catch (NoSuchFieldException e) {
return value;
}
return value;
}
}
在mvc配置类中加一个消息转换器
@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addFormatterForFieldAnnotation(new DesensitizationAnnotationFormatterFactory());
}
@Bean
public HttpMessageConverters fastJsonHttpMessageConverters() {
// 1.定义一个converters转换消息的对象
FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
// 2.添加fastjson的配置信息,比如: 是否需要格式化返回的json数据
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
fastJsonConfig.setSerializerFeatures(SerializerFeature.WriteMapNullValue);
fastJsonConfig.setSerializeFilters(new ValueDesensitizeFilter());//添加自己写的拦截器
// 3.在converter中添加配置信息
fastConverter.setFastJsonConfig(fastJsonConfig);
// 4.将converter赋值给HttpMessageConverter
HttpMessageConverter<?> converter = fastConverter;
// 5.返回HttpMessageConverters对象
return new HttpMessageConverters(converter);
}
}
这样子脱敏成功了,不过时间问题用的JsonFormat为jackson里面的注解,消息转换器用的是alibaba的Fastjson,所以原来的不起作用,处理方式为在实体类字段上的JsonFormat改成JsonField如图
还有id为Long类型,因为之前JsonFormat转字符串的注解失效所以在项目中取到的值有精度丢失情况(这儿postman调用没有精度丢失,项目测试时候看到浏览器preview中取到的id精度丢失),处理方式有两种:
1.在消息转换器中加上Long类型返回为字符串的情况
不过这样子的话所有的Long类型都变成字符串类型,但是有些不用精度丢失的字段没必要,下面第二种方法可以解决这个问题
2.消息转换器不变,在前面那个过滤器中加上逻辑判断
再次测试发现格式转换和脱敏都成功了
源码地址在https://gitee.com/fuiyue/desensitization
对于fastjson消息转换器中SerializerFeature属性参考Fastjson SerializerFeature详解,可以根据需求设置一些属性。