springboot/springcloud 实现脱敏实现和方案

文章介绍了如何在SpringBoot和SpringCloud项目中,通过Nacos配置脱敏策略并利用ResponseBodyAdvice进行全局拦截,以实现对实体类字段、注解和请求数据的脱敏处理。作者提供了详细的代码示例,包括配置和逻辑处理部分。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


springboot/springcloud 实现脱敏实现和方案

最近在搞脱敏的东西,因为老项目,嗯,很多规范不太统一,业务也有很多很多的代码实现,所以需要考虑一种兼容的脱敏实现

结论方案

先说最终的实现方案:
nacos配置脱敏策略+统一拦截处理(使用的继承ResponseBodyAdvice )的方案

常用方案

1、entity/dto/vo等模型的字段json脱敏,
字段注解方式,利用返回数据json序列化时做脱敏
2、controller层做注解返回字段脱敏
自定义注解,拦截注解
拦截注解可以使用:
mvc的HandlerMethodReturnValueHandler方式,自行处理返回格式和数据
aop拦截器,拦截自定义注解
3、做全局统一拦截处理
可以使用aop拦截,拦截controller层的注解
使用继承ResponseBodyAdvice,拦截controller层的使用注解
4、其他
方案还会有很多种,我们只说说基本面的

继承ResponseBodyAdvice实现脱敏方式

嗯,太多的文字不写了,直接上代码

nacos策略配置

简单的策略,也可以搞搞复杂的

# 脱敏处理
desensitization:
  merchant:
    # saas化,拦截不同的租户数据
    merchantIds: 2
    # 规则 MOBILE_PHONE:拦截的key规则,mobile,mobile2:拦截的具体字段
    rules: MOBILE_PHONE:mobile,mobile2;FIXED_PHONE:ddd
    # 权限是另一个规则

脱敏逻辑处理

package com.domes.common.restful.filter;

import cn.hutool.core.util.DesensitizedUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.nacos.api.config.annotation.NacosValue;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * @author weidong
 * @version V1.0.0
 * @since 2023/8/24
 */
@Component
@Slf4j
public class DesensitizationDeal {

    //可执行的请求路径
    private final static List<String> ReqIncludeUrls = new ArrayList<>();
    //过滤掉请求路径
    private final static List<String> ReqExcludeUrls = new ArrayList<>();
    //处理规则权限映射
    private final static Map<String, String> PermissionCodeMapping = new HashMap<>();

    @NacosValue(value = "${desensitization.merchant.merchantIds}", autoRefreshed = true)
    private String MerchantIds;
    @NacosValue(value = "${desensitization.merchant.rules}", autoRefreshed = true)
    private String Rules;


    public Object dealReturnData(Object obj, String reqPath) {
        //不处理请求

        if (validateReqUrl(reqPath, ReqExcludeUrls)) return obj;

        //判断是否匹配请求路径
        if (!validateReqUrl(reqPath, ReqIncludeUrls)) return obj;

        //判断当前用户是否在配置租户中,测试
        Integer merchantId = 2;
        if (!MerchantIds.contains(merchantId + "")) return obj;

        //剔除管理员和不需要的用户
        //.......
        //判断当前用户是否拥有权限,测试
        Set<String> permissionCodes = new HashSet<String>();
        permissionCodes.set("D9100");

        //处理规则数据
        Map<String, String> rulesMap = new HashMap<>();
        List<String> rulesList = Arrays.asList(Rules.split(";"));
        for (String item : rulesList) {
            String[] subStr = item.split(":");
            if (subStr.length == 2) {
                if (StringUtils.isNotEmpty(PermissionCodeMapping.get(subStr[0]))) {
                    rulesMap.put(subStr[0], subStr[1]);
                }
            }
        }

        //需要处理的规则数据
        Map<String, String> rulesNeedMap = new HashMap<>();
        rulesMap.forEach((key, val) -> {
            if (permissionCodes.contains(PermissionCodeMapping.get(key))) rulesNeedMap.put(key, val);
        });

        //处理脱敏数据
        if (rulesNeedMap.size() > 0) {
            List<String> existKeys = new ArrayList<>();
            String objString = JSON.toJSONString(obj);
            rulesNeedMap.forEach((key, item) -> {
                Arrays.stream(item.split(",")).forEach(subItem -> {
                    if (objString.contains(subItem)) existKeys.add(subItem);
                });

            });

            if (existKeys.size() > 0) {
                JSONObject object = JSONObject.parseObject(objString);
                dealDataObject(object, rulesNeedMap);
                obj = object;
            }
        }

        return obj;
    }


    private void dealDataObject(JSONObject object, Map<String, String> rulesNeedMap) {
        object.forEach((key, val) -> {
            if (val instanceof JSONObject) {
                dealDataObject((JSONObject) val, rulesNeedMap);
            } else if (val instanceof JSONArray) {
                dealDataArray((JSONArray) val, rulesNeedMap);
            } else {
                rulesNeedMap.forEach((ruleKey, ruleVal) -> {
                    if (ruleVal.contains(key) && StringUtil.isNotEmpty(val)) {
                        object.put(key, DesensitizedUtil.desensitized(String.valueOf(val), DesensitizedUtil.DesensitizedType.valueOf(ruleKey)));
                    }
                });
            }
        });

    }

    private void dealDataArray(JSONArray array, Map<String, String> rulesNeedMap) {
        array.forEach(item -> {
            if (item instanceof JSONObject) {
                dealDataObject((JSONObject) item, rulesNeedMap);
            } else if (item instanceof JSONArray) {
                dealDataArray((JSONArray) item, rulesNeedMap);
            }
        });

    }

    private Boolean validateReqUrl(String path, List<String> reqUrls) {
        AtomicBoolean goFlag = new AtomicBoolean(false);
        for (String item : reqUrls) {
            if (item.contains("*")) {
                if (path.contains(item.split("\\*")[0])) {
                    goFlag.set(true);
                    break;
                }
            } else {
                if (path.equals(item)) {
                    goFlag.set(true);
                    break;
                }

            }
        }

        return goFlag.get();
    }

    static {

        PermissionCodeMapping.put(DesensitizedUtil.DesensitizedType.MOBILE_PHONE.name(), "D9100");
        PermissionCodeMapping.put(DesensitizedUtil.DesensitizedType.FIXED_PHONE.name(), "D9100");
        //包含的请求路径
        ReqIncludeUrls.add("/xxxx/xxxx/detailPage");



        //不过滤路径
        ReqExcludeUrls.add("/xxxx/xxxx/login");
        ReqExcludeUrls.add("/xxxx/open/*");

    }
}


拦截实现


import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

/**
 * 手机号脱敏拦截处理
 *
 * @author weidong
 * @version V1.0.0
 * @since 2023/8/10
 */
//拦截两种不同的controller注解
@ControllerAdvice
@RestControllerAdvice
@Slf4j
public class DesensitizationResponseResultAdvice implements ResponseBodyAdvice<Object> {

    @Autowired
    DesensitizationDeal desensitizationDeal;

    //直接的MethodParameter类型,可以配置不同的
    @Override
    public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object obj, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        //log.info("------ResponseResultAdvice----:{}", JSON.toJSONString(obj));
        String path = serverHttpRequest.getURI().getPath();
        obj = desensitizationDeal.dealReturnData(obj,path);
        return obj;
    }

}

外传

😜 原创不易,如若本文能够帮助到您的同学
🎉 支持我:关注我+点赞👍+收藏⭐️
📝 留言:探讨问题,看到立马回复
💬 格言:己所不欲勿施于人 扬帆起航、游历人生、永不言弃!🔥

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

南巷Dong

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值