基于注解的字段脱敏,无侵入代码

 

好久没更新了,最近一直忙于加班。

之前脱敏的规则,抽象出来做成公共方法。方便其他业务引用。

 

 

1 之前的做法

 

有前端传一个字段,是否需要脱敏,后端根据业务字段进行脱敏,这样的脱敏是放在业务中。

这样做就对代码有污染,有更新的字段就得继续加代码。而且别的业务需要,也得自己添加,既不简洁,又显冗余。

尤其是在微服务阶段,底层服务本身不关心脱敏业务,不需要坐这个业务;中台业务每个业务都得自己写,显得十分冗余。

 

 

2 基于注解的脱敏方式

业务的脱敏规则,基本大差不差。例如手机号屏蔽中间四位,姓名只保留姓等。所以这些业务完全可以基于注解来解决。

如果有特殊的规则,在注解上添加规则即可。

 

这里会用到 JsonPath  的语法,用之前,请点击连接了解。

JsonPath提供的json解析非常强大,它提供了类似正则表达式的语法,基本上可以满足所有你想要获得的json内容。

 

引用fastjson ,提供了jsonpath的方法。

       <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.83</version>
            <scope>compile</scope>
        </dependency>

 

这里脱敏规则有两种实现方式,可以打在方法上,也可以打在字段上。

因为这边前端需要手动打开与展示,并且通过了额外的字段来控制,这边是用的打在类上,没有使用在方法上。

当然也可以做到在字段上进行脱敏,这边又是一种实现方式。

这样做也有风险,需要脱敏字段的和识别脱敏字段的改变,如果同步更新到注解上,会造成字段映射不到,注解失效。

 注解:

这里根据自己业务进行定义

package com.acxiom.crm5.common.core.desensitization;


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SensitiveWrapped {
/**
* jsonPath路径[n],用于获取接口入参的一个bool值,标识接口是否需要脱敏
* @return
*/
String flagKey() default "";

/**
* 需要脱敏得字段
* @return
*/

Field[] field();


@interface Field {
/**
* jsonPath路径$.records[*].cstFullName,用于获取脱敏字段
* @return
*/
String flagKey() default "";

/**
* jsonPath路径[n].showName,用于获取一个bool值标识当前字段是否需要脱敏
* @return
*/
String key();

/**
* SensitiveEnum标识字段的脱敏类型
*
* @return
*/
SensitiveEnum type();
}
}

 

 实现:

package com.acxiom.crm5.common.core.desensitization;

import com.alibaba.fastjson.JSONPath;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

import java.util.Collection;
import java.util.List;

@Aspect
@Component
public class SensitiveHandler {

    @Around("@annotation(annotation)")
    public Object desensitizationAspect(ProceedingJoinPoint joinPoint, SensitiveWrapped annotation) throws Throwable {

        Signature signature = joinPoint.getSignature();

        joinPoint.getTarget().getClass().getDeclaredMethods();

        Object[] args = joinPoint.getArgs();
        Object result = joinPoint.proceed();
        if (result != null) {
            desensitization(result, args, annotation);
        }
        return result;
    }


    public void desensitization(Object result, Object[] args, SensitiveWrapped annotation) {
        //所有数据脱敏 所有字段加密
        boolean needToDo = "".equals(annotation.flagKey());
        if (!needToDo) {
            needToDo = (boolean) JSONPath.eval(args, annotation.flagKey());
        }
        if (!needToDo) {
            return;
        }
        // 指定字段脱敏 flagKey()为false的加密,true 明文
        for (SensitiveWrapped.Field field : annotation.field()) {
            boolean doIt = "".equals(field.flagKey());
            if (!doIt) {
                doIt = (boolean) JSONPath.eval(args, field.flagKey());
            }
            if (!doIt) {
                continue;
            }
            swap(result, field);
        }

    }

    /**
     * 字段替换 敏感字段 --> *
     *
     * @param result
     * @param field
     */
    private void swap(Object result, SensitiveWrapped.Field field) {
        String key = field.key();
        SensitiveEnum type = field.type();
        if (key.contains("*")) {
            String prefix = key.substring(0, key.indexOf("*") - 1);
            String suffix = key.substring(key.indexOf("*") + 2);
            List<String> sourceValue = (List<String>) JSONPath.eval(result, key);
            Collection collection = (Collection) JSONPath.eval(result, prefix);
            for (int i = 0, j = 0; i < collection.size(); i++) {
                String path = prefix + "[" + i + "]" + suffix;
                if (JSONPath.eval(result, path) != null) {
                    JSONPath.set(result, path, getDesensitizationValue(type, sourceValue.get(j++)));
                }
            }
        } else {
            String sourceValue = (String) JSONPath.eval(result, key);
            if (StringUtils.isNoneBlank(sourceValue)) {
                String newValue = getDesensitizationValue(type, sourceValue);
                JSONPath.set(result, key, newValue);
            }
        }
    }

    public String getDesensitizationValue(SensitiveEnum type, String value) {
        switch (type) {
            case CHINESE_NAME: {
                value = SensitiveInfoUtils.chineseName(value);
                break;
            }
            case LAST_NAME: {
                value = SensitiveInfoUtils.lastName(value);
                break;
            }
            case FIRST_NAME: {
                value = SensitiveInfoUtils.firstName(value);
                break;
            }
            case ID_CARD: {
                value = SensitiveInfoUtils.idCardNum(value);
                break;
            }
            case FIXED_PHONE: {
                value = SensitiveInfoUtils.fixedPhone(value);
                break;
            }
            case MOBILE_PHONE: {
                value = SensitiveInfoUtils.mobilePhone(value);
                break;
            }
            case ADDRESS: {
                value = SensitiveInfoUtils.address(value, 4);
                break;
            }
            case EMAIL: {
                value = SensitiveInfoUtils.email(value);
                break;
            }
            case BANK_CARD: {
                value = SensitiveInfoUtils.bankCard(value);
                break;
            }
            case CNAPS_CODE: {
                value = SensitiveInfoUtils.cnapsCode(value);
                break;
            }
            case SEX: {
                value = SensitiveInfoUtils.sex(value);
                break;
            }
            case BIRTHDAY: {
                value = SensitiveInfoUtils.birthDay(value);
                break;
            }

        }
        return value;
    }

}

 

工具类:

package com.acxiom.crm5.common.core.desensitization;

import org.apache.commons.lang3.StringUtils;

public class SensitiveInfoUtils {

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

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

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

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

    /**
     * [身份证号] 显示最后四位,其他隐藏。共计18位或者15位。<例子:420**********5762>
     */
    public static String idCardNum(final String id) {
        if (StringUtils.isBlank(id)) {
            return "";
        }

        return StringUtils.left(id, 3).concat(StringUtils
                .removeStart(StringUtils.leftPad(StringUtils.right(id, 4), StringUtils.length(id), "*"),
                        "***"));
    }

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

    /**
     * [手机号码] 前三位,后四位,其他隐藏<例子:138******1234>
     */
    public static String mobilePhone(final 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 sensitiveSize 敏感信息长度
     */
    public static String address(final String address, final int sensitiveSize) {
        if (StringUtils.isBlank(address)) {
            return "";
        }
        final int length = StringUtils.length(address);
        return StringUtils.rightPad(StringUtils.left(address, length - sensitiveSize), length, "*");
    }

    /**
     * [电子邮箱] 邮箱前缀仅显示第一个字母,前缀其他隐藏,用星号代替,@及后面的地址显示<例子:g**@163.com>
     */
    public static String email(final String email) {
        if (StringUtils.isBlank(email)) {
            return "";
        }
        final 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>
     */
    public static String bankCard(final 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********>
     */
    public static String cnapsCode(final String code) {
        if (StringUtils.isBlank(code)) {
            return "";
        }
        return StringUtils.rightPad(StringUtils.left(code, 2), StringUtils.length(code), "*");
    }


    public static String sex(final String code) {
        if (StringUtils.isBlank(code)) {
            return "";
        }
        return mixStr(code, '*', 0, 0);
    }

    public static String birthDay(final String code) {
        if (StringUtils.isBlank(code)) {
            return "";
        }
        return mixStr(code, '*', 2, 0);
    }

    public static String mixStr(String str, char mixStr, int befor, int after) {
        if (str == null) {
            return null;
        }
        if (str.length() <= befor + after) {
            return str;
        }
        String p1 = "";
        String p2 = "";
        if (befor != 0) {
            p1 = str.substring(0, befor);
            str = str.substring(befor);
        }

        if (after != 0) {
            p2 = str.substring(str.length() - after, str.length());
            str = str.substring(0,str.length() - after);
        }

        str = str.replaceAll(".", String.valueOf(mixStr));
        str = p1 + str + p2;

        return str;
    }

}

 

业务枚举:

package com.acxiom.crm5.common.core.desensitization;

public enum SensitiveEnum {
    /**
     * 中文名
     */
    CHINESE_NAME,
    /**
     * 名
     */
    FIRST_NAME,
    /**
     * 姓
     */
    LAST_NAME,
    /**
     * 身份证号
     */
    ID_CARD,
    /**
     * 座机号
     */
    FIXED_PHONE,
    /**
     * 手机号
     */
    MOBILE_PHONE,
    /**
     * 地址
     */
    ADDRESS,
    /**
     * 电子邮件
     */
    EMAIL,
    /**
     * 银行卡
     */
    BANK_CARD,
    /**
     * 公司开户银行联号
     */
    CNAPS_CODE,
    /**
     * 性别
     */
    SEX,
    /**
     * 生日
     */
    BIRTHDAY
}

 

 

 

效果:

 

posted @ 2023-05-09 14:39  未确定  阅读(92)  评论(0编辑  收藏  举报