千家信息网

如何使用自定义Json注解实现输出日志字段脱敏

发表于:2026-11-03 作者:千家信息网编辑
千家信息网最后更新 2026年11月03日,这篇文章给大家分享的是有关如何使用自定义Json注解实现输出日志字段脱敏的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。自定义Json注解实现输出日志字段脱敏背景在日志输出的
千家信息网最后更新 2026年11月03日如何使用自定义Json注解实现输出日志字段脱敏

这篇文章给大家分享的是有关如何使用自定义Json注解实现输出日志字段脱敏的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。

自定义Json注解实现输出日志字段脱敏

背景

在日志输出的时候,有时会输出一些用户的敏感信息,如手机号,身份证号,银行卡号等,现需要对这些信息在日志输出的时候进行脱敏处理

思路

使用fastjson的ValueFilter对带有自定义注解的字段进行过滤

/** * 敏感信息类型 * * @author worstEzreal * @version V1.0.0 * @date 2017/7/19 */public enum SensitiveType {    ID_CARD,    BANK_CARD,    PHONE}
/** * 脱敏字段注解 * * @author worstEzreal * @version V1.0.0 * @date 2017/7/19 */@Target({ElementType.TYPE, ElementType.FIELD})@Retention(RetentionPolicy.RUNTIME)public @interface SensitiveInfo {    SensitiveType type();}
/** * 日志敏感信息脱敏工具 * * @author worstEzreal * @version V1.0.0 * @date 2017/7/19 */public class SensitiveInfoUtils {     public static String toJsonString(Object object) {        return JSON.toJSONString(object, getValueFilter());    }     private static String desensitizePhoneOrIdCard(String num) {        if (StringUtils.isBlank(num)) {            return "";        }        return StringUtils.left(num, 3).concat(StringUtils.removeStart(StringUtils.leftPad(StringUtils.right(num, 4), StringUtils.length(num), "*"), "***"));    }     private static String desensitizeBankCard(String cardNum) {        if (StringUtils.isBlank(cardNum)) {            return "";        }        return StringUtils.left(cardNum, 4).concat(StringUtils.removeStart(StringUtils.leftPad(StringUtils.right(cardNum, 4), StringUtils.length(cardNum), "*"), "****"));    }     private static final ValueFilter getValueFilter() {        return new ValueFilter() {            @Override            public Object process(Object obj, String key, Object value) {//obj-对象  key-字段名  value-字段值                try {                    Field field = obj.getClass().getDeclaredField(key);                    SensitiveInfo annotation = field.getAnnotation(SensitiveInfo.class);                    if (null != annotation && value instanceof String) {                        String strVal = (String) value;                        if (StringUtils.isNotBlank(strVal)) {                            switch (annotation.type()) {                                case PHONE:                                    return desensitizePhoneOrIdCard(strVal);                                case ID_CARD:                                    return desensitizePhoneOrIdCard(strVal);                                case BANK_CARD:                                    return desensitizeBankCard(strVal);                                default:                                    break;                            }                        }                    }                } catch (NoSuchFieldException e) {                    //找不到的field对功能没有影响,空处理                }                return value;            }        };    }     public static void main(String[] args) {        CardInfo cardInfo = new CardInfo();        cardInfo.setId("11111111111111111");        cardInfo.setCardId("6228480402564890018");        System.out.println(SensitiveInfoUtils.toJsonString(cardInfo));    }}

附CardInfo类

public class CardInfo {     private String userId;    private String name;    @SensitiveInfo(type = SensitiveType.ID_CARD)    private String certId;    @SensitiveInfo(type = SensitiveType.BANK_CARD)    private String cardId;    private String bank;    private String phone;     public String getUserId() {        return userId;    }     public void setUserId(String userId) {        this.userId = userId;    }     public String getName() {        return name;    }     public void setName(String name) {        this.name = name;    }     public String getCertId() {        return certId;    }     public void setCertId(String certId) {        this.certId = certId;    }     public String getCardId() {        return cardId;    }     public void setCardId(String cardId) {        this.cardId = cardId;    }     public String getBank() {        return bank;    }     public void setBank(String bank) {        this.bank = bank;    }     public String getPhone() {        return phone;    }     public void setPhone(String phone) {        this.phone = phone;    } }

java注解式脱敏

随着互联网时代普及,用户的信息越来越重要,我们开发软件过程中也需要对用户的信息进行脱敏处理活着加密处理,针对于比较繁杂的工作,个人来讲解如何实现注解式脱敏,支持静态调用和aop统一拦截实现脱敏或者加密返回。

代码讲解

脱敏枚举类

定义枚举类,处理所有脱敏和加密等,同时可扩展性,这里只是注解式调用方法而已,以便编写样例。DesensitizationEnum若还需要其他脱敏或者加密方法是,只需要添加下面枚举类型即可

package com.lgh.common.sensitive;import com.lgh.common.utils.MaskUtils;import java.lang.reflect.Method;/** * 若需要新定义一个扫描规则,这里添加即可 * * @author lgh * @version 1.0 * @date 2021/1/17 */public enum DesensitizationEnum {    // 执行类和脱敏方法名    PHONE(MaskUtils.class, "maskPhone", new Class[]{String.class});     private Class clazz;     private Method method;    DesensitizationEnum(Class target, String method, Class[] paramTypes) {        this.clazz = target;        try {            this.method = target.getDeclaredMethod(method, paramTypes);        } catch (NoSuchMethodException e) {             e.printStackTrace();        }    }    public Method getMethod() {        return method;    }}

脱敏工具

package com.lgh.common.utils;import org.springframework.util.StringUtils;/** * @author lgh * @version 1.0 * @date 2021/1/17 */public class MaskUtils {    public static String maskPhone(String phone){        if(StringUtils.isEmpty(phone) || phone.length() < 8){            return phone;        }        return phone.replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2");    }}
注解类编写

此类添加到需要脱敏的类属性上即可实现脱敏,具体是递归遍历此注解,通过反射机制来实现脱敏功能

package com.lgh.common.sensitive;import java.lang.annotation.Documented;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * 参数定义注解类 * @author linguohu * @version 1.0 * @date 2021/1/17 **/@Target({ElementType.FIELD})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface SensitiveValid {    DesensitizationEnum type();}
脱敏工具类

特殊声明,我们递归时是索引递归,会出现死循环的情况,比如对象引用了对象,循环地址引用,所以会出现死循环,这里设置了10层递归,一般我们也不允许有那么深的对象设置。

package com.lgh.common.utils;import com.lgh.common.sensitive.DesensitizationEnum;import com.lgh.common.sensitive.SensitiveValid;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.util.ReflectionUtils;import java.lang.reflect.Field;import java.util.Collection;import java.util.Map;/** * 对象脱敏工具 * * @author lgh * @version 1.0 * @date 2021/1/17 */public class DesensitizationUtils {    private static final Logger log = LoggerFactory.getLogger(DesensitizationUtils.class);    private DesensitizationUtils() {    }    /**     * 扫描对象注解,脱敏,最高层次8层     *     * @param obj     */    public static void format(Object obj) {        DesensitizationUtils.formatMethod(obj, 10);    }    /**     * 递归遍历数据,因为可能有对象地址应用导致循环问题,同时设置莫名奇妙的异常,所以设置递归层次,一般不要超过10层     *     * @param obj   需要反射对象     * @param level 递归层次,必须输入     */    private static void formatMethod(Object obj, int level) {        if (obj == null || isPrimitive(obj.getClass()) || level <= 0) {            return;        }        if (obj.getClass().isArray()) {            for (Object object : (Object[]) obj) {                formatMethod(object, level--);            }        } else if (Collection.class.isAssignableFrom(obj.getClass())) {            for (Object o : ((Collection) obj)) {                formatMethod(o, level--);            }        } else if (Map.class.isAssignableFrom(obj.getClass())) {            for (Object o : ((Map) obj).values()) {                formatMethod(o, level--);            }        } else {            objFormat(obj, level);        }    }    /**     * 只有对象才格式化数据     *     * @param obj     * @param level     */    private static void objFormat(Object obj, int level) {        for (Field field : obj.getClass().getDeclaredFields()) {            try {                if (isPrimitive(field.getType())) {                    SensitiveValid sensitiveValid = field.getAnnotation(SensitiveValid.class);                    if (sensitiveValid != null) {                        ReflectionUtils.makeAccessible(field);                        DesensitizationEnum desensitizationEnum = sensitiveValid.type();                        Object fieldV = desensitizationEnum.getMethod().invoke(null, field.get(obj));                        ReflectionUtils.setField(field, obj, fieldV);                    }                } else {                    ReflectionUtils.makeAccessible(field);                    Object fieldValue = ReflectionUtils.getField(field, obj);                    if (fieldValue == null) {                        continue;                    }                    formatMethod(fieldValue, level - 1);                }            } catch (Exception e) {                log.error("脱敏数据处理异常", e);            }        }    }    /**     * 基本数据类型和String类型判断     *     * @param clz     * @return     */    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;        }    }}
脱敏AOP的实现

aop插拔式编程,以便防止有不需要的操作,所以编写可控制类注解EnableDesensitization

package com.lgh.common.sensitive;import java.lang.annotation.Documented;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * 方法返回值拦截器,需要注解才生效 * @author lgh * @version 1.0 * @date 2021/1/17 **/@Target({ElementType.TYPE,ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface EnableDesensitization {}

最后实现拦截aop

package com.lgh.common.sensitive;import com.lgh.common.utils.DesensitizationUtils;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.reflect.MethodSignature;import org.springframework.context.annotation.Configuration;import org.aspectj.lang.annotation.Aspect;import java.lang.reflect.Method;/** * @author lgh * @version 1.0 * @date 2021/1/17 */@Aspect@Configurationpublic class SensitiveAspect {    public static final String ACCESS_EXECUTION = "execution(* com.lgh.controller..*.*(..))";    /**     * 注解脱敏处理     *     * @param joinPoint     * @return     * @throws Throwable     */    @Around(ACCESS_EXECUTION)    public Object sensitiveClass(ProceedingJoinPoint joinPoint) throws Throwable {        return sensitiveFormat(joinPoint);    }    /**     * 插拔式注解统一拦截器。@{link EnableDesensitization } 和 @SensitiveValid     *     * @param joinPoint     * @return     * @throws Throwable     */    public Object sensitiveFormat(ProceedingJoinPoint joinPoint) throws Throwable {        Object obj = joinPoint.proceed();        if (obj == null || DesensitizationUtils.isPrimitive(obj.getClass())) {            return obj;        }        MethodSignature signature = (MethodSignature) joinPoint.getSignature();        Method method = signature.getMethod();        EnableDesensitization desensitization = joinPoint.getTarget().getClass().getAnnotation(EnableDesensitization.class);        if (desensitization != null || method.getAnnotation(EnableDesensitization.class) != null) {            DesensitizationUtils.format(obj);        }        return obj;    }}

实战演练

我居于上一章节的UserDetail对象增加phone字段,同时加入注解,如下:

package com.lgh.common.authority.entity;import com.lgh.common.sensitive.DesensitizationEnum;import com.lgh.common.sensitive.SensitiveValid;public class UserDetail {    private long id;    private String name;    @SensitiveValid(type = DesensitizationEnum.PHONE)    private String phone;    public long getId() {        return id;    }    public void setId(long id) {        this.id = id;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public void setPhone(String phone) {        this.phone = phone;    }    public String getPhone() {        return phone;    }}

接下来controller中启动注解

@GetMapping("/detail")    @EnableDesensitization    public IResult getUser(@AuthenticationPrincipal UserDetail userDetail) {        return CommonResult.successData(userDetail);    }

大功告成,接下来我们实现一下访问操作

感谢各位的阅读!关于"如何使用自定义Json注解实现输出日志字段脱敏"这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,让大家可以学到更多知识,如果觉得文章不错,可以把它分享出去让更多的人看到吧!

脱敏 注解 对象 字段 递归 处理 日志 输出 信息 工具 数据 方法 类型 加密 循环 同时 层次 用户 接下来 内容 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 上海挚品互联网科技有限公司 深圳科技互联网 科技与互联网发展 华宇互联网科技 上海致骋互联网科技 互联网金融监管科技 太原新华互联网科技学校怎么样 农村开互联网科技讲堂 互联网媒体平台算高科技产业 华夏恒生互联网科技013171 威龙国际互联网科技公司 中和天地互联网科技有限公司 广州千川互联网科技有限公司 江苏互联网智能科技软件 35岁以上互联网科技行业 西安有哪些互联网高科技企业 上海万顺科技互联网有限公司招聘 互联网时代高科技布局 互联网科技的前景 颐高云石互联网科技 湖北九亚互联网络科技有限公司 广东万易互联网科技有限公司惠州分公司 新华互联网科技学校 互联网为科技 开启互联网科技时代小说 浙江互联网科技研学营 用友工业互联网科技发展 长沙互联网科技 黑云互联网科技有限公司 上海互联网科技公司ppt
0