


How does SpringBoot customize annotations to desensitize confidential fields?
关于数据脱敏,网上的文章都是硬编码规则,比如对身份证,手机号,邮件地址等固定写法脱敏。本文在此基础上,拓展动态从数据库查出涉密关键字执行脱敏操作。
数据脱敏:把系统里的一些敏感数据进行加密处理后再返回,达到保护隐私作用,实现效果图如下:
其实要实现上面的效果,可能最先想到的方法是直接改每个controller接口,在返回数据前做一次加密处理,当然这个方法肯定是非常捞的。这里推荐用注解来实现,即高效又优雅,省时省力,支持扩展。
其实解决方案大体上分两种:
在拿到数据时就已经脱敏了(如在 mysql 查询时用 insert 函数进行隐藏)
拿到数据后在序列化的时候再进行脱敏(如用 fastjson、jackson)
这里我所选用的方案是第二种,即在接口返回数据前,在序列化的时候对敏感字段值进行处理,并且选用 jackson 的序列化来实现(推荐)
1. 创建隐私数据类型枚举:PrivacyTypeEnum
import lombok.Getter; /** * 隐私数据类型枚举 * */ @Getter public enum PrivacyTypeEnum { /** * 自定义(此项需设置脱敏的范围) */ CUSTOMER, /** * 姓名 */ NAME, /** * 身份证号 */ ID_CARD, /** * 手机号 */ PHONE, /** * 邮箱 */ EMAIL, /** * 关键字 */ KEYWORD }
2. 创建自定义隐私注解:PrivacyEncrypt
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 自定义数据脱敏注解 */ @Target(ElementType.FIELD) // 作用在字段上 @Retention(RetentionPolicy.RUNTIME) // class文件中保留,运行时也保留,能通过反射读取到 @JacksonAnnotationsInside // 表示自定义自己的注解PrivacyEncrypt @JsonSerialize(using = PrivacySerializer.class) // 该注解使用序列化的方式 public @interface PrivacyEncrypt { /** * 脱敏数据类型(没给默认值,所以使用时必须指定type) */ PrivacyTypeEnum type(); /** * 前置不需要打码的长度 */ int prefixNoMaskLen() default 1; /** * 后置不需要打码的长度 */ int suffixNoMaskLen() default 1; /** * 用什么打码 */ String symbol() default "*"; }
3. 创建自定义序列化器:PrivacySerializer
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.zk.common.core.domain.enumerate.PrivacyTypeEnum; import com.zk.common.core.utils.PrivacyUtil; import java.io.IOException; import java.util.Objects; import lombok.AllArgsConstructor; import lombok.NoArgsConstructor; import org.apache.commons.lang3.StringUtils; @NoArgsConstructor @AllArgsConstructor public class PrivacySerializer extends JsonSerializer<String> implements ContextualSerializer { // 脱敏类型 private PrivacyTypeEnum privacyTypeEnum; // 前几位不脱敏 private Integer prefixNoMaskLen; // 最后几位不脱敏 private Integer suffixNoMaskLen; // 用什么打码 private String symbol; @Override public void serialize(final String origin, final JsonGenerator jsonGenerator, final SerializerProvider serializerProvider) throws IOException { if (StringUtils.isNotBlank(origin) && null != privacyTypeEnum) { switch (privacyTypeEnum) { case CUSTOMER: jsonGenerator.writeString(PrivacyUtil.desValue(origin, prefixNoMaskLen, suffixNoMaskLen, symbol)); break; case NAME: jsonGenerator.writeString(PrivacyUtil.hideChineseName(origin)); break; case ID_CARD: jsonGenerator.writeString(PrivacyUtil.hideIDCard(origin)); break; case PHONE: jsonGenerator.writeString(PrivacyUtil.hidePhone(origin)); break; case EMAIL: jsonGenerator.writeString(PrivacyUtil.hideEmail(origin)); break; default: throw new IllegalArgumentException("unknown privacy type enum " + privacyTypeEnum); } } } @Override public JsonSerializer<?> createContextual(final SerializerProvider serializerProvider, final BeanProperty beanProperty) throws JsonMappingException { if (beanProperty != null) { if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) { PrivacyEncrypt privacyEncrypt = beanProperty.getAnnotation(PrivacyEncrypt.class); if (privacyEncrypt == null) { privacyEncrypt = beanProperty.getContextAnnotation(PrivacyEncrypt.class); } if (privacyEncrypt != null) { return new PrivacySerializer(privacyEncrypt.type(), privacyEncrypt.prefixNoMaskLen(), privacyEncrypt.suffixNoMaskLen(), privacyEncrypt.symbol()); } } return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty); } return serializerProvider.findNullValueSerializer(null); } }
这里是具体的实现过程,因为要脱敏的数据都是 String 类型的,所以继承 JsonSerializer 时的类型填 String
重写的 serialize 方法是实现脱敏的核心,根据类型 type 的不同去设置序列化后的值
重写的 createContextual 方法就是去读取我们自定义的 PrivacyEncrypt 注解,打造一个上下文的环境。
4. 隐私数据隐藏工具类:PrivacyUtil
public class PrivacyUtil { /** * 隐藏手机号中间四位 */ public static String hidePhone(String phone) { return phone.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2"); } /** * 隐藏邮箱 */ public static String hideEmail(String email) { return email.replaceAll("(\\w?)(\\w+)(\\w)(@\\w+\\.[a-z]+(\\.[a-z]+)?)", "$1****$3$4"); } /** * 隐藏身份证 */ public static String hideIDCard(String idCard) { return idCard.replaceAll("(\\d{4})\\d{10}(\\w{4})", "$1*****$2"); } /** * 【中文姓名】只显示第一个汉字,其他隐藏为星号,比如:任** */ public static String hideChineseName(String chineseName) { if (chineseName == null) { return null; } return desValue(chineseName, 1, 0, "*"); } /** * 隐藏关键字 * * @param context * @return */ public static String hideKeyWord(String context) { // SpringUtil.getBean()方法来源于hutool提供的工具类。网上也有很多spring静态获取bean的方法,没有hutool依赖的,可自行实现。 // 实际项目中,请替换为自己的Mapper或者Dao。这里是为大家提供动态获取数据库的示例写法。 SysSecurityFieldMapper sysSecurityFieldMapper = SpringUtil.getBean(SysSecurityFieldMapper.class); List<SysSecurityFieldEntity> privacyKeyWord = sysSecurityFieldMapper.selectList(new LambdaQueryWrapper<SysSecurityFieldEntity>() .eq(SysSecurityFieldEntity::getStatus, SecurityFieldConstant.STATUS_NORMAL)); if (CollectionUtils.isEmpty(privacyKeyWord)) { return context; } for (int i = 0; i < privacyKeyWord.size(); i++) { if (context.contains(privacyKeyWord.get(i).getKeyWord())) { context = context.replaceAll(privacyKeyWord.get(i).getKeyWord(), "***"); } } return context; } // /** // * 【身份证号】显示前4位, 后2位 // */ // public static String hideIdCard(String idCard) { // return desValue(idCard, 4, 2, "*"); // } // /** // * 【手机号码】前三位,后四位,其他隐藏。 // */ // public static String hidePhone(String phone) { // return desValue(phone, 3, 4, "*"); // } /** * 对字符串进行脱敏操作 * @param origin 原始字符串 * @param prefixNoMaskLen 左侧需要保留几位明文字段 * @param suffixNoMaskLen 右侧需要保留几位明文字段 * @param maskStr 用于遮罩的字符串, 如'*' * @return 脱敏后结果 */ public static String desValue(String origin, int prefixNoMaskLen, int suffixNoMaskLen, String maskStr) { if (origin == null) { return null; } StringBuilder sb = new StringBuilder(); for (int i = 0, n = origin.length(); i < n; i++) { if (i < prefixNoMaskLen) { sb.append(origin.charAt(i)); continue; } if (i > (n - suffixNoMaskLen - 1)) { sb.append(origin.charAt(i)); continue; } sb.append(maskStr); } return sb.toString(); } public static void main(String[] args) { System.out.println(hideChineseName("张三三")); } }
这个工具类其实可以自己定,根据自己的业务去扩展,提一点:
在自定义注解 PrivacyEncrypt 里,只有 type 的值为 PrivacyTypeEnum.CUSTOMER(自定义)时,才需要指定脱敏范围,即 prefixNoMaskLen 和 suffixNoMaskLen 的值,像邮箱、手机号这种隐藏格式都采用固定的
hideKeyWord() 方法中,SpringUtil.getBean()方法来源于hutool提供的工具类。网上也有很多spring静态获取bean的方法,没有hutool依赖的,可自行实现。实际项目中,请替换为自己的Mapper或者Dao。这里是为大家提供动态获取数据库的示例写法。
5. 注解使用
直接在需要脱敏的字段上加上注解,指定 type 值即可,如下:
@Data public class People { private Integer id; private String name; private Integer sex; private Integer age; @PrivacyEncrypt(type = PrivacyTypeEnum.PHONE) // 隐藏手机号 private String phone; @PrivacyEncrypt(type = PrivacyTypeEnum.EMAIL) // 隐藏邮箱 private String email; private String sign; }
测试效果图如下:
The above is the detailed content of How does SpringBoot customize annotations to desensitize confidential fields?. For more information, please follow other related articles on the PHP Chinese website!

Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

AI Hentai Generator
Generate AI Hentai for free.

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics

Introduction to Jasypt Jasypt is a java library that allows a developer to add basic encryption functionality to his/her project with minimal effort and does not require a deep understanding of how encryption works. High security for one-way and two-way encryption. , standards-based encryption technology. Encrypt passwords, text, numbers, binaries... Suitable for integration into Spring-based applications, open API, for use with any JCE provider... Add the following dependency: com.github.ulisesbocchiojasypt-spring-boot-starter2. 1.1Jasypt benefits protect our system security. Even if the code is leaked, the data source can be guaranteed.

1. Redis implements distributed lock principle and why distributed locks are needed. Before talking about distributed locks, it is necessary to explain why distributed locks are needed. The opposite of distributed locks is stand-alone locks. When we write multi-threaded programs, we avoid data problems caused by operating a shared variable at the same time. We usually use a lock to mutually exclude the shared variables to ensure the correctness of the shared variables. Its scope of use is in the same process. If there are multiple processes that need to operate a shared resource at the same time, how can they be mutually exclusive? Today's business applications are usually microservice architecture, which also means that one application will deploy multiple processes. If multiple processes need to modify the same row of records in MySQL, in order to avoid dirty data caused by out-of-order operations, distribution needs to be introduced at this time. The style is locked. Want to achieve points

Usage scenario 1. The order was placed successfully but the payment was not made within 30 minutes. The payment timed out and the order was automatically canceled. 2. The order was signed and no evaluation was conducted for 7 days after signing. If the order times out and is not evaluated, the system defaults to a positive rating. 3. The order is placed successfully. If the merchant does not receive the order for 5 minutes, the order is cancelled. 4. The delivery times out, and push SMS reminder... For scenarios with long delays and low real-time performance, we can Use task scheduling to perform regular polling processing. For example: xxl-job Today we will pick

Springboot reads the file, but cannot access the latest development after packaging it into a jar package. There is a situation where springboot cannot read the file after packaging it into a jar package. The reason is that after packaging, the virtual path of the file is invalid and can only be accessed through the stream. Read. The file is under resources publicvoidtest(){Listnames=newArrayList();InputStreamReaderread=null;try{ClassPathResourceresource=newClassPathResource("name.txt");Input

SpringBoot and SpringMVC are both commonly used frameworks in Java development, but there are some obvious differences between them. This article will explore the features and uses of these two frameworks and compare their differences. First, let's learn about SpringBoot. SpringBoot was developed by the Pivotal team to simplify the creation and deployment of applications based on the Spring framework. It provides a fast, lightweight way to build stand-alone, executable

1. Customize RedisTemplate1.1, RedisAPI default serialization mechanism. The API-based Redis cache implementation uses the RedisTemplate template for data caching operations. Here, open the RedisTemplate class and view the source code information of the class. publicclassRedisTemplateextendsRedisAccessorimplementsRedisOperations, BeanClassLoaderAware{//Declare key, Various serialization methods of value, the initial value is empty @NullableprivateRedisSe

When Springboot+Mybatis-plus does not use SQL statements to perform multi-table adding operations, the problems I encountered are decomposed by simulating thinking in the test environment: Create a BrandDTO object with parameters to simulate passing parameters to the background. We all know that it is extremely difficult to perform multi-table operations in Mybatis-plus. If you do not use tools such as Mybatis-plus-join, you can only configure the corresponding Mapper.xml file and configure The smelly and long ResultMap, and then write the corresponding sql statement. Although this method seems cumbersome, it is highly flexible and allows us to

In projects, some configuration information is often needed. This information may have different configurations in the test environment and the production environment, and may need to be modified later based on actual business conditions. We cannot hard-code these configurations in the code. It is best to write them in the configuration file. For example, you can write this information in the application.yml file. So, how to get or use this address in the code? There are 2 methods. Method 1: We can get the value corresponding to the key in the configuration file (application.yml) through the ${key} annotated with @Value. This method is suitable for situations where there are relatively few microservices. Method 2: In actual projects, When business is complicated, logic
