一个注解让 Spring Boot 项目接口返回数据脱敏

1 背景

需求是某些接口返回的信息,涉及到敏感数据的必须进行脱敏操作
2 思路

①要做成可配置多策略的脱敏操作,要不然一个个接口进行脱敏操作,重复的工作量太多,很显然违背了“多写一行算我输”的程序员规范。思来想去,定义数据脱敏注解和数据脱敏逻辑的接口, 在返回类上,对需要进行脱敏的属性加上,并指定对应的脱敏策略操作。

②接下来我只需要拦截控制器返回的数据,找到带有脱敏注解的属性操作即可,一开始打算用 @ControllerAdvice 去实现,但发现需要自己去反射类获取注解。当返回对象比较复杂,需要递归去反射,性能一下子就会降低,于是换种思路,我想到平时使用的 @JsonFormat,跟我现在的场景很类似,通过自定义注解跟字段解析器,对字段进行自定义解析,tql。
3 实现代码
3.1自定义数据注解,并可以配置数据脱敏策略:

package com.wkf.workrecord.tools.desensitization;
     
    import java.lang.annotation.*;
     
    /**
     * 注解类
     * @author wuKeFan
     * @date 2023-02-20 09:36:39
     */
     
    @Target({ElementType.FIELD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface DataMasking {
     
        DataMaskingFunc maskFunc() default DataMaskingFunc.NO_MASK;
     
    }

3.2 自定义 Serializer,参考 jackson 的 StringSerializer,下面的示例只针对 String 类型进行脱敏

DataMaskingOperation.class:

package com.wkf.workrecord.tools.desensitization;
     
    /**
     * 接口脱敏操作接口类
     * @author wuKeFan
     * @date 2023-02-20 09:37:48
     */
    public interface DataMaskingOperation {
     
        String MASK_CHAR = “*”;
     
        String mask(String content, String maskChar);
     
    }

DataMaskingFunc.class:

package com.wkf.workrecord.tools.desensitization;
     
    import org.springframework.util.StringUtils;
     
    /**
     * 脱敏转换操作枚举类
     * @author wuKeFan
     * @date 2023-02-20 09:38:35
     */
    public enum DataMaskingFunc {
     
        /**
         *  脱敏转换器
         */
        NO_MASK((str, maskChar) -> {
            return str;
        }),
        ALL_MASK((str, maskChar) -> {
            if (StringUtils.hasLength(str)) {
                StringBuilder sb = new StringBuilder();
                for (int i = 0; i < str.length(); i++) {
                    sb.append(StringUtils.hasLength(maskChar) ? maskChar : DataMaskingOperation.MASK_CHAR);
                }
                return sb.toString();
            } else {
                return str;
            }
        });
     
        private final DataMaskingOperation operation;
     
        private DataMaskingFunc(DataMaskingOperation operation) {
            this.operation = operation;
        }
     
        public DataMaskingOperation operation() {
            return this.operation;
        }
     
    }

DataMaskingSerializer.class:

package com.wkf.workrecord.tools.desensitization;
     
    import com.fasterxml.jackson.core.JsonGenerator;
    import com.fasterxml.jackson.databind.JavaType;
    import com.fasterxml.jackson.databind.JsonMappingException;
    import com.fasterxml.jackson.databind.JsonNode;
    import com.fasterxml.jackson.databind.SerializerProvider;
    import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
    import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
    import com.fasterxml.jackson.databind.ser.std.StdScalarSerializer;
     
    import java.io.IOException;
    import java.util.Objects;
     
    /**
     * 自定义Serializer
     * @author wuKeFan
     * @date 2023-02-20 09:39:47
     */
    public final class DataMaskingSerializer extends StdScalarSerializer {
        private final DataMaskingOperation operation;
     
        public DataMaskingSerializer() {
            super(String.class, false);
            this.operation = null;
        }
     
        public DataMaskingSerializer(DataMaskingOperation operation) {
            super(String.class, false);
            this.operation = operation;
        }
     
     
        public boolean isEmpty(SerializerProvider prov, Object value) {
            String str = (String)value;
            return str.isEmpty();
        }
     
        public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
            if (Objects.isNull(operation)) {
                String content = DataMaskingFunc.ALL_MASK.operation().mask((String) value, null);
                gen.writeString(content);
            } else {
                String content = operation.mask((String) value, null);
                gen.writeString(content);
            }
        }
     
        public final void serializeWithType(Object value, JsonGenerator gen, SerializerProvider provider, TypeSerializer typeSer) throws IOException {
            this.serialize(value, gen, provider);
        }
     
        public JsonNode getSchema(SerializerProvider provider) {
            return this.createSchemaNode(“string”, true);
        }
     
        public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint) throws JsonMappingException {
            this.visitStringFormat(visitor, typeHint);
        }
    }

3.3 自定义 AnnotationIntrospector,适配我们自定义注解返回相应的 Serializer

package com.wkf.workrecord.tools.desensitization;
     
    import com.fasterxml.jackson.databind.introspect.Annotated;
    import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector;
    import lombok.extern.slf4j.Slf4j;
     
    /**
     * @author wuKeFan
     * @date 2023-02-20 09:43:41
     */
    @Slf4j
    public class DataMaskingAnnotationIntroSpector extends NopAnnotationIntrospector {
     
        @Override
        public Object findSerializer(Annotated am) {
            DataMasking annotation = am.getAnnotation(DataMasking.class);
            if (annotation != null) {
                return new DataMaskingSerializer(annotation.maskFunc().operation());
            }
            return null;
        }
     
    }

3.4 覆盖 ObjectMapper:

package com.wkf.workrecord.tools.desensitization;
     
    import com.fasterxml.jackson.databind.AnnotationIntrospector;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.databind.introspect.AnnotationIntrospectorPair;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Primary;
    import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
     
    /**
     * 覆盖 ObjectMapper
     * @author wuKeFan
     * @date 2023-02-20 09:44:35
     */
    @Configuration(proxyBeanMethods = false)
    public class DataMaskConfiguration {
     
        @Configuration(proxyBeanMethods = false)
        @ConditionalOnClass({Jackson2ObjectMapperBuilder.class})
        static class JacksonObjectMapperConfiguration {
            JacksonObjectMapperConfiguration() {
            }
     
            @Bean
            @Primary
            ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
                ObjectMapper objectMapper = builder.createXmlMapper(false).build();
                AnnotationIntrospector ai = objectMapper.getSerializationConfig().getAnnotationIntrospector();
                AnnotationIntrospector newAi = AnnotationIntrospectorPair.pair(ai, new DataMaskingAnnotationIntroSpector());
                objectMapper.setAnnotationIntrospector(newAi);
                return objectMapper;
            }
        }
     
    }

3.5 返回对象加上注解:

package com.wkf.workrecord.tools.desensitization;
     
    import lombok.Data;
     
    import java.io.Serializable;
     
    /**
     * 需要脱敏的实体类
     * @author wuKeFan
     * @date 2023-02-20 09:35:52
     */
    @Data
    public class User implements Serializable {
        /**
         * 主键ID
         */
        private Long id;
     
        /**
         * 姓名
         */
        @DataMasking(maskFunc = DataMaskingFunc.ALL_MASK)
        private String name;
     
        /**
         * 年龄
         */
        private Integer age;
     
        /**
         * 邮箱
         */
        @DataMasking(maskFunc = DataMaskingFunc.ALL_MASK)
        private String email;
     
    }

4 测试

我们写一个Controller测试一下看是不是我们需要的效果
4.1 测试的Controller类DesensitizationController.class如下:

package com.wkf.workrecord.tools.desensitization;
     
    import com.biboheart.brick.model.BhResponseResult;
    import com.wkf.workrecord.utils.ResultVOUtils;
    import lombok.RequiredArgsConstructor;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RestController;
     
    /**
     * 测试接口脱敏测试控制类
     * @author wuKeFan
     * @date 2022-06-21 17:23
     */
    @Slf4j
    @RestController
    @RequiredArgsConstructor
    @RequestMapping(“/desensitization/”)
    public class DesensitizationController {
     
        @RequestMapping(value = “test”, method = {RequestMethod.GET, RequestMethod.POST})
        public BhResponseResult test() {
            User user = new User();
            user.setAge(1);
            user.setEmail(“123456789@qq.com”);
            user.setName(“吴名氏”);
            user.setId(1L);
            return ResultVOUtils.success(user);
        }
     
    }

4.2 PostMan接口请求,效果符合预期,如图:

————————————————
版权声明:本文为CSDN博主「吴名氏.」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_37284798/article/details/129118284

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/117994.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

css如何将border线加到元素内部,占内边距,不占外边距

要将边框线添加到元素的内部&#xff0c;可以使用CSS的box-sizing属性和内边距&#xff08;padding&#xff09;来实现。 首先&#xff0c;将元素的box-sizing属性设置为border-box。这会使元素的宽度和高度包括边框和内边距在内。例如&#xff1a; .element {box-sizing: bo…

软件工程与计算总结(二十三)软件工程职业基础

本系列最后一更&#xff0c;《软计》系列总结的大结局&#xff01;本栏目告一段落&#xff0c;之后会结合真题和练习题再发布新的总结~ 往期链接&#xff1a; 《软件工程与计算》总结 一.软件工程职业 1.行业的发展 20世纪50年代&#xff1a;计算机还是研究型机器&#xff…

C++中显示构造和隐式构造

截图来源于C primerplus 第六版。

tomcat的负载均衡、动静分离(nginx联动)

动静分离&#xff1a; 访问静态页面和动态页面分开 实现动态和静态页面负载均衡 实验5台虚拟机 一、动态负载均衡 3台虚拟机模拟&#xff1a; 代理服务器&#xff1a;30 tomcat动态页面&#xff1a;21、22 代理服务器&#xff1a; proxy_pass http://tomcat; proxy_set_h…

buuctf[HCTF 2018]WarmUp 1

题目环境&#xff1a; 发现除了表情包&#xff0c;再无其他F12试试发现source.php文件访问这个文件&#xff0c;格式如下&#xff1a;url/source.php回显如下&#xff1a;PHP代码审计&#xff1a; <?php highlight_file(__FILE__); class emmm {public static function ch…

GIS在地质灾害危险性评估与灾后重建中的实践技术应用及python机器学习灾害易发性评价模型建立与优化

地质灾害是指全球地壳自然地质演化过程中&#xff0c;由于地球内动力、外动力或者人为地质动力作用下导致的自然地质和人类的自然灾害突发事件。由于降水、地震等自然作用下&#xff0c;地质灾害在世界范围内频繁发生。我国除滑坡灾害外&#xff0c;还包括崩塌、泥石流、地面沉…

我们在 Linux 环境中用 C 编程时,如果对文件读写,Linux 会自动给文件加锁嘛?

先说结论&#xff0c;结论是不会 我写了一个这样的程序 #include <stdio.h> #include <unistd.h>int main() {const char* pathname "your_file_pathname.txt";FILE* file NULL;int count 100;if(access(pathname, F_OK) 0) {file fopen(pathname…

Onnx精度转换 FP32->FP16

Onnx精度转换 FP32->FP16 1、依赖包 onnxonnxmltools 2、转换 from onnxmltools.utils.float16_converter import convert_float_to_float16 from onnxmltools.utils import load_model,save_modelonnx_model load_model("model.onnx") fp16_model convert_…

C语言进阶第九课 --------动态内存管理

作者前言 &#x1f382; ✨✨✨✨✨✨&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f382; ​&#x1f382; 作者介绍&#xff1a; &#x1f382;&#x1f382; &#x1f382; &#x1f389;&#x1f389;&#x1f389…

各种添加路由的方法

Linux 篇&#xff1a; ipv4: #添加到主机的路由 # route add –host 192.168.168.110 dev eth0 # route add –host 192.168.168.119 gw 192.168.168.1 #添加到网络的路由 # route add –net IP netmask MASK eth0 # route add –net IP netmask MASK gw IP # route add –n…

k8s-----19、Helm

Helm 1、引入2、概述2.1 重点2.2 V3版本的Helm2.2.1 与之前版本的不同之处2.2.2 V3版本的运行流程 3、安装和配置仓库、一些附带操作3.1 安装3.2 配置仓库3.3 常用命令3.4 添加helm的自动补齐 4、快速部署应用(weave应用)5、 自行创建Chart5.1 Chart目录内容解析5.2 简单安装部…

codeforces (C++ Haunted House)

题目&#xff1a; 翻译&#xff1a; 思路&#xff1a; 1、由题目可知&#xff0c;他想让我们判断交换相邻字符位置后将二进制转为十进制后&#xff0c;能否整除2的次方。能整除即输出需要交换的次数&#xff0c;不能则输出-1。&#xff08;例&#xff1a;输入3和010这组数据就…

论文学习:漏洞检测SnapVuln

SnapVuln SnapVuln是一种基于学习的漏洞检测方法&#xff0c;它应用多个针对特定漏洞的跨函数算法来识别源和汇点&#xff0c;以捕捉各种漏洞类型的精确程序语义。通过从源代码中提取跨函数的图形&#xff08;IG&#xff09;&#xff0c;SnapVuln可以综合使用PDG、CFG和调用图…

WebDAV之π-Disk派盘 +Polaris Office

推荐一款可以实现在Windows桌面PC,Mac,Android设备和iOS设备上同步的移动办公软件,还支持通过WebDAV添加葫芦儿派盘。Polaris Office是一款功能全面的办公自动化套件,软件集成了Word、Excel、幻灯片(PPT)、ODT等文档格式,并且可以完美兼容任何Microsoft Office,PDF,TXT或…

封装、继承、多态的概念

封装&#xff1a;将数据&#xff08;属性&#xff09;和函数&#xff08;操作&#xff09;合成一个整体&#xff0c;封装在类里面。 继承&#xff1a;派生与独立设计若干相关的类&#xff0c;前者工作量更少&#xff0c;重复的部分可以从基类继承来&#xff0c;不需要单独编程…

html web前端 登录,短信验证码登录

html web前端 登录&#xff0c;短信验证码登录 1&#xff0c;手机号码格式校验 2&#xff0c;按钮点击60秒倒计时&#xff0c;按钮限制点击 3&#xff0c;验证码/或密码长度校验&#xff08;被注释&#xff0c;公司发的验证码长度不一致&#xff0c;不一定是6位&#xff09; 4…

linux两块硬盘挂载同一个目录

参考&#xff1a;https://zhuanlan.zhihu.com/p/456193585 第一步&#xff1a;查看磁盘信息 查看现有物理卷信息 pvdisplay如果没有安装逻辑卷管理系统工具 apt install lvm2 ## 我用的 Ubuntu查看磁盘分区 fdisk -l我要用以下两块 SSD 来挂载到同一目录。 执行结果&…

虹科培训 | 虹科携手PLCopen开展IEC 61131-3国际工程师培训

文章来源&#xff1a;虹科工业控制 阅读原文&#xff1a;https://mp.weixin.qq.com/s/MLYhBWiWx7qQSApx_3xhmA &#xff08;一&#xff09;课程背景 什么是IEC 61131-3&#xff1f; IEC 61131-3 是工业自动化行业唯一得到大量应用的组态编程语言国际标准&#xff1b;主导制定…

xcode15一直显示正在连接iOS17真机问题解决

前言 更新xcode15之后&#xff0c;出现了各种报错问题&#xff0c;可谓是一路打怪啊&#xff0c;解决一个报错问题又来一个。没想到到了最后还能出现一个一直显示正在连接iOS17真机的问题 一直显示正在连接iOS17真机的问题 问题截图如下&#xff1a; 解决方法 1. 打开De…

apple-app-site-association nginx

项目里面配置 applinks: 域名 eg: baidu.com 2. 创建 apple-app-site-association 文件&#xff0c;无json等后缀名 eg&#xff1a; appID 构成 teamId bundleId {"applinks": {"apps": [],"details": [{"appID": "2TEAM6D5.…