Java Springboot切面+注解实现数据脱敏

1. 业务概述

随着互联网发展日益繁盛,数据安全也越来越受到人们重视,数据脱敏一方面为了数据安全,另一方面也是为了符合法务合规规范。
本文主要实现特殊字段脱敏响应,通过切面+注解实现数据脱敏。
通过切面获取指定脱敏类型,实现将数据统一脱敏返回。

2. 设计编码

2.1 脱敏类型枚举

package com.example.category.desen;

/**
 * 脱敏类型枚举
 *
 * @author zrj
 * @since 2021/8/16
 **/
public enum SensitiveTypeEnum {
    /**
     * 中文名
     */
    CHINESE_NAME,
    /**
     * 身份证号
     */
    ID_CARD,
    /**
     * 座机号
     */
    FIXED_PHONE,
    /**
     * 手机号
     */
    MOBILE_PHONE,
    /**
     * 地址
     */
    ADDRESS,
    /**
     * 电子邮件
     */
    EMAIL,
    /**
     * 银行卡
     */
    BANK_CARD,
    /**
     * 公司开户银行联号
     */
    CNAPS_CODE;
}

2.2 脱敏注解

package com.example.category.desen;

import java.lang.annotation.*;

/**
 * 脱敏注解
 *
 * @author zrj
 * @since 2021/8/16
 **/
@Inherited
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Desensitized {
    /**
     * 脱敏类型(规则)
     */
    SensitiveTypeEnum type();
}

2.3 脱敏工具类

package com.example.category.desen;

import com.example.category.entity.Response;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.*;

/**
 * 脱敏工具类
 *
 * @author zrj
 * @since 2021/8/16
 **/
@Slf4j
public class DesensitizedUtil {

    /**
     * 脱敏工具开关,0:关闭,1:开启
     */
    public static final String DESENT_STATUS = "1";

    /**
     * 脱敏数据
     *
     * @param obj
     * @return void
     */
    public static void desentData(Object obj) throws IllegalAccessException {
        if (null == obj) {
            return;
        }

        //如果是原始类型,则忽略处理
        if (obj.getClass().isPrimitive()) {
            return;
        }
        // 是否是接口
        if (obj.getClass().isInterface()) {
            return;
        }

        Object data = null;
        Class<?> clazz = null;
        //如果是通用的分页响应对象,则对该对象内部的List<T>进行脱敏
        if (obj.getClass().equals(Response.class)) {
            data = ((Response) obj).getData();//这里是自定义返回对象
            clazz = data.getClass();
            if (null == clazz) {
                return;
            }
        }

        // 获取所有属性
        Field[] fields = clazz.getDeclaredFields();
        while (null != clazz.getSuperclass() && !Object.class.equals(clazz.getSuperclass())) {
            fields = (Field[]) ArrayUtils.addAll(fields, clazz.getSuperclass().getDeclaredFields());
            clazz = clazz.getSuperclass();
        }

        if (null == fields && fields.length == 0) {
            return;
        }

        for (Field field : fields) {
            field.setAccessible(true);
            if (null == field) {
                return;
            }
            Object value = field.get(data);
            if (null != value) {
                Class<?> type = value.getClass();

                // 1.处理子属性,包括集合中的
                if (type.isArray()) {
                    int len = Array.getLength(value);
                    for (int i = 0; i < len; i++) {
                        Object arrayObject = Array.get(value, i);
                        DesensitizedUtil.desentData(arrayObject);
                    }
                } else if (value instanceof Collection<?>) {
                    Collection<?> c = (Collection<?>) value;
                    Iterator<?> it = c.iterator();
                    while (it.hasNext()) {
                        Object collectionObj = it.next();
                        DesensitizedUtil.desentData(collectionObj);
                    }
                } else if (value instanceof Map<?, ?>) {
                    Map<?, ?> m = (Map<?, ?>) value;
                    Set<?> set = m.entrySet();
                    for (Object o : set) {
                        Map.Entry<?, ?> entry = (Map.Entry<?, ?>) o;
                        Object mapVal = entry.getValue();
                        DesensitizedUtil.desentData(mapVal);
                    }
                } else if (!type.isPrimitive()
                        && !StringUtils.startsWith(type.getPackage().getName(), "javax.")
                        && !StringUtils.startsWith(type.getPackage().getName(), "java.")
                        && !StringUtils.startsWith(field.getType().getName(), "javax.")
                        && !StringUtils.startsWith(field.getName(), "java.")) {
                    DesensitizedUtil.desentData(type);
                }
            }

            // 2. 处理自身的属性
            Desensitized annotation = field.getDeclaredAnnotation(Desensitized.class);
            if (field.getType().equals(String.class) && null != annotation) {
                String valueStr = (String) field.get(data);
                if (StringUtils.isNotBlank(valueStr)) {
                    switch (annotation.type()) {
                        case CHINESE_NAME: {
                            field.set(data, DesensitizedUtil.chineseName(valueStr));
                            break;
                        }
                        case ID_CARD: {
                            field.set(data, DesensitizedUtil.idCardNum(valueStr));
                            break;
                        }
                        case FIXED_PHONE: {
                            field.set(data, DesensitizedUtil.fixedPhone(valueStr));
                            break;
                        }
                        case MOBILE_PHONE: {
                            field.set(data, DesensitizedUtil.mobilePhone(valueStr));
                            break;
                        }
                        case ADDRESS: {
                            field.set(data, DesensitizedUtil.address(valueStr, 4));
                            break;
                        }
                        case EMAIL: {
                            field.set(data, DesensitizedUtil.email(valueStr));
                            break;
                        }
                        case BANK_CARD: {
                            field.set(data, DesensitizedUtil.bankCard(valueStr));
                            break;
                        }
                        case CNAPS_CODE: {
                            field.set(data, DesensitizedUtil.cnapsCode(valueStr));
                            break;
                        }
                        default: {
                            break;
                        }
                    }
                }
            }
        }

    }


    /**
     * [中文姓名] 只显示第一个汉字,其他隐藏为2个星号<例子:李**>
     *
     * @param fullName 中文姓名
     * @return 中文姓名
     */
    private static String chineseName(String fullName) {
        if (StringUtils.isBlank(fullName)) {
            return "";
        }
        String name = StringUtils.left(fullName, 1);
        return StringUtils.rightPad(name, StringUtils.length(fullName), "*");
    }

    /**
     * [中文姓名] 只显示第一个汉字,其他隐藏为2个星号<例子:李**>
     *
     * @param familyName 中文姓名
     * @param givenName  中文姓名
     * @return
     */
    private static String chineseName(String familyName, String givenName) {
        if (StringUtils.isBlank(familyName) || StringUtils.isBlank(givenName)) {
            return "";
        }
        return chineseName(familyName + givenName);
    }

    /**
     * [身份证号] 显示最后四位,其他隐藏。共计18位或者15位。<例子:*************5762>
     *
     * @param id 身份证号
     * @return 身份证号
     */
    private static String idCardNum(String id) {
        if (StringUtils.isBlank(id)) {
            return "";
        }
        String num = StringUtils.right(id, 4);
        return StringUtils.leftPad(num, StringUtils.length(id), "*");
    }

    /**
     * [固定电话] 后四位,其他隐藏<例子:****1234>
     *
     * @param num 固定电话
     * @return 固定电话
     */
    private static String fixedPhone(String num) {
        if (StringUtils.isBlank(num)) {
            return "";
        }
        return StringUtils.leftPad(StringUtils.right(num, 4), StringUtils.length(num), "*");
    }

    /**
     * [手机号码] 前三位,后四位,其他隐藏<例子:138******1234>
     *
     * @param num 手机号码
     * @return String
     */
    private static String mobilePhone(String num) {
        if (StringUtils.isBlank(num)) {
            return "";
        }
        return StringUtils.left(num, 3).concat(StringUtils.removeStart(StringUtils.leftPad(StringUtils.right(num, 4), StringUtils.length(num), "*"), "***"));
    }

    /**
     * [地址] 只显示到地区,不显示详细地址;我们要对个人信息增强保护<例子:北京市海淀区****>
     *
     * @param address       地址
     * @param sensitiveSize 敏感信息长度
     * @return String
     */
    private static String address(String address, int sensitiveSize) {
        if (StringUtils.isBlank(address)) {
            return "";
        }
        int length = StringUtils.length(address);
        return StringUtils.rightPad(StringUtils.left(address, length - sensitiveSize), length, "*");
    }

    /**
     * [电子邮箱] 邮箱前缀仅显示第一个字母,前缀其他隐藏,用星号代替,@及后面的地址显示<例子:g**@163.com>
     *
     * @param email 电子邮箱
     * @return String
     */
    private static String email(String email) {
        if (StringUtils.isBlank(email)) {
            return "";
        }
        int index = StringUtils.indexOf(email, "@");
        if (index <= 1) {
            return email;
        } else {
            return StringUtils.rightPad(StringUtils.left(email, 1), index, "*").concat(StringUtils.mid(email, index, StringUtils.length(email)));
        }
    }

    /**
     * [银行卡号] 前六位,后四位,其他用星号隐藏每位1个星号<例子:6222600**********1234>
     *
     * @param cardNum 银行卡号
     * @return String
     */
    private static String bankCard(String cardNum) {
        if (StringUtils.isBlank(cardNum)) {
            return "";
        }
        return StringUtils.left(cardNum, 6).concat(StringUtils.removeStart(StringUtils.leftPad(StringUtils.right(cardNum, 4), StringUtils.length(cardNum), "*"), "******"));
    }

    /**
     * [公司开户银行联号] 公司开户银行联行号,显示前两位,其他用星号隐藏,每位1个星号<例子:12********>
     *
     * @param code 卡号
     * @return java.lang.String
     */
    private static String cnapsCode(String code) {
        if (StringUtils.isBlank(code)) {
            return "";
        }
        return StringUtils.rightPad(StringUtils.left(code, 2), StringUtils.length(code), "*");
    }
}

2.4 统一拦截器

package com.example.category.desen;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.context.annotation.Configuration;

/**
 * 统一拦截器
 *
 * @author zrj
 * @since 2021/8/16
 **/
@Aspect
@Configuration
public class SensitiveAspect {
    public static final String ACCESS_EXECUTION = "execution(* com.example.category.controller..*.*(..))";

    /**
     * 注解脱敏处理
     */
    @Around(ACCESS_EXECUTION)
    public Object sensitiveClass(ProceedingJoinPoint joinPoint) throws Throwable {
        return sensitiveFormat(joinPoint);
    }

    /**
     * 注解统一拦截器
     */
    public Object sensitiveFormat(ProceedingJoinPoint joinPoint) throws Throwable {
        Object obj = joinPoint.proceed();
        if (obj == null || isPrimitive(obj.getClass())) {
            return obj;
        }

        if (DesensitizedUtil.DESENT_STATUS.equals("1")) {
            DesensitizedUtil.desentData(obj);
        }
        return obj;
    }

    /**
     * 基本数据类型和String类型判断
     */
    public static boolean isPrimitive(Class<?> clz) {
        try {
            if (String.class.isAssignableFrom(clz) || clz.isPrimitive()) {
                return true;
            } else {
                return ((Class) clz.getField("TYPE").get(null)).isPrimitive();
            }
        } catch (Exception e) {
            return false;
        }
    }
}

2.5 统一结果集

package com.example.category.entity;

import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * 统一结果集
 *
 * @author zrj
 * @date 2021/6/2
 * @since V1.0
 **/
@Data
@Component
public class Response<T> {
    public static ResponseCode responseCode;
    /**
     * 提示消息
     */
    public String message;

    /**
     * 具体返回的数据
     */
    public T data;

    /**
     * 状态码
     */
    public String code;

    public Response(String code, String message, T data) {
        this.message = message;
        this.code = code;
        this.data = data;
    }

    public Response(String code, String msg) {
        this.message = msg;
        this.code = code;
    }

    @Autowired
    public Response(ResponseCode responseCode) {
        Response.responseCode = responseCode;
    }

    /**
     * 返回成功Response对象
     */
    public static <T> Response<T> success(String successMessage, T data) {
        return new Response<>(responseCode.getSuccessCode(), successMessage, data);
    }

    /**
     * 返回错误Response对象
     */
    public static <T> Response<T> fail(String errorMessage) {
        return new Response<>(responseCode.getErrorCode(), errorMessage);
    }
}

package com.example.category.entity;

import lombok.Data;
import org.springframework.stereotype.Component;

/**
 * 响应码
 *
 * @author zrj
 * @date 2021/6/2
 * @since V1.0
 **/
@Data
@Component
public class ResponseCode {
    public String successCode = "200";

    public String errorCode = "500";

    public String authErrorCode = "300";
}

2.6 用户实体类

package com.example.category.entity;

import com.example.category.desen.Desensitized;
import com.example.category.desen.SensitiveTypeEnum;
import lombok.Data;

import java.io.Serializable;

/**
 * 用户实体类
 *
 * @author zrj
 * @since 2021-06-21
 */
@Data
public class User implements Serializable {
    private static final long serialVersionUID = -37201319317456395L;
    /**
     * id
     */
    private Integer id;

    /**
     * 姓名
     */
    @Desensitized(type = SensitiveTypeEnum.CHINESE_NAME)
    private String name;

    /**
     * 手机号
     */
    @Desensitized(type = SensitiveTypeEnum.MOBILE_PHONE)
    private String mobilePhone;

    /**
     * 身份证号码
     */
    @Desensitized(type = SensitiveTypeEnum.ID_CARD)
    private String idCard;

    /**
     * 地址
     */
    @Desensitized(type = SensitiveTypeEnum.ADDRESS)
    private String address;

    /**
     * 邮箱
     */
    @Desensitized(type = SensitiveTypeEnum.EMAIL)
    private String email;
}

2.7 用户控制类

package com.example.category.controller;

import com.example.category.entity.User;
import com.example.category.service.UserService;
import io.swagger.annotations.Api;
import org.springframework.web.bind.annotation.*;
import com.example.category.entity.Response;

import java.util.List;

import javax.annotation.Resource;

/**
 * 用户控制类
 *
 * @author makejava
 * @since 2021-06-21 16:44:24
 */
@RestController
@RequestMapping("/user")
@Api(tags = "用户管理", description = "用户管理")
public class UserController {
    /**
     * 脱敏测试
     */
    @RequestMapping(value = "test", method = RequestMethod.GET)
    public Response<User> test() {
        User result = new User();
        result.setId(1);
        result.setName("王小二");
        result.setMobilePhone("13057631239");
        result.setIdCard("320102199303073260");
        result.setAddress("中国上海市张江高新区256号");
        result.setEmail("hello@163.com");

        if (result != null) {
            return Response.success("查询成功", result);
        }
        return Response.fail("查询失败");
    }
}

3. 实现验证

在这里插入图片描述

  • 9
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值