当前位置: 首页 > news >正文

一个注解让 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

相关文章:

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

1 背景 需求是某些接口返回的信息&#xff0c;涉及到敏感数据的必须进行脱敏操作 2 思路 ①要做成可配置多策略的脱敏操作&#xff0c;要不然一个个接口进行脱敏操作&#xff0c;重复的工作量太多&#xff0c;很显然违背了“多写一行算我输”的程序员规范。思来想去&#xff…...

测试人员的KPI怎么设置

关于测试部的KPI&#xff0c;简单列举下自己所经历部门的考核指标&#xff1a; 工作量。根据平时跟踪的需求&#xff0c;编写的用例&#xff0c;提交的bug数等综合评估&#xff1b; 负责项目的质量。上线项目是否出现重大功能的事故&#xff0c; 如果出现了事故&#xff0c;分…...

Databend 开源周报第 116 期

Databend 是一款现代云数仓。专为弹性和高效设计&#xff0c;为您的大规模分析需求保驾护航。自由且开源。即刻体验云服务&#xff1a;https://app.databend.cn 。 Whats On In Databend 探索 Databend 本周新进展&#xff0c;遇到更贴近你心意的 Databend 。 特性预览&#…...

mongodb-gridfs下载文件报Sort exceeded memory limit of 104857600 bytes异常

报错详细信息 com.mongodb.MongoQueryException: Query failed with error code 292 and error message Executor error during find command :: caused by :: Sort exceeded memory limit of 104857600 bytes, but did not opt in to external sorting. on server 11.51.141.…...

分享一下微信小程序里怎么实现扫码点餐链接

在当今数字化时代&#xff0c;扫码点餐已经成为了餐饮行业的一种趋势。通过微信小程序&#xff0c;实现扫码点餐功能&#xff0c;可以为餐厅带来诸多便利和优势。本文将详细介绍如何在微信小程序中实现扫码点餐功能&#xff0c;帮助餐厅提高服务效率和质量&#xff0c;提升用户…...

安卓开发环境安装教程

在本教程中&#xff0c;我将向您介绍如何在Windows操作系统上安装Android开发环境。Android开发环境包括Java Development Kit&#xff08;JDK&#xff09;&#xff0c;Android Studio IDE和相应的SDK工具。跟随以下步骤&#xff0c;您将能够搭建安卓开发环境并开始开发自己的应…...

深入探究Selenium定位技巧及最佳实践

在使用Selenium进行Web自动化测试时&#xff0c;准确地定位元素是非常重要的一步。Selenium提供了多种元素定位方法&#xff0c;本文将深入探究这八大元素定位方法&#xff0c;帮助读者更好地理解和应用Selenium的定位技巧。 1. ID定位 ID是元素在HTML中的唯一标识符&#xff…...

如何正确安装psycopg2,No module named ‘psycopg2._psycopg‘解决

二、psycopg2安装方法 psycopg2可以通过多种方式安装&#xff0c;我们这里介绍两种常用的方式&#xff1a;通过pip安装和手动安装。 1、通过pip安装psycopg2 如果已经安装了pip&#xff0c;那么在命令行中输入以下命令即可完成psycopg2的安装&#xff1a; pip install psyc…...

go WriteFile文件追加写入(适合小文件)

go 在做文件追加写入时一般用os.OpenFile 指定 FileMode 为 os.O_APPEND. 如官方文档示例: f, err : os.OpenFile("access.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)if err ! nil {log.Fatal(err)}if _, err : f.Write([]byte("appended some data\n&…...

history 模式上线需要注意什么事项?

结论先行&#xff1a; 首先&#xff0c;需要在服务器上对所有的路由路径进行配置&#xff0c;避免在访问路由时出现 404 的情况&#xff1b; 其次&#xff0c;需要特别注意安全性和兼容性问题。 因为使用 History 模式会暴露出服务器上的文件路径&#xff0c;因此在部署时需要…...

VMware虚拟机安装Ubuntu22.04教程(2023最新最详细)

目录 简介 1 VMware虚拟机下载与安装 2 Ubuntu操作系统安装与配置 2.1 Ubuntu虚拟机配置 2.2 Ubuntu操作系统安装 简介 Linux是一种自由和开放源代码的操作系统内核&#xff0c;被广泛应用于各种计算机系统中。它以稳定性、安全性和灵活性而闻名&#xff0c;并成为服务器…...

yakit使用爆破编码明文_dnslog使用

yakit使用爆破编码密码 文章目录 yakit使用爆破编码密码yakit使用1 yakit编码密码进行爆破2 准备eval.php文件放入web3 访问http://192.168.225.206/eval.php,使用bp抓包,测试后环境准本好4 使用yakit4.1 进入页面&#xff0c;点击这里进行配置默认端口80834.2 发送到模糊测试4…...

3分钟教你用Python+Appium实现自动化测试

一、环境准备 1.脚本语言&#xff1a;Python3.x IDE&#xff1a;安装Pycharm 2.安装Java JDK 、Android SDK 3.adb环境&#xff0c;path添加E:\Software\Android_SDK\platform-tools 4.安装Appium for windows&#xff0c;官网地址 Redirecting 点击下载按钮会到GitHub…...

qt的一些自绘控件

https://download.csdn.net/download/venice0708/88469835...

类图表示法

设计模式&#xff0c;用设计图表示的话&#xff0c;主要用到类图。常见UML类图如下&#xff1a; 1、类图&#xff1a;矩形框&#xff0c;代表一个类&#xff08;Class&#xff09;。类图分为三层&#xff0c;第一层显示类的名称&#xff0c;如果是抽象类&#xff0c;则用斜体显…...

大模型训练框架

一文搞定分布式训练&#xff1a;dataparallel、distirbuted、deepspeed、accelerate、transformers、horovod - 知乎代码地址&#xff1a;taishan1994/pytorch-distributed-NLP: pytorch分布式训练 (github.com)pytorch-distributed-NLPpytorch单机多卡分布式训练-中文文本分类…...

好用的Visio绘图文件工具 VSD Viewer最新 for mac

VSD Viewer是一款可以查看Microsoft Visio绘图文件的工具&#xff0c;适用于Windows和macOS操作系统。它具有以下优点&#xff1a; 直观易用&#xff1a;VSD Viewer的用户界面非常简单直观&#xff0c;易于使用。支持多种文件格式&#xff1a;VSD Viewer支持多种Visio文件格式…...

三代自动驾驶系统及主流科技公司自动驾驶技术方案简介

截止目前&#xff0c;按技术特点&#xff0c;自动驾驶技术大致经历了三代发展&#xff1a;第一代自动驾驶技术以后融合感知技术&#xff0c;高精度地图&#xff0c;基于惯导、GPS定位系统&#xff0c;预测模块&#xff0c;基于优化、搜索的规控等组成。第一代比较成熟的自动驾驶…...

mac安装nodejs,跑vue程序

1. 下载node.js for mac&#xff0c;地址&#xff1a;Node.js。一路安装就可以了&#xff0c;无需修改。 2. mac终端&#xff0c;查看node和npm的版本。 3. 配置环境变量&#xff0c; vim .bash_profile增加PATH$PATH:/usr/local/bin/ 4. 但是毕竟npm安装一些东西还是太慢了所…...

VC++程序崩溃时,使用Visual Studio静态分析dump文件

1、通过Visual Studio直接把Dump文件打开 2、点击【仅限本机进行调试】&#xff0c;启动Dump 3.1、本机调试启动后&#xff0c;如果程序运行模块和pdb文件在同一个目录的&#xff0c;直接定位到异常代码行 3.2、如果显示找不到pdb文件&#xff0c;则需要通过【新建路径】设置…...

RocketMQ延迟消息机制

两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数&#xff0c;对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后&#xf…...

QMC5883L的驱动

简介 本篇文章的代码已经上传到了github上面&#xff0c;开源代码 作为一个电子罗盘模块&#xff0c;我们可以通过I2C从中获取偏航角yaw&#xff0c;相对于六轴陀螺仪的yaw&#xff0c;qmc5883l几乎不会零飘并且成本较低。 参考资料 QMC5883L磁场传感器驱动 QMC5883L磁力计…...

c++ 面试题(1)-----深度优先搜索(DFS)实现

操作系统&#xff1a;ubuntu22.04 IDE:Visual Studio Code 编程语言&#xff1a;C11 题目描述 地上有一个 m 行 n 列的方格&#xff0c;从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子&#xff0c;但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...

【Java_EE】Spring MVC

目录 Spring Web MVC ​编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 ​编辑参数重命名 RequestParam ​编辑​编辑传递集合 RequestParam 传递JSON数据 ​编辑RequestBody ​…...

select、poll、epoll 与 Reactor 模式

在高并发网络编程领域&#xff0c;高效处理大量连接和 I/O 事件是系统性能的关键。select、poll、epoll 作为 I/O 多路复用技术的代表&#xff0c;以及基于它们实现的 Reactor 模式&#xff0c;为开发者提供了强大的工具。本文将深入探讨这些技术的底层原理、优缺点。​ 一、I…...

什么是Ansible Jinja2

理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具&#xff0c;可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板&#xff0c;允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板&#xff0c;并通…...

Java多线程实现之Thread类深度解析

Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...

20个超级好用的 CSS 动画库

分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码&#xff0c;而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库&#xff0c;可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画&#xff0c;可以包含在你的网页或应用项目中。 3.An…...

NPOI Excel用OLE对象的形式插入文件附件以及插入图片

static void Main(string[] args) {XlsWithObjData();Console.WriteLine("输出完成"); }static void XlsWithObjData() {// 创建工作簿和单元格,只有HSSFWorkbook,XSSFWorkbook不可以HSSFWorkbook workbook new HSSFWorkbook();HSSFSheet sheet (HSSFSheet)workboo…...

给网站添加live2d看板娘

给网站添加live2d看板娘 参考文献&#xff1a; stevenjoezhang/live2d-widget: 把萌萌哒的看板娘抱回家 (ノ≧∇≦)ノ | Live2D widget for web platformEikanya/Live2d-model: Live2d model collectionzenghongtu/live2d-model-assets 前言 网站环境如下&#xff0c;文章也主…...