How to add desensitization function in SpringBoot
New desensitization function in SpringBoot project
Project background
We are currently developing a SpringBoot project, which has a Web client and a WeChat applet client. The web terminal is provided for staff to use, and the WeChat applet is provided for the public to make reservations. Some sensitive data in the project needs to be desensitized and passed to the WeChat applet for public viewing.
Project Requirements Description
In the project, since there are two users, the data permissions for the two ends are not the same. All data can be viewed on the web side, while only desensitized data can be viewed on the mini program.
Need to develop a universal desensitization function
Manual desensitization operation
Supports multiple A variety of objects,
supports different fields, and desensitizes specified fields
The desensitization methods of fields are diverse
The desensitization method of the field can be customized
Project solution
1. Solution
Use Annotation method
, to support multiple desensitization operations on specified fields, different fields, and can be separated from the object.
Use tool objects and pass generic parameters to support desensitization operations on different objects.
2. Implementation code
2.1 Annotation Sensitive
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 自定义数据脱敏 * * 例如: 身份证,手机号等信息进行模糊处理 * * @author lzddddd */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Sensitive { /** * 脱敏数据类型 */ SensitiveType type() default SensitiveType.CUSTOMER; /** * 前置不需要打码的长度 */ int prefixNoMaskLen() default 0; /** * 后置不需要打码的长度 */ int suffixNoMaskLen() default 0; /** * 用什么打码 */ String symbol() default "*"; }
2.1 Desensitized type enumeration SensitiveType
public enum SensitiveType { /** * 自定义 */ CUSTOMER, /** * 名称 **/ CHINESE_NAME, /** * 身份证证件号 **/ ID_CARD_NUM, /** * 手机号 **/ MOBILE_PHONE, /** * 固定电话 */ FIXED_PHONE, /** * 密码 **/ PASSWORD, /** * 银行卡号 */ BANKCARD, /** * 邮箱 */ EMAIL, /** * 地址 */ ADDRESS, }
2.3 Desensitized tool DesensitizedUtils
import com.ruoyi.common.annotation.Sensitive; import com.ruoyi.common.constant.HttpStatus; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.enums.SensitiveType; import lombok.extern.slf4j.Slf4j; import java.lang.reflect.Field; import java.util.*; @Slf4j public class DesensitizedUtils<T> { /** * 脱敏数据列表 */ private List<T> list; /** * 注解列表 */ private List<Object[]> fields; /** * 实体对象 */ public Class<T> clazz; public DesensitizedUtils(Class<T> clazz) { this.clazz = clazz; } /** * 初始化数据 * * @param list 需要处理数据 */ public void init(List<T> list){ if (list == null) { list = new ArrayList<T>(); } this.list = list; // 得到所有定义字段 createSensitiveField(); } /** * 初始化数据 * * @param t 需要处理数据 */ public void init(T t){ list = new ArrayList<T>(); if (t != null) { list.add(t); } // 得到所有定义字段 createSensitiveField(); } /** * 得到所有定义字段 */ private void createSensitiveField() { this.fields = new ArrayList<Object[]>(); List<Field> tempFields = new ArrayList<>(); tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields())); tempFields.addAll(Arrays.asList(clazz.getDeclaredFields())); for (Field field : tempFields) { // 单注解 if (field.isAnnotationPresent(Sensitive.class)) { putToField(field, field.getAnnotation(Sensitive.class)); } // 多注解 // if (field.isAnnotationPresent(Excels.class)) // { // Excels attrs = field.getAnnotation(Excels.class); // Excel[] excels = attrs.value(); // for (Excel excel : excels) // { // putToField(field, excel); // } // } } } /** * 对list数据源将其里面的数据进行脱敏处理 * * @param list * @return 结果 */ public AjaxResult desensitizedList(List<T> list){ if (list == null){ return AjaxResult.error("脱敏数据为空"); } // 初始化数据 this.init(list); int failTimes = 0; for (T t: this.list) { if ((Integer)desensitization(t).get("code") != HttpStatus.SUCCESS){ failTimes++; } } if (failTimes >0){ return AjaxResult.error("脱敏操作中出现失败",failTimes); } return AjaxResult.success(); } /** * 放到字段集合中 */ private void putToField(Field field, Sensitive attr) { if (attr != null) { this.fields.add(new Object[] { field, attr }); } } /** * 脱敏:JavaBean模式脱敏 * * @param t 需要脱敏的对象 * @return */ public AjaxResult desensitization(T t) { if (t == null){ return AjaxResult.error("脱敏数据为空"); } // 初始化数据 init(t); try { // 遍历处理需要进行 脱敏的字段 for (Object[] os : fields) { Field field = (Field) os[0]; Sensitive sensitive = (Sensitive) os[1]; // 设置实体类私有属性可访问 field.setAccessible(true); desensitizeField(sensitive,t,field); } return AjaxResult.success(t); } catch (Exception e) { e.printStackTrace(); log.error("日志脱敏处理失败,回滚,详细信息:[{}]", e); return AjaxResult.error("脱敏处理失败",e); } } /** * 对类的属性进行脱敏 * * @param attr 脱敏参数 * @param vo 脱敏对象 * @param field 脱敏属性 * @return */ private void desensitizeField(Sensitive attr, T vo, Field field) throws IllegalAccessException { if (attr == null || vo == null || field == null){ return ; } // 读取对象中的属性 Object value = field.get(vo); SensitiveType sensitiveType = attr.type(); int prefixNoMaskLen = attr.prefixNoMaskLen(); int suffixNoMaskLen = attr.suffixNoMaskLen(); String symbol = attr.symbol(); //获取属性后现在默认处理的是String类型,其他类型数据可扩展 Object val = convertByType(sensitiveType, value, prefixNoMaskLen, suffixNoMaskLen, symbol); field.set(vo, val); } /** * 以类的属性的get方法方法形式获取值 * * @param o 对象 * @param name 属性名 * @return value * @throws Exception */ private Object getValue(Object o, String name) throws Exception { if (StringUtils.isNotNull(o) && StringUtils.isNotEmpty(name)) { Class<?> clazz = o.getClass(); Field field = clazz.getDeclaredField(name); field.setAccessible(true); o = field.get(o); } return o; } /** * 根据不同注解类型处理不同字段 */ private Object convertByType(SensitiveType sensitiveType, Object value, int prefixNoMaskLen, int suffixNoMaskLen, String symbol) { switch (sensitiveType) { case CUSTOMER: value = customer(value, prefixNoMaskLen, suffixNoMaskLen, symbol); break; case CHINESE_NAME: value = chineseName(value, symbol); break; case ID_CARD_NUM: value = idCardNum(value, symbol); break; case MOBILE_PHONE: value = mobilePhone(value, symbol); break; case FIXED_PHONE: value = fixedPhone(value, symbol); break; case PASSWORD: value = password(value, symbol); break; case BANKCARD: value = bankCard(value, symbol); break; case EMAIL: value = email(value, symbol); break; case ADDRESS: value = address(value, symbol); break; } return value; } /*--------------------------下面的脱敏工具类也可以单独对某一个字段进行使用-------------------------*/ /** * 【自定义】 根据设置进行配置 * * @param value 需处理数据 * @param symbol 填充字符 * @return 脱敏后数据 */ public Object customer(Object value, int prefixNoMaskLen, int suffixNoMaskLen, String symbol) { //针对字符串的处理 if (value instanceof String){ return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol); } return value; } /** * 对字符串进行脱敏处理 * * @param s 需处理数据 * @param prefixNoMaskLen 开头展示字符长度 * @param suffixNoMaskLen 结尾展示字符长度 * @param symbol 填充字符 * @return */ private String handleString(String s, int prefixNoMaskLen, int suffixNoMaskLen, String symbol){ // 是否为空 if (StringUtils.isBlank(s)) { return ""; } // 如果设置为空之类使用 * 代替 if (StringUtils.isBlank(symbol)){ symbol = "*"; } // 对长度进行判断 int length = s.length(); if (length > prefixNoMaskLen + suffixNoMaskLen){ String namePrefix = StringUtils.left(s, prefixNoMaskLen); String nameSuffix = StringUtils.right(s, suffixNoMaskLen); s = StringUtils.rightPad(namePrefix, StringUtils.length(s) - suffixNoMaskLen, symbol).concat(nameSuffix); } return s; } /** * 【中文姓名】只显示第一个汉字,其他隐藏为2个星号,比如:李** * * @param value 需处理数据 * @param symbol 填充字符 * @return 脱敏后数据 */ public String chineseName(Object value, String symbol) { //针对字符串的处理 if (value instanceof String){ // 对前后长度进行设置 默认 开头只展示一个字符 int prefixNoMaskLen = 1; int suffixNoMaskLen = 0; return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol); } return ""; } /** * 【身份证号】显示最后四位,其他隐藏。共计18位或者15位,比如:*************1234 * * @param value 需处理数据 * @param symbol 填充字符 * @return 脱敏后数据 */ public String idCardNum(Object value, String symbol) { //针对字符串的处理 if (value instanceof String){ // 对前后长度进行设置 默认 结尾只展示四个字符 int prefixNoMaskLen = 0; int suffixNoMaskLen = 4; return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol); } return ""; } /** * 【固定电话】 显示后四位,其他隐藏,比如:*******3241 * * @param value 需处理数据 * @param symbol 填充字符 * @return 脱敏后数据 */ public String fixedPhone(Object value, String symbol) { //针对字符串的处理 if (value instanceof String){ // 对前后长度进行设置 默认 结尾只展示四个字符 int prefixNoMaskLen = 0; int suffixNoMaskLen = 4; return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol); } return ""; } /** * 【手机号码】前三位,后四位,其他隐藏,比如:135****6810 * * @param value 需处理数据 * @param symbol 填充字符 * @return 脱敏后数据 */ public String mobilePhone(Object value, String symbol) { //针对字符串的处理 if (value instanceof String){ // 对前后长度进行设置 默认 开头只展示三个字符 结尾只展示四个字符 int prefixNoMaskLen = 3; int suffixNoMaskLen = 4; return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol); } return ""; } /** * 【地址】只显示到地区,不显示详细地址,比如:湖南省长沙市岳麓区*** * 只能处理 省市区的数据 * * @param value 需处理数据 * @param symbol 填充字符 * @return */ public String address(Object value, String symbol) { //针对字符串的处理 if (value instanceof String){ // 对前后长度进行设置 默认 开头只展示九个字符 int prefixNoMaskLen = 9; int suffixNoMaskLen = 0; return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol); } return ""; } /** * 【电子邮箱】 邮箱前缀仅显示第一个字母,前缀其他隐藏,用星号代替,@及后面的地址显示,比如:d**@126.com * * @param value 需处理数据 * @param symbol 填充字符 * @return 脱敏后数据 */ public String email(Object value, String symbol) { //针对字符串的处理 if (value instanceof String){ // 对前后长度进行设置 默认 开头只展示一个字符 结尾只展示@及后面的地址 int prefixNoMaskLen = 1; int suffixNoMaskLen = 4; String s = (String) value; if (StringUtils.isBlank(s)) { return ""; } // 获取最后一个@ int lastIndex = StringUtils.lastIndexOf(s, "@"); if (lastIndex <= 1) { return s; } else { suffixNoMaskLen = s.length() - lastIndex; } return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol); } return ""; } /** * 【银行卡号】前六位,后四位,其他用星号隐藏每位1个星号,比如:6222600**********1234 * * @param value 需处理数据 * @param symbol 填充字符 * @return 脱敏后数据 */ public String bankCard(Object value, String symbol) { //针对字符串的处理 if (value instanceof String){ // 对前后长度进行设置 默认 开头只展示六个字符 结尾只展示四个字符 int prefixNoMaskLen = 6; int suffixNoMaskLen = 4; return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol); } return ""; } /** * 【密码】密码的全部字符都用*代替,比如:****** * * @param value 需处理数据 * @param symbol 填充字符 * @return */ public String password(Object value,String symbol) { //针对字符串的处理 if (value instanceof String){ // 对前后长度进行设置 默认 开头只展示六个字符 结尾只展示四个字符 int prefixNoMaskLen = 0; int suffixNoMaskLen = 0; return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol); } return ""; } }
3 Usage examples
3.1 Objects to be annotated
public class User { private static final long serialVersionUID = 1L; /** 普通用户ID */ private Long userId; /** 昵称 */ @Sensitive(type = SensitiveType.CUSTOMER,prefixNoMaskLen = 2,suffixNoMaskLen = 1) private String nickName; /** 姓名 */ @Sensitive(type = SensitiveType.CHINESE_NAME) private String userName; /** 身份证 */ @Sensitive(type = SensitiveType.ID_CARD_NUM) private String identityCard; /** 手机号码 */ @Sensitive(type = SensitiveType.MOBILE_PHONE) private String phoneNumber; }
3.2 Desensitization operation
// 脱敏对象 User user = new User(); ...... DesensitizedUtils<User> desensitizedUtils = new DesensitizedUtils<>(User.class); desensitizedUtils.desensitization(user); //脱敏队列 List<User> users = new ArrayList<>(); ...... DesensitizedUtils<User> desensitizedUtils = new DesensitizedUtils<>(User.class); desensitizedUtils.desensitizedList(users);
The above is the detailed content of How to add desensitization function in SpringBoot. 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

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

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.

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

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

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

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

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

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
