动态脱敏的一种思路
引言
本文只是记录一种动态定义脱敏字段的解决方法。
本人是一个Java实习生,本方法只针对特殊环境进行使用。
此方法已运用与某区域医疗集成平台以及某健康门户Vip平台
注:此方法不适合大并发项目。
现有方案
-
基于注解
-
优点:方便 缺点:无法不重启修改需要脱敏的字段,不符合需求
-
-
前端脱敏
优点:无需技巧
缺点:稍微懂一点计算机的,看一下浏览器控制台就拿到数据了。一般不可取
思路
-
无需重启即可更新字段
-
解决方案
-
写在独立的配置文件
-
独立维护一个表(脱敏表),存储脱敏规则,定期读取更新或者触发式读取更新
此处独立选择维护一个脱敏表。
项目启动时,将需要脱敏的表,字段以及字段对应的规则全部读取存入Hash Map 中。
对外暴露一个新增和更新接口,触发新增或者更新接口时,通知工具类对Hash Map进行更新。(观察者模式)
下为规则更新的伪代码。仅展示单个表的方法,多表则修改Hash Map的存储类型即可
public class DesensitiseUtil{ /** * Long为角色id,List<String>规则存储方法 */ static HashMap<Long, List<String>> desensitiseMap = new HashMap<>(); /** * 观察者模式,插入新的规则或者更新规则触发 */ public static void update() { // 读取数据库 // 根据Key更新对应的 Hash Map的值 } }
下为外暴接口,提供给Controller层调用
@Service public class DesensitiseServiceImpl implements DesensitiseService { @Override public int insertOrUpdateRule(Dto dto){ // 插入或者更新 // 根据dto的输入更新脱敏规则表 // 调用DesensitiseUtil的更新接口 DesensitiseUtil.update(); // 返回插入或者更新影响行数 } }
-
-
-
脱敏方法
-
解决方案
- 通过反射进行脱敏
public class DesensitiseUtil { /** * 对任意类型的属性进行 * * @param result 查询结果 * @param fieldName 需要脱敏的属性 */ public static <T> void desensitise(T result, String fieldName) { Field field = ReflectionUtils.findField(result.getClass(), fieldName); if (field != null) { field.setAccessible(true); switch (fieldName) { case "mobile": { ReflectionUtils.setField(field, result, SensitiveUtil.sensitiveMobilePhone(ReflectionUtils.getField(field, result))); break; } case "realName": { ReflectionUtils.setField(field, result, SensitiveUtil.sensitiveName(ReflectionUtils.getField(field, result))); break; } case "certificateNum": { ReflectionUtils.setField(field, result, SensitiveUtil.sensitiveExitOrEntryPermit(ReflectionUtils.getField(field, result))); break; } default: { break; } } } } /** * 对结果集进行脱敏 * * @param resultList 结果列 * @param fieldName 需要脱敏的属性 */ private static <T> void desensitise(List<T> resultList, String fieldName) { resultList.forEach(result-> desensitise(result, fieldName)); } /** * 对任意List进行脱敏 * * @param resultList 结果集 * @param fieldNameList 需要脱敏的属性 */ private static <T> void desensitise(List<T> resultList, List<String> fieldNameList) { fieldNameList.forEach(fieldName-> desensitise(resultList, fieldName)); } /** * 对返回体内的结果集进行批量脱敏 * * @param result 结果集 * @param rowsName 结果集所在的属性 * @param fieldNameList 结果集内单个结果需要脱敏的属性 */ @SuppressWarnings({"rawtypes", "unchecked"}) private static <T> void desensitise(T result, String rowsName, List<String> fieldNameList) { Field field = ReflectionUtils.findField(result.getClass(), rowsName); if (field != null) { field.setAccessible(true); List resultList = (List) ReflectionUtils.getField(field, result); if (null != resultList && !resultList.isEmpty()) { fieldNameList.forEach(fieldName->desensitise(resultList, fieldName)); } } } /** * 根据角色id对应的规则进行脱敏 * * @param roleId 角色id,用于获取角色对应的脱敏列表 {@link #getDesensitiseRule(Long)} * @param result 结果集 * @param rowsName 结果集所在的属性 */ public static <T> void desensitise(Long roleId, T result, String rowsName) { List<String> ruleList = getDesensitiseRule(roleId); desensitise(result, rowsName, ruleList); } /** * 根据角色id对应的规则进行脱敏 * * @param roleId 角色id,用于获取角色对应的脱敏列表 {@link #getDesensitiseRule(Long)} * @param resultList 结果列 */ public static <T> void desensitise(Long roleId, List<T> resultList) { List<String> ruleList = getDesensitiseRule(roleId); desensitise(resultList, ruleList); } /** * 通过角色id获得角色对应的脱敏列表 * * @param roleId 角色id,用于获取角色对应的脱敏列表 * @return 角色对应的脱敏列表 */ private static List<String> getDesensitiseRule(Long roleId) { // 获得角色对应的脱敏规则 } }
-
-
调用方法
-
解决方案
-
Mybatis拦截器
@Intercepts( value = { @Signature( type = Executor.class, method = "query", args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class } ) } ) public class DesensitizationInterceptor implements Interceptor { final String INVOCATION_QUERY_METHOD = "query"; @Override public Object intercept(Invocation invocation) throws Throwable { // Query语句 if (INVOCATION_QUERY_METHOD.equals(invocation.getMethod().getName())) { Object proceed = invocation.proceed(); if(proceed instanceof List){ // 拿到角色id,设为roleId // 调用函数 List<entity> list = (List<entity>) proceed; desensitise(roleId,list); // 返回结果 }else if(proceed instanceof Dto){ // 拿到角色id,设为roleId // 调用函数 Entity list = (Entity) proceed; desensitise(roleId,dto,rowsName); // 返回结果 } } return invocation.proceed(); }
- 存在的问题:如果存在查到A,取A的属性,若A的属性是脱敏后的,就会查不到数据
-
AOP 拦截
@Aspect @Component public class DesensitizationAspect { final DesensitizationTool desensitizationTool; // 定义切入点 @Pointcut(...) public void desensitization() { } @Around("desensitization()") public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint) { Object result = null; try { result = proceedingJoinPoint.proceed(); if(result instanceof List){ // 拿到角色id,设为roleId // 调用函数 List<entity> list = (List<entity>) proceed; desensitise(roleId,list); // 返回结果 }else if(result instanceof Dto){ // 拿到角色id,设为roleId // 调用函数 Entity list = (Entity) proceed; desensitise(roleId,dto,rowsName); // 返回结果 } } catch (Throwable throwable) { throwable.printStackTrace(); } return result; }
-
Controller层调用
该方法不再展示,直接调用函数即可,如果是现有项目改造,修改量较大
-
-