ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

基于AOP的Controller接口脱敏方式

2021-09-06 19:32:21  阅读:355  来源: 互联网

标签:jsonObject 接口 Controller jsonPath desensitized AOP 注解 脱敏


目录

前言

我们的后台系统收到了数据脱敏的需求, 要求在一些关联页面的手机号, 收货地址等重要信息需要进行脱敏显示. 所以才有了这个脱敏方案

常见方案

方案一

在关键的DTO 和VO上增加注解, 然后这些对象通过接口返回调用者的时候, 会屏蔽掉注释字段的值.
优点:

  1. 只要增加了注释, 所有的返回这个对象的接口都会屏蔽, 不用每个接口单独写代码

缺点:

  1. 无法针对特定的接口精细化控制
  2. 返回的前端的DTO对象是其它微服务中定义的, 不方便在字段上增加注释

方案二

在controller接口中加上注解, 标识出具体的字段需要进行屏蔽
优点:

  1. 精细控制每个接口的返回结果屏蔽
  2. 对于返回对象的类型的代码不用修改

缺点:

  1. 需要每个接口都写上注解,
  2. 需要写AOP切面类

方案三

前端自己进行屏蔽显示
缺点: 属于自己骗自己的方案

综合考虑, 我们的后台系统引用了太多其他微服务的DTO对象, 并直接返回给前端, 无法采用方案一, 最终使用了方案二

实现方式

一次请求大致需要经过的流程如下:
在这里插入图片描述
我采用的是方案二, 实现:

  • 在返回结果的时候切面类中获取注解值. 并将注解值放入ThreadLocal 中, 注解使用的是jsonPath的语法标识屏蔽字段
  • 在序列化的时候, 获取ThreadLocal中的注解, 根据注解的信息屏蔽对应的字段值, 具体屏蔽使用的是fastjson的

注意:

  • 返回响应的时候在切面类在获取注解放入ThreadLocal中, 由于可能在controller的逻辑中会发出http请求, 这样就会在controller中又内嵌上图中的一套逻辑, 过早的放入ThreadLocal 会互相影响.
  • 序列化的时候 从ThreadLocal中获取配置信息, 再使用fastjson的jsonpath 修改对应的值

两个注解类

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SensitiveWord {
    Sensitive[] value();
}
import java.lang.annotation.*;

import static com.youdao.athena.starfire.core.support.sensitive.DesensitizedType.PASSWORD;

@Target({})
@Retention(RetentionPolicy.RUNTIME)
public @interface Sensitive {
    /**
     * json path 的标识
     * @return
     */
    String jsonPath();

    /**
     * 脱敏的字段的数据分类,  默认是密码类型脱敏,   
     * @return
     */
     DesensitizedType desensitizedType() default PASSWORD;
}

切面类

使用匿名内部类和反射的方式获取注解信息, 并放入到ThreadLocal中

    @Bean
    public Advisor pointcutAdvisor() {
        MethodInterceptor interceptor = methodInvocation -> {
            Object proceed = methodInvocation.proceed();

            SensitiveWord annotation = AnnotationUtil.getAnnotation(methodInvocation.getMethod(), SensitiveWord.class);
            if (annotation != null && !this.isExport(methodInvocation)) {
                Sensitive[] value = annotation.value();
                sensitiveThreadLocal.set(value);
            }
            return proceed;
        };

        AnnotationMatchingPointcut pointcut = new AnnotationMatchingPointcut(null, SensitiveWord.class);
        // 配置增强类advisor
        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
        advisor.setPointcut(pointcut);
        advisor.setAdvice(interceptor);
        return advisor;
    }

脱敏序列化类

  • MappingJackson2HttpMessageConverter 覆盖这个类的writeInternal 方法 , 在序列化的时候进行脱敏操作
  • replace 方法是进行jsonPath 替换的, 使用到的DesensitizedUtil 类是hutool的脱敏相关的工具类
    @Bean
    public MappingJackson2HttpMessageConverter getMappingJackson2HttpMessageConverter() {
        MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter() {
            /**
             * 重写方法, 进行敏感信息的脱敏操作
             * @param object
             * @param type
             * @param outputMessage
             * @throws IOException
             * @throws HttpMessageNotWritableException
             */
            @Override
            protected void writeInternal(Object object, Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
                Sensitive[] values = InterceptorConfig.sensitiveThreadLocal.get();
                if (values == null) {
                    super.writeInternal(object, type, outputMessage);
                    return;
                }
                JSONObject jsonObject = (JSONObject) JSON.toJSON(object);
                for (Sensitive sensitive : values) {
                    String jsonPath = sensitive.jsonPath();
                    replace(jsonObject, jsonPath);
                }
                super.writeInternal(jsonObject, type, outputMessage);
                InterceptorConfig.sensitiveThreadLocal.remove();
            }
        };

        return mappingJackson2HttpMessageConverter;
    }

    /**
     * 敏感数据替换
     *
     * @param jsonObject
     * @param jsonPath
     */
    private static void replace(JSONObject jsonObject, String jsonPath) {
        if (JSONPath.contains(jsonObject, jsonPath)) {
            int index = jsonPath.lastIndexOf("[*]");
            if (index > -1) {
                String prefix = StrUtil.subPre(jsonPath, index);
                String suffix = StrUtil.subSuf(jsonPath, index + 3);
                Object eval = JSONPath.eval(jsonObject, prefix);
                JSONArray jsonArray = (JSONArray) eval;
                int size = jsonArray.size();
                for (int i = 0; i < size; i++) {
                    String indexJsonPath = StrUtil.strBuilder().append(prefix).append("[").append(i).append("]").append(suffix).toString();
                    String desensitized = Convert.toStr(JSONPath.eval(jsonObject, indexJsonPath));
                    if (StrUtil.isBlank(desensitized)) {
                        continue;
                    }
                    desensitized = DesensitizedUtil.desensitized(desensitized, DesensitizedType.MOBILE_PHONE);
                    JSONPath.set(jsonObject, indexJsonPath, desensitized);
                }
            } else {
                Object eval = JSONPath.eval(jsonObject, Convert.toStr(jsonPath));
                String desensitized = DesensitizedUtil.desensitized(Convert.toStr(eval), DesensitizedType.MOBILE_PHONE);
                JSONPath.set(jsonObject, jsonPath, desensitized);
            }
        }
    }

接口类

  • SensitiveWord 注解标识这个接口需要脱敏
  • Sensitive 有几个字段需要脱敏就配置几个次注解
    @SensitiveWord({
            @Sensitive(jsonPath = "$.body.courseUsers[*].mobile",desensitizedType = MOBILE_PHONE),
            @Sensitive(jsonPath = "$.body.courseUsers[*].address",desensitizedType = ADDRESS),
    })
    @PostMapping("/query/list")
    public WebResponse queryList(UserDTO userDTO, @RequestBody CourseUserQueryParam queryParam) {
   
		..... 业务代码省略
    }

接口调用

调用结果 如下图
在这里插入图片描述

踩过的坑

  1. 在切面代理controller方法前面就把注解放入threadLocal中, 结果controller方法中还会通过RPC(http)方式调用其他的微服务的接口, 也会使用到MappingJackson2HttpMessageConverter 进行序列化, 结果就是threadLocal中的注解值被rpc方法使用并释放了

  2. jsonPath在获取指定位置的值的时候, 会忽略调用null的情况, 导致屏蔽错位

标签:jsonObject,接口,Controller,jsonPath,desensitized,AOP,注解,脱敏
来源: https://blog.csdn.net/leisurelen/article/details/120127470

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有