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