SpringBoot项目中新增脱敏功能的实例代码

目录
  • SpringBoot项目中新增脱敏功能
    • 项目背景
    • 项目需求描述
    • 项目解决方案
    • 1. 解决方案
    • 2. 实现代码
      • 2.1 注解 Sensitive
      • 2.1 脱敏类型枚举 SensitiveType
      • 2.3 脱敏工具 DesensitizedUtils
    • 3 使用实例
      • 3.1 需注解对象
      • 3.2 脱敏操作

SpringBoot项目中新增脱敏功能

项目背景

目前正在开发一个SpringBoot项目,此项目有Web端和微信小程序端。web端提供给工作人员使用,微信小程序提供给群众进行预约操作。项目中有部分敏感数据需要脱敏传递给微信小程序,给与群众查看。

项目需求描述

项目中,由于使用端有两个,对于两个端的数据权限并不一样。Web端可以查看所有数据,小程序端只能查看脱敏后的数据。

需要开发一个通用脱敏功能

  • 手动进行脱敏操作
  • 支持多种对象,
  • 支持不同字段,并脱敏指定字段
  • 字段的脱敏方式多样
  • 字段的脱敏方式可自定义

项目解决方案

1. 解决方案

使用注解方式,来支持对指定字段,不同字段,多种脱敏操作,并可以脱离对象。
使用工具对象,通过泛型传参,来支持对不同对象的脱敏操作。

2. 实现代码

2.1 注解 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 脱敏类型枚举 SensitiveType

public enum SensitiveType {

    /**
     * 自定义
     */
    CUSTOMER,
    /**
     * 名称
     **/
    CHINESE_NAME,
    /**
     * 身份证证件号
     **/
    ID_CARD_NUM,
    /**
     * 手机号
     **/
    MOBILE_PHONE,
    /**
     * 固定电话
     */
    FIXED_PHONE,
    /**
     * 密码
     **/
    PASSWORD,
    /**
     * 银行卡号
     */
    BANKCARD,
    /**
     * 邮箱
     */
    EMAIL,
    /**
     * 地址
     */
    ADDRESS,

}

2.3 脱敏工具 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 使用实例

3.1 需注解对象

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 脱敏操作

		// 脱敏对象
		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);

到此这篇关于SpringBoot项目中新增脱敏功能的文章就介绍到这了,更多相关SpringBoot脱敏内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Springboot敏感字段脱敏的实现思路

    生产环境用户的隐私数据,比如手机号.身份证或者一些账号配置等信息,入库时都要进行不落地脱敏,也就是在进入我们系统时就要实时的脱敏处理. 用户数据进入系统,脱敏处理后持久化到数据库,用户查询数据时还要进行反向解密.这种场景一般需要全局处理,那么用AOP切面来实现在适合不过了. 首先自定义两个注解@EncryptField.@EncryptMethod分别用在字段属性和方法上,实现思路很简单,只要方法上应用到@EncryptMethod注解,则检查入参字段是否标注@EncryptField注解,有则

  • Springboot之日志、配置文件、接口数据如何脱敏

    目录 一.前言 二.配置文件如何脱敏? 1. 添加依赖 2. 配置秘钥 3. 生成加密后的数据 4. 将加密后的密文写入配置 5. 总结 三.接口返回数据如何脱敏? 1. 自定义一个Jackson注解 2. 定制脱敏策略 3. 定制JSON序列化实现 4. 定义Person类,对其数据脱敏 5. 模拟接口测试 6. 总结 四.日志文件如何数据脱敏? 1. 添加log4j2日志依赖 2. 在/resource目录下新建log4j2.xml配置 3. 自定义PatternLayout实现数据脱敏 4

  • SpringBoot 自定义注解之脱敏注解详解

    目录 自定义注解之脱敏注解 一.脱敏后的效果 二.代码 1.脱敏注解 2.定义脱敏类型 3.敏感工具类 4.脱敏序列化信息 小结一下 自己手写的一个高效自定义字符串脱敏注解 自己写了个 仅供参考 自定义注解之脱敏注解 数据脱敏是指对某些敏感信息通过脱敏规则进行数据的变形,实现敏感隐私数据的可靠保护.需求是把返回到前端的数据进行脱敏,以免造成隐私信息的泄露. 一.脱敏后的效果 这样显示很不好吧,所有信息都泄露了 这样就很好了吧 二.代码 1.脱敏注解 @Retention(RetentionPol

  • SpringBoot项目中新增脱敏功能的实例代码

    目录 SpringBoot项目中新增脱敏功能 项目背景 项目需求描述 项目解决方案 1. 解决方案 2. 实现代码 2.1 注解 Sensitive 2.1 脱敏类型枚举 SensitiveType 2.3 脱敏工具 DesensitizedUtils 3 使用实例 3.1 需注解对象 3.2 脱敏操作 SpringBoot项目中新增脱敏功能 项目背景 目前正在开发一个SpringBoot项目,此项目有Web端和微信小程序端.web端提供给工作人员使用,微信小程序提供给群众进行预约操作.项目中有

  • SpringBoot项目中接口防刷的完整代码

    一.自定义注解 import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * @author Yang * @version 1.0 * @date 2021/2/22

  • 在Vue项目中使用d3.js的实例代码

    之前写一个 Demo里面 有些东西要使用d3实现一些效果 但是在很多论坛找资源都找不到可以在Vue里面使用D3.js的方法,npm 上面的D3相对来说 可以说是很不人性化了 完全没有说 在webpack上怎么使用D3.js 最后折腾很久 看到某位外国大佬 看他的案例 成功的实现了在Vue项目里面实现D3的使用 首先安装 npm install d3 --save-dev 以防万一,然后看package.json 安装完成 在我们开始之前,让我们渲染一个Vue组件,它使用常规的D3 DOM操作呈现

  • django项目中使用手机号登录的实例代码

    本文使用聚合数据的短信接口,需要先获取到申请接口的appkey和模板id 项目目录下创建ubtils文件夹,定义返回随机验证码和调取短信接口的函数 function.py文件 import random import re # 随机数 def range_num(num): # 定义一个种子,从这里面随机拿出一个值,可以是字母 seeds = "1234567890" # 定义一个空列表,每次循环,将拿到的值,加入列表 random_num = [] # choice函数:每次从see

  • SpringBoot项目中使用Groovy脚本的示例代码

    目录 1. 引入依赖 2. 使用脚本引擎运行groovy脚本 3.思考 SpringBoot+Groovy运行动态脚本 GroovyClassLoader方式 GroovyScriptEngine方式 变量绑定 最近项目中遇到了这样的需求:需要检查一个表的某些字段,是否为空,或者是否符合预期规则:比如大于0,或者在某个范围内.考虑将表名和字段名配置在数据库中,然后规则使用Groovy来写,比较灵活. 1. 引入依赖 <dependency> <groupId>org.codehau

  • Java Web项目中实现文件下载功能的实例教程

    需求:实现一个具有文件下载功能的网页,主要下载压缩包和图片 两种实现方法: 一:通过超链接实现下载 在HTML网页中,通过超链接链接到要下载的文件的地址 <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Insert title here</title> </head> <body> <h1>通过链接下载文件&

  • 如何在ASP.NET Core中给上传图片功能添加水印实例代码

    前言 因某些原因需要在图片上添加文字水印.图片水印,所以这里做个简单的记录.下面话不多说了,来一起看看详细的实现过程吧 实现方法: 在传统的.NET框架中,我们给图片添加水印有的是通过HttpModules或者是HttpHandler,然后可以通过以下代码添加水印: var image = new WebImage(imageBytes); image.AddTextWatermark( Settings.Instance.WatermarkText, "White", Setting

  • 关于在IDEA中SpringBoot项目中activiti工作流的使用详解

    记录一下工作流的在Springboot中的使用,,顺便写个demo,概念,什么东西的我就不解释了,如有问题欢迎各位大佬指导一下. 1.创建springboot项目后导入依赖 <dependency> <groupId>org.activiti</groupId> <artifactId>activiti-spring-boot-starter-basic</artifactId> <version>6.0.0</version&

  • 在SpringBoot项目中的使用Swagger的方法示例

    一. 首先Swagger是什么? Swagger 是一个规范和完整的框架,用于生成.描述.调用和可视化 RESTful 风格的 Web 服务.总体目标是使客户端和文件系统作为服务器以同样的速度来更新.文件的方法,参数和模型紧密集成到服务器端的代码,允许API来始终保持同步.Swagger官方API文档:https://swagger.io/ 作用:   1. 接口的文档在线自动生成.   2. 功能测试. Swagger的主见介绍:    Swagger Codegen: 通过Codegen 可

  • 最新MySql8.27主从复制及SpringBoot项目中的读写分离实战教程

    目录 最新MySql8.27主从复制以及SpringBoot项目中的读写分离实战 1.MySql主从复制 2.配置-主库Master 3.配置-从库Slave 3.主从复制测试 4.读写分离案例 4.1.Sharding-JDBC框架介绍 最新MySql8.27主从复制以及SpringBoot项目中的读写分离实战 1.MySql主从复制 MySQL主从复制是一个异步的复制过程,底层是基于MySQL1数据库自带的二进制日志功能.就是一台或多台MySQL数据库(slave,即从库)从另一台ySQL数

随机推荐