首页 > 其他分享 >自定义注解实现数据序列化时进行数据脱敏(基于springboot默认jackjson)、消息转换器

自定义注解实现数据序列化时进行数据脱敏(基于springboot默认jackjson)、消息转换器

时间:2023-05-24 11:44:15浏览次数:54  
标签:springboot 自定义 new 注解 import 序列化 com public

消息转换器

fastjson与jackjson

问题

在springboot中使用fastjson的@jsonField无效
原因:在springboot默认有json(jackjson)解析工具,所以使用fastjson不会生效
解决方案替换默认的解析工具(笔者不推荐,这里根据自己项目决定)

fastjson替换默认的jackjson

第一种方法bean方法

package com.anson.config;
 
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
 
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
 
@Configuration
public class WebConfig {
 
    /**
     * @Author anson
     * @Description 配置消息转换器
     * @Date: 2019-12-8 11:23:33
     * @version: 1.0
     * new HttpMessageConverters(true, converters);
     * 一定要设为true才能替换否则不会替换
     * @return 返回一个消息转换的bean
     */
    @Bean
    public HttpMessageConverters fastJsonMessageConverters() {
        List<HttpMessageConverter<?>> converters = new ArrayList<>();
        //需要定义一个convert转换消息的对象;
        FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
        //添加fastJson的配置信息;
        FastJsonConfig fastJsonConfig = new FastJsonConfig();
        fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
        //全局时间配置
        fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss");
        fastJsonConfig.setCharset(Charset.forName("UTF-8"));
        //处理中文乱码问题
        List<MediaType> fastMediaTypes = new ArrayList<>();
        fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
        //在convert中添加配置信息.
        fastConverter.setSupportedMediaTypes(fastMediaTypes);
        fastConverter.setFastJsonConfig(fastJsonConfig);
 
        converters.add(0, fastConverter);
        return new HttpMessageConverters(converters);
    }
}

第二种方法实现webMvcConfigurer

@Configuration
public class WebConfigure implements WebMvcConfigurer{
 
    /**
     * 配置消息转换器
     * @param converters
     */
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
       //需要定义一个convert转换消息的对象;
        FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
        //添加fastJson的配置信息;
        FastJsonConfig fastJsonConfig = new FastJsonConfig();
        fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
        //全局时间配置
        fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss");
        fastJsonConfig.setCharset(Charset.forName("UTF-8"));
        //处理中文乱码问题
        List<MediaType> fastMediaTypes = new ArrayList<>();
        fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
        //在convert中添加配置信息.
        fastConverter.setSupportedMediaTypes(fastMediaTypes);
        fastConverter.setFastJsonConfig(fastJsonConfig);
        converters.add(0,fastConverter);
    }
}

jackjson注解实现原理

参考:https://blog.csdn.net/qq_18515155/article/details/128891441?spm=1001.2014.3001.5501
这里以Jackson中常用的@JsonFormat注解为例,目标就是将Company对象序列化。

public class Company {
    /**
     * 开张时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd")
    private Date openDate;

    public Date getOpenDate() {
        return openDate;
    }

    public void setOpenDate(Date openDate) {
        this.openDate = openDate;
    }
}

public class Test {
    public static void main(String[] args) {
        Company company = new Company();
        company.setOpenDate(new Date());
      
        ObjectMapper objectMapper = new ObjectMapper();

        String s;
        try {
            s = objectMapper.writeValueAsString(company);
            System.out.println(s);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
    }
}

执行结果如下:
{"openDate":"2023-02-05"}

处理过程简述:
寻找合适的序列化器
序列化器二次处理(主要用于传递属性元信息)
执行序列化
详细如下:
一·寻找合适的序列化器
image
(1)根据当前数据类型从缓存中获取对应的序列化器
(2)如果获取不到,则创建序列化器
(3)根据Company上的注解获取序列化器(Company上没有注解)
(4)根据当前的注解内省器获取能够处理 步骤3 拿到的注解的序列化器(默认的注解内省器是JacksonAnnotationIntrospector,对于注解@JsonSerialize则返回注解指定的序列化器,对于注解JsonRawValue则返回RawSerializer)
(5)如果获取不到,再根据是否实现了JsonSerializable接口或者标注了 @JsonValue注解获取对应的序列化器(当前Company都没有)
(6)如果没有,就只能创建通用的对象处理器BeanSerialzer
(7)在创建BeanSerialzer之前,需要先去找到对象的属性元信息
(8)为对象的属性(Compnay.openDate)找到合适的序列化器(类似上面的步骤,一步一步找,这里最终没有找对于openDate属性的序列化器)
(9)把属性元信息设置到对象元信息里,然后new一个BeanSerialzer并返回
(10)拿到返回的序列化器以后,如果该序列化器实现了ContextualSerializer接口,则调用它的createContextual方法进行二次处理,并返回序列化器
二.序列化器二次处理(主要用于传递属性元信息)
步骤10即为序列化器二次处理:当找到合适的序列化器以后,还需要根据是否这个序列化器实现了ContextualSerializer接口,该接口声明了方法JsonSerializer<?> createContextual(SerializerProvider provider, BeanProperty property)

该接口可以拿到属性的元信息,如属性上的注解信息,根据元信息可以做一些二次处理,然后再返回序列化器,再用这个返回的序列化器做序列化处理。

三.执行序列化
image
(1)序列化对象
(2)序列化对象的字段
(3)动态获取能处理字段的序列化器
(4)检索内置的序列化器集合
(5)根据类型从内置的序列化器集合获取对应的序列化器,即根据openDate的类型Date.class获取到DateSerializer
(6)序列化器二次处理
(7)设置需要传递到序列化器中的上下文信息
(8)将openDate上的JsonFormat注解的pattern字段值传递到DateSerializer,用来在序列化openDate时知道应该转为哪种时间格式
(9)调用DateSerializer.serialze进行字段序列化

jackjson实现数据脱敏

脱敏即是对数据的部分信息用脱敏符号(*)处理。
自定义注解:

package com.chinatower.platform.awh.framework;

import com.chinatower.platform.awh.framework.enums.SentitiveFieldEnum;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @Description :
 * @Date : 2023/5/23
 * @Author :
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface SensitiveField {
    SentitiveFieldEnum type();
}

枚举类:

package com.chinatower.platform.awh.framework.enums;

/**
 * @Description :
 * @Date : 2023/5/23
 * @Author :
 */
public enum  SentitiveFieldEnum {
    /**
     * 身份证号
     */
    ID_CARD,
    /**
     * 姓名
     */
    NAME,
    /**
     * 手机号
     */
    PHONE,
}

工具类:

package com.chinatower.platform.awh.framework.config;

import cn.hutool.core.util.StrUtil;
import org.apache.commons.lang3.StringUtils;

/**
 * @Description :
 * @Date : 2023/5/23
 * @Author :
 */
public class SensitiveDataUtils {
    public static String encryptCard(String card){
        String left = StringUtils.left(card, 1);
        return StringUtils.rightPad(left,card.length(),"*");
    }
    public static String encryptPhone(String card){
        String left = StringUtils.left(card, 2);
        return StringUtils.rightPad(left,card.length(),"*");
    }
    public static String encryptName(String card){
        String left = StringUtils.left(card, 3);
        return StringUtils.rightPad(left,card.length(),"*");
    }
}

自定义处理注解的序列化器

package com.chinatower.platform.awh.framework.config;

import com.chinatower.platform.awh.framework.SensitiveField;
import com.chinatower.platform.awh.framework.enums.SentitiveFieldEnum;
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 java.io.IOException;

/**
 * @Description :
 * @Date : 2023/5/23
 * @Author :
 */
public class SensitiveFieldSerializer extends JsonSerializer<String> implements ContextualSerializer {
    private  SentitiveFieldEnum eum ;
    public SensitiveFieldSerializer(){}
    public SensitiveFieldSerializer(SentitiveFieldEnum enu){this.eum = enu;}


    @Override
    public void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {

        SentitiveFieldEnum sentitiveFieldEnum = eum;
        if (sentitiveFieldEnum == null){
            return;
        }
        switch (sentitiveFieldEnum){
            case ID_CARD:
                s=SensitiveDataUtils.encryptCard(s);
                break;
            case NAME:
                s=SensitiveDataUtils.encryptName(s);
                break;
            case PHONE:
                s=SensitiveDataUtils.encryptPhone(s);
                break;
            default:break;

        }
        jsonGenerator.writeObject(s);
    }

    @Override
    public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
        //获取注解
        System.out.println(1);
        if (beanProperty != null){
            SensitiveField annotation = beanProperty.getAnnotation(SensitiveField.class);
            SentitiveFieldEnum type = annotation.type();
            return new SensitiveFieldSerializer(type);
        }
        //返回处理枚举
        System.out.println(2);
        return serializerProvider.findNullValueSerializer(null);
    }
}

自定义注解内省器

package com.chinatower.platform.awh.framework;

import com.chinatower.platform.awh.framework.config.SensitiveFieldSerializer;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector;

/**
 * @Description :
 * @Date : 2023/5/23
 * @Author :
 */
public class SensitiveFieldAnnotationIntrospector extends NopAnnotationIntrospector {
    @Override
    public Object findSerializer(Annotated am) {
        SensitiveField annotation = am.getAnnotation(SensitiveField.class);
        if (annotation !=null){
            return SensitiveFieldSerializer.class;
        }
        return super.findSerializer(am);
    }
}

Jackson序列化配置

package com.chinatower.platform.awh.framework.config;

import com.chinatower.platform.awh.framework.SensitiveFieldAnnotationIntrospector;
import com.fasterxml.jackson.databind.AnnotationIntrospector;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;

/**
 * @Description :
 * @Date : 2023/5/23
 * @Author :
 */
@Configuration
public class JacksonConfig {


    @Bean
    public SensitiveFieldAnnotationIntrospector sensitiveFieldAnnotationIntrospector(){
        return new SensitiveFieldAnnotationIntrospector();
    }

    @Bean
    public SensitiveFieldSerializer sensitiveFieldSerializer(){
        return new SensitiveFieldSerializer();
    }

    @Bean
    public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder, SensitiveFieldAnnotationIntrospector introspector){
        ObjectMapper mapper = builder.createXmlMapper(false).build();
        AnnotationIntrospector annotationIntrospector = mapper.getSerializationConfig().getAnnotationIntrospector();
        AnnotationIntrospector pair = AnnotationIntrospector.pair(annotationIntrospector, introspector);
        mapper.setAnnotationIntrospector(pair);
        return mapper;
    }
}

springboot的bean是单例模式

测试

/ 定义一个对象,字段标有注解
@Data
public class User {
    @SensitiveField(type = SensitiveFieldTypeEnum.NAME)
    private String name;
    @SensitiveField(type = SensitiveFieldTypeEnum.PHONE)
    private String phone;
    private Integer age;
}
@GetMapping("/get")
    public User get() {
        User user = new User();
        user.setAge(20);
        user.setName("徐小莫");
        user.setPhone("18211843612");
        return user;
    }
执行结果:
{
  "name": "***",
  "phone": "182****3672",
  "age": 20
}

代码分析:
(1)如何找到处理自定义注解的序列化器
就要通过注解內省器找到合适的序列化器。
所以我们自定义了一个注解內省器,并把它加入到了当前Jackson注解內省器集合里:

        ObjectMapper objectMapper = builder.createXmlMapper(false).build();
        AnnotationIntrospector annotationIntrospector = objectMapper.getSerializationConfig().getAnnotationIntrospector();
        // 将自定义注解內省器加入到Jackson注解内省器集合里,AnnotationIntrospector是双向链表结构
        AnnotationIntrospector pair = AnnotationIntrospectorPair.pair(annotationIntrospector, introspector);
        objectMapper.setAnnotationIntrospector(pair);

(2)**如何不影响已有的Jackson序列化配置 **
实际的SpringBoot项目中,对于Controller的返回数据,经常会在配置文件或者配置类进行一些Jackson的自定义配置。

如果针对当前的自定义注解序列化器新建一个ObjectMapper:

        ObjectMapper objectMapper = new ObjectMapper();
        SimpleModule simpleModule = new SimpleModule();
        SensitiveFieldSerializer sensitiveFieldSerializer = new SensitiveFieldSerializer();
        simpleModule.addSerializer(String.class, sensitiveFieldSerializer);
        objectMapper.registerModule(simpleModule);
        AnnotationIntrospector annotationIntrospector = objectMapper.getSerializationConfig().getAnnotationIntrospector();
        AnnotationIntrospector newIntro = AnnotationIntrospectorPair.pair(annotationIntrospector,new SensitiveFieldAnnotationIntrospector());
        objectMapper.setAnnotationIntrospector(newIntro);

那这个ObjectMapper不会包含原来的那些配置,因为他是一个新的对象。

要保证原来的配置生效,就需要对SpringBoot自动配置的ObjectMapper动手脚。

通过分析JacksonAutoConfiguration.class这个自动配置类

        @Bean
        JacksonAutoConfiguration.Jackson2ObjectMapperBuilderCustomizerConfiguration.StandardJackson2ObjectMapperBuilderCustomizer standardJacksonObjectMapperBuilderCustomizer(ApplicationContext applicationContext, JacksonProperties jacksonProperties) {
            return new JacksonAutoConfiguration.Jackson2ObjectMapperBuilderCustomizerConfiguration.StandardJackson2ObjectMapperBuilderCustomizer(applicationContext, jacksonProperties);
        }
        
        
        @Bean
        @Scope("prototype")
        @ConditionalOnMissingBean
        Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder(ApplicationContext applicationContext, List<Jackson2ObjectMapperBuilderCustomizer> customizers) {
            Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
            builder.applicationContext(applicationContext);
            this.customize(builder, customizers);
            return builder;
        }
        @Bean
        @Primary
        // 这个注解代表没有自定义的ObjectMqpper bean对象时才会在这里创建
        @ConditionalOnMissingBean
        ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
            return builder.createXmlMapper(false).build();
        }

发现SpringBoot会将配置信息封装成Jackson2ObjectMapperBuilder,进而去创建ObjectMapper对象。

所以需要通过注入已有的Jackson2ObjectMapperBuilder对象来创建我们自己的ObjectMapper,再将自定义注解内省器加入到它的內省器集合,使我们的自定义注解序列化器生效:

    @Bean
    public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder, SensitiveFieldAnnotationIntrospector introspector) {
        ObjectMapper objectMapper = builder.createXmlMapper(false).build();
        AnnotationIntrospector annotationIntrospector = objectMapper.getSerializationConfig().getAnnotationIntrospector();
        // 将自定义注解內省器加入到Jackson注解内省器集合里,AnnotationIntrospector是双向链表结构
        AnnotationIntrospector pair = AnnotationIntrospectorPair.pair(annotationIntrospector, introspector);
        objectMapper.setAnnotationIntrospector(pair);
        return objectMapper;
    }

标签:springboot,自定义,new,注解,import,序列化,com,public
From: https://www.cnblogs.com/cgy1995/p/17427825.html

相关文章

  • Visual Studio Code (vscode)自定义用户代码段快速打出for循环等
    比如fori这样的快捷键就打不出代码块了自定义用户代码块的方法:工具栏>文件>首选项>用户代码片段  然后在弹出的搜索框中填写javascript.json有提示不用打全就行(会有javascript选中)  打开配置文件javascript.json这里面显示的就是编写代码块的例子"P......
  • springboot~对应sharding-jdbc实现分库分表
    原因当mysql数据库单表大于1千万以后,查询的性能就不能保证了,我们必须考虑分库,分表的方案了,还好,sharding-jdbc可以很优雅的与springboot对接,完成对mysql的分库和分表。依赖整理为了不影响其它小容量的表,所有添加了动态数据源,只对需要分库分表的进行配置即可com.baomidou:dyna......
  • SpringBoot中使用枚举类、switch、常量类(声明并初始化map)实现类策略者模式,接口返回
    场景SpringBoot中策略模式+工厂模式业务实例(接口传参-枚举类查询策略映射关系-执行不同策略)规避大量if-else:https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/130503707SpringBoot+@Validate+全局异常拦截实现自定义规则参数校验(校验get请求参数不能为空且在指定......
  • springboot框架实现网站权限管理系统
    技术架构技术框架:springboot+layui+shiro+activiti+thymeleaf+mybatis运行环境:jdk8+IntelliJIDEA+maven3+宝塔面板宝塔部署教程回到IDEA,点击编辑器右侧maven图标,执行package,完成后就会在根目录里生成一个target目录,在里面会打包出一个jar文件。宝塔新建一个......
  • Java语言springboot框架实现的停车位管理系统
    技术架构技术框架:jQuery+MySQL5.7+mybatis+shiro+Layui+HTML+CSS+JS+thymeleaf运行环境:jdk8+IntelliJIDEA+maven3+宝塔面板宝塔部署教程1.回到IDEA,点击编辑器右侧maven图标,执行package,完成后就会在根目录里生成一个target目录,在里面会打包出一个jar文件......
  • 界面控件DevExtreme使用指南 - 如何自定义上下文菜单和工具栏
    DevExtreme FileManager(文件管理器)小部件现在支持自定义内置的工具栏和上下文菜单,用户可以使用标准和定义的命令项填充项目集合,并配置设置来更改其外观和操作。DevExtreme拥有高性能的HTML5/JavaScript小部件集合,使您可以利用现代Web开发堆栈(包括React,Angular,ASP.NETCore,jQu......
  • SpringBoot中操作Redis解析JsonArray数据为对象List(ruoyi字典值sys_dict为例)
    场景若依前后端分离版手把手教你本地搭建环境并运行项目:https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/108465662在上面搭建系统的基础上,会将系统的字典值缓存进redis中。看数据格式存储的是Json数组,如何从redis中读取并解析成对象的list从而进行数据处理。注......
  • 【web 开发】PHP8中数组的序列化和反序列化
    前言数组的序列化(serialize)用来将数组的数据转换为字符串,以方便传递和数据库的存储。与之相对应的操作就是反序列化(unserialize),把字符串数据转换为数组加以使用。数组的序列化主要通过serialize()函数来完成。字符串的反序列化主要通过unserialize()函数来完成。对象的序列化与反序......
  • 基于springboot+vue数码论坛系统设计与实现、论坛管理系统,附源码+数据库+lw文档+PPT
    1、项目介绍考虑到实际生活中在数码论坛方面的需要以及对该系统认真的分析,将系统权限按管理员和用户这两类涉及用户划分。(1)系统功能需求登录系统后,主要模块包括首页、数码板块、数码评价、数码论坛、畅聊板块、新闻资讯、个人中心、后台管理等功能。系统功能用例图如图3-1所示......
  • php反序列化逃逸
    之前就对字符串逃逸这一块理解的不是很深刻,下面通过一位师傅的博客来进一步深入理解一下有关php字符串逃逸的相关内容。贴上师傅的博客地址:https://blog.csdn.net/qq_45521281/article/details/107135706先来说一下什么是字符串逃逸,就是我们可以构造一些恶意代码,让其在反序列化......