package org.springblade.common.desensitization; import cn.hutool.core.convert.Convert; import cn.hutool.core.util.DesensitizedUtil; import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONPath; import liquibase.util.StringUtils; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springblade.core.secure.utils.AuthUtil; import org.springframework.stereotype.Component; import static cn.hutool.core.util.DesensitizedUtil.DesensitizedType.FIXED_PHONE; /** * 数据脱敏切面 * @author zhongrj * @date 2023-09-11 */ @Slf4j @Aspect @Component public class DesensitizationAspect { /** * 脱敏环绕通知 * @param pjp * @param desensitizationWord * @return * @throws Throwable */ @Around("@annotation(desensitizationWord)") public Object around(ProceedingJoinPoint pjp, DesensitizationWord desensitizationWord) throws Throwable{ Object object = pjp.proceed(); // 判断是否需要脱敏处理 if (isDesensitization()) { try { Class aClass = object.getClass(); // 改变返回值 Desensitization[] value = desensitizationWord.value(); JSONObject ob = JSON.parseObject(JSON.toJSONString(object)); // 循环处理每一个 json 路径下的值 for (Desensitization s : value) { replace(ob, s); } object = JSON.parseObject(JSON.toJSONString(ob), aClass); } catch (Exception e) { log.info("数据脱敏失败:" + e.getMessage()); } } return object; } /** * 判断是否需要脱敏 * @return */ private boolean isDesensitization() { // 获取用户角色,根据角色判断是否进行脱敏处理 String userRole = AuthUtil.getUserRole(); // 如果用户角色为null,则需要脱敏 if (null!=userRole && !userRole.equals("")){ // 判断角色是否为民警角色,如果是则不需要脱敏,否则需要脱敏处理 if (userRole.equals("民警")){ return false; } } return true; } /** * 脱敏 */ private Object sensitiveByString(Object value) { if (StringUtils.isNotEmpty(value.toString())) { String st = Convert.toStr(value); st = st.substring(0, st.length() - 3 > 0 ? 3 : st.length()) + "****" + st.substring(Math.max(st.length() - 4, 0)); return st; } return value; } /** * 敏感数据替换 * * @param jsonObject * @param s */ private void replace(JSONObject jsonObject, Desensitization s) { // 只有传入的 JSON 路径在 这个 JSONObject 中才会进行脱敏处理 if (JSONPath.contains(jsonObject, s.jsonPath())) { // 查询是否有数组 列表 类型的数据需要脱敏 int index = s.jsonPath().lastIndexOf("[*]"); if (index > -1) { String prefix = StrUtil.subPre(s.jsonPath(), index); String suffix = StrUtil.subSuf(s.jsonPath(), index + 3); // 提取json 路径下的 数组\链表 元素 Object eval = JSONPath.eval(jsonObject, prefix); // 将数组\链表 元素 转为 JSONArray 方便做 统一格式处理 JSONArray jsonArray = (JSONArray) eval; int size = jsonArray.size(); for (int i = 0; i < size; i++) { // 由于脱敏数组内部的参数传入格式为 :jsonPath = "$.data.records[*].username" // 所以需要重新组装 jsonPath 将 * 号 替换成具体的值 String indexJsonPath = StrUtil.strBuilder().append(prefix).append("[").append(i).append("]").append(suffix).toString(); // 使用 cn.hutool.core.convert Convert.toStr 转换为字符串 如果给定的值为null,或者转换失败,返回默认值null,这样可以减少报错,避免程序异常 String desensitized = Convert.toStr(JSONPath.eval(jsonObject, indexJsonPath)); if (StrUtil.isBlank(desensitized)) { continue; } // 如果是默认指定 则使用默认方式脱敏 if (s.desensitizedType() == FIXED_PHONE) { desensitized = sensitiveByString(desensitized).toString(); }else { // 否则使用 cn.hutool.core.util 进行数据脱敏 desensitized = DesensitizedUtil.desensitized(desensitized, s.desensitizedType()); } // 使用JSON 路径操作,将已经脱敏的新数据,放入之前未脱敏的数据地址处,替换未脱敏数据 JSONPath.set(jsonObject, indexJsonPath, desensitized); } } else { // 使用 cn.hutool.core.convert Convert.toStr 转换为字符串 如果给定的值为null,或者转换失败,返回默认值null,这样可以减少报错,避免程序异常 Object eval = JSONPath.eval(jsonObject, Convert.toStr(s.jsonPath())); String desensitized = ""; if (s.desensitizedType() == FIXED_PHONE) { desensitized = sensitiveByString(Convert.toStr(eval)).toString(); }else { desensitized = DesensitizedUtil.desensitized(Convert.toStr(eval), s.desensitizedType()); } JSONPath.set(jsonObject, s.jsonPath(), desensitized); } } } }