基于Mybatis插件方式实现数据脱敏处理

一、项目介绍

1、项目背景

有时候我们数据库中存储一些敏感的信息比如手机号银行卡号,我们希望我们查询出来的的时候对一些敏感信息做一些脱敏处理。

当面项目是基于自定义Mybatis插件方式实现数据脱敏处理,通过插件拦截结果集进行脱敏后再返回,所以对于使用者透明,业务逻辑无感知。

目前支持用户名脱敏、手机号脱敏、座机号码脱敏、银行卡脱敏、身份证号脱敏、邮箱脱敏、地址脱敏。

2、注解说明

<span style="color:#4b4b4b"><span style="background-color:#ffffff"><code class="language-java"><span style="color:#75715e">/**
 * 对需要脱敏的字段加上该注解
 */</span>
<span style="color:#75715e">@Documented</span>
<span style="color:#75715e">@Inherited</span>
<span style="color:#75715e">@Retention(RetentionPolicy.RUNTIME)</span>
<span style="color:#75715e">@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})</span>
<span style="color:#f92672">public</span> <span style="color:#75715e">@interface</span> SensitiveField {

    <span style="color:#75715e">/**
     * 脱敏类型
     */</span>
    SensitiveTypeEnums <span style="color:#a6e22e">value</span><span style="color:#f8f8f2">()</span>;

    <span style="color:#75715e">/**
     * 填充值
     */</span>
    String <span style="color:#a6e22e">fillValue</span><span style="color:#f8f8f2">()</span> <span style="color:#f92672">default</span> <span style="color:#e6db74">"*"</span>;

}
</code></span></span>

目前支持脱敏类型

<span style="color:#4b4b4b"><span style="background-color:#ffffff"><code class="language-java"><span style="color:#75715e">/**
 * 脱敏类型
 */</span>
<span style="color:#f92672">public</span> <span style="color:#f92672">enum</span> <span style="color:#a6e22e">SensitiveTypeEnums</span> {

    <span style="color:#75715e">/**
     * 默认方式脱敏
     */</span>
    DEFAULT(<span style="color:#ae81ff">0</span>,<span style="color:#ae81ff">6</span>),

    <span style="color:#75715e">/**
     * 中文名称
     */</span>
    CHINESE_NAME(<span style="color:#ae81ff">1</span>,<span style="color:#ae81ff">1</span>),

    <span style="color:#75715e">/**
     * 手机号
     */</span>
    MOBILE(<span style="color:#ae81ff">3</span>,<span style="color:#ae81ff">4</span>),

    <span style="color:#75715e">/**
     * 座机号码
     */</span>
    FIXED_PHONE(<span style="color:#ae81ff">0</span>,<span style="color:#ae81ff">4</span>),

    <span style="color:#75715e">/**
     * 银行卡
     */</span>
    BANK_CARD(<span style="color:#ae81ff">6</span>,<span style="color:#ae81ff">4</span>),

    <span style="color:#75715e">/**
     * 身份证号
     */</span>
    ID_CARD(<span style="color:#ae81ff">0</span>,<span style="color:#ae81ff">4</span>),

    <span style="color:#75715e">/**
     * 邮箱
     */</span>
    EMAIL(<span style="color:#ae81ff">2</span>,<span style="color:#ae81ff">0</span>),

    <span style="color:#75715e">/**
     * 地址
     */</span>
    ADDRESS(<span style="color:#ae81ff">6</span>,<span style="color:#ae81ff">4</span>),

    ;
}
</code></span></span>

举例: 如11位的手机号,默认脱敏策略是显示(3,4)前三后四,如 13712345678 脱敏后变成 137****5678。

二、实现原理

1、该插件项目的原理

通过拦截器拦截Mybatis的select语句,通过自定义注解获取到需要脱敏处理的属性字段,并为该属性根据不同的脱敏类型进行脱敏,处理后再返回。

2、插件源码

<span style="color:#4b4b4b"><span style="background-color:#ffffff"><code class="language-java"><span style="color:#75715e">/**
 * 基于拦截器对数据脱敏
 *
 * <span style="color:#808080">@author</span> xub
 * <span style="color:#808080">@date</span> 2022/6/2 下午2:23
 */</span>
<span style="color:#75715e">@Intercepts({
        @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {java.sql.Statement.class})
})</span>
<span style="color:#f92672">public</span> <span style="color:#f92672">class</span> <span style="color:#a6e22e">DesensitizationInterceptor</span> <span style="color:#f92672">implements</span> <span style="color:#a6e22e">Interceptor</span> {

    <span style="color:#f92672">private</span> <span style="color:#f92672">static</span> <span style="color:#f92672">final</span> <span style="color:#e6db74">Logger</span> <span style="color:#e6db74">log</span> <span style="color:#ab5656">=</span> LoggerFactory.getLogger(DesensitizationInterceptor.class);

    <span style="color:#75715e">/**
     * key值为class对象 value可以理解成是该类带有SensitiveField注解的属性,只不过对属性封装了一层。
     * 它是非常能够提高性能的处理器 它的作用就是不用每一次一个对象经来都要看下它的哪些属性带有SensitiveField注解
     * 毕竟类的反射在性能上并不友好。只要key包含该对象那就不需要检查它哪些属性带SensitiveField注解。
     */</span>
    <span style="color:#f92672">private</span> Map<Class, List<Handler>> handlerMap = <span style="color:#f92672">new</span> <span style="color:#a6e22e">ConcurrentHashMap</span><>();

    <span style="color:#75715e">@Override</span>
    <span style="color:#f92672">public</span> Object <span style="color:#a6e22e">intercept</span><span style="color:#f8f8f2">(Invocation invocation)</span> <span style="color:#f92672">throws</span> Throwable {
        <span style="color:#75715e">// 获取结果</span>
        List<Object> results = (List<Object>) invocation.proceed();
        <span style="color:#f92672">if</span> (CollectionUtils.isEmpty(results)) {
            <span style="color:#f92672">return</span> results;
        }
        <span style="color:#75715e">// 批量设置加密</span>
        <span style="color:#f92672">for</span> (Object object : results) {
            process(object);
        }
        <span style="color:#f92672">return</span> results;
    }


    <span style="color:#f92672">private</span> <span style="color:#f92672">void</span> <span style="color:#a6e22e">process</span><span style="color:#f8f8f2">(Object object)</span> <span style="color:#f92672">throws</span> Throwable {
        <span style="color:#e6db74">Class</span> <span style="color:#e6db74">handlerKey</span> <span style="color:#ab5656">=</span> object.getClass();
        List<Handler> handlerList = handlerMap.get(handlerKey);

        <span style="color:#75715e">//性能优化点,如果有两个都是user对象同时,那么只需有个进行反射处理属性就好了,另一个只需执行下面的for循环</span>
        SYNC:
        <span style="color:#f92672">if</span> (handlerList == <span style="color:#ae81ff">null</span>) {
            <span style="color:#f92672">synchronized</span> (<span style="color:#e6db74">this</span>) {
                handlerList = handlerMap.get(handlerKey);
                <span style="color:#75715e">//如果到这里map集合已经存在,则跳出到指定SYNC标签</span>
                <span style="color:#f92672">if</span> (handlerList != <span style="color:#ae81ff">null</span>) {
                    <span style="color:#f92672">break</span> SYNC;
                }
                handlerMap.put(handlerKey, handlerList = <span style="color:#f92672">new</span> <span style="color:#a6e22e">ArrayList</span><>());
                <span style="color:#75715e">// 反射工具类 获取带有SensitiveField注解的所有属性字段</span>
                Set<Field> allFields = ReflectionUtils.getAllFields(
                        object.getClass(),
                        input -> input != <span style="color:#ae81ff">null</span> && input.getAnnotation(SensitiveField.class) != <span style="color:#ae81ff">null</span>
                );

                <span style="color:#f92672">for</span> (Field field : allFields) {
                    handlerList.add(<span style="color:#f92672">new</span> <span style="color:#a6e22e">Handler</span>(field));
                }
            }
        }
        <span style="color:#f92672">for</span> (Handler handler : handlerList) {
            handler.accept(object);
        }
    }


    <span style="color:#75715e">@Override</span>
    <span style="color:#f92672">public</span> Object <span style="color:#a6e22e">plugin</span><span style="color:#f8f8f2">(Object target)</span> {
        <span style="color:#f92672">return</span> Plugin.wrap(target, <span style="color:#e6db74">this</span>);
    }

    <span style="color:#75715e">@Override</span>
    <span style="color:#f92672">public</span> <span style="color:#f92672">void</span> <span style="color:#a6e22e">setProperties</span><span style="color:#f8f8f2">(Properties properties)</span> {
    }


    <span style="color:#f92672">private</span> <span style="color:#f92672">static</span> <span style="color:#f92672">class</span> <span style="color:#a6e22e">Handler</span> {
        Field field;

        Handler(Field field) {
            <span style="color:#e6db74">this</span>.field = field;
        }

        <span style="color:#f92672">private</span> <span style="color:#e6db74">boolean</span> <span style="color:#a6e22e">checkField</span><span style="color:#f8f8f2">(Object object, Field field)</span> <span style="color:#f92672">throws</span> IllegalAccessException {
            <span style="color:#f92672">if</span> (!field.isAccessible()) {
                field.setAccessible(<span style="color:#ae81ff">true</span>);
            }
            <span style="color:#75715e">//如果为空 那么就不用进行脱敏操作了</span>
            <span style="color:#f92672">return</span> field.get(object) != <span style="color:#ae81ff">null</span>;
        }

        <span style="color:#f92672">public</span> <span style="color:#f92672">void</span> <span style="color:#a6e22e">accept</span><span style="color:#f8f8f2">(Object o)</span> <span style="color:#f92672">throws</span> Throwable {
            <span style="color:#f92672">if</span> (checkField(o, field)) {
                <span style="color:#e6db74">SensitiveField</span> <span style="color:#e6db74">annotation</span> <span style="color:#ab5656">=</span> field.getAnnotation(SensitiveField.class);
                <span style="color:#e6db74">SensitiveTypeEnums</span> <span style="color:#e6db74">typeEnums</span> <span style="color:#ab5656">=</span> annotation.value();
                <span style="color:#e6db74">String</span> <span style="color:#e6db74">fillValue</span> <span style="color:#ab5656">=</span> annotation.fillValue();
                <span style="color:#e6db74">Object</span> <span style="color:#e6db74">o1</span> <span style="color:#ab5656">=</span> field.get(o);
                log.info(<span style="color:#e6db74">"加密之前数据 = {}"</span>,o1);
                <span style="color:#e6db74">SensitiveStrategy</span> <span style="color:#e6db74">sensitiveStrategy</span> <span style="color:#ab5656">=</span> SensitiveContext.get(typeEnums);
                <span style="color:#e6db74">String</span> <span style="color:#e6db74">s</span> <span style="color:#ab5656">=</span> sensitiveStrategy.handle(o1, fillValue);
                log.info(<span style="color:#e6db74">"加密之后数据 = {}"</span>,s);
                field.set(o, s);
            }
        }
    }
}
</code></span></span>

三、使用方式

1、添加jar包

<span style="color:#4b4b4b"><span style="background-color:#ffffff"><code class="language-xml"> <span style="color:#f8f8f2"><<span style="color:#f92672 !important">dependency</span>></span>
    <span style="color:#f8f8f2"><<span style="color:#f92672 !important">groupId</span>></span>com.jincou<span style="color:#f8f8f2"></<span style="color:#f92672 !important">groupId</span>></span>
    <span style="color:#f8f8f2"><<span style="color:#f92672 !important">artifactId</span>></span>mybatis-sensitive-plugin-starter<span style="color:#f8f8f2"></<span style="color:#f92672 !important">artifactId</span>></span>
    <span style="color:#f8f8f2"><<span style="color:#f92672 !important">version</span>></span>1.0.0<span style="color:#f8f8f2"></<span style="color:#f92672 !important">version</span>></span>
 <span style="color:#f8f8f2"></<span style="color:#f92672 !important">dependency</span>></span>
</code></span></span>

2、User实体添加注解

<span style="color:#4b4b4b"><span style="background-color:#ffffff"><code class="language-java"><span style="color:#75715e">/**
 * 用户信息表
 */</span>
<span style="color:#75715e">@Data</span>
<span style="color:#f92672">public</span> <span style="color:#f92672">class</span> <span style="color:#a6e22e">User</span> <span style="color:#f92672">implements</span> <span style="color:#a6e22e">Serializable</span> {

    <span style="color:#f92672">private</span> <span style="color:#f92672">static</span> <span style="color:#f92672">final</span> <span style="color:#e6db74">long</span> <span style="color:#e6db74">serialVersionUID</span> <span style="color:#ab5656">=</span> <span style="color:#ae81ff">1L</span>;

    <span style="color:#75715e">/**
     * 主键
     */</span>
    <span style="color:#f92672">private</span> Integer id;

    <span style="color:#75715e">/**
     * 姓名
     */</span>
    <span style="color:#75715e">@SensitiveField(SensitiveTypeEnums.CHINESE_NAME)</span>
    <span style="color:#f92672">private</span> String name;

    <span style="color:#75715e">/**
     * 邮箱
     */</span>
    <span style="color:#75715e">@SensitiveField(SensitiveTypeEnums.EMAIL)</span>
    <span style="color:#f92672">private</span> String email;

    <span style="color:#75715e">/**
     * 手机号
     */</span>
    <span style="color:#75715e">@SensitiveField(SensitiveTypeEnums.MOBILE)</span>
    <span style="color:#f92672">private</span> String mobile;

    <span style="color:#75715e">/**
     * 地址
     */</span>
    <span style="color:#75715e">@SensitiveField(SensitiveTypeEnums.ADDRESS)</span>
    <span style="color:#f92672">private</span> String address;
}
</code></span></span>

3、请求接口

<span style="color:#4b4b4b"><span style="background-color:#ffffff"><code class="language-java"><span style="color:#75715e">@RestController</span>
<span style="color:#f92672">public</span> <span style="color:#f92672">class</span> <span style="color:#a6e22e">TestController</span> {

    <span style="color:#75715e">@Autowired</span>
   <span style="color:#f92672">private</span> UserMapper userMapper;

    <span style="color:#75715e">@GetMapping("/getById")</span>
    User <span style="color:#a6e22e">get</span><span style="color:#f8f8f2">(Integer id)</span>{
     <span style="color:#f92672">return</span>  userMapper.selectByPrimaryKey(id);
    }


    <span style="color:#75715e">@GetMapping("/findAllUser")</span>
    List<User> <span style="color:#a6e22e">findAllUser</span><span style="color:#f8f8f2">()</span>{
     <span style="color:#f92672">return</span>  userMapper.findAllUser();
    }
}
</code></span></span>

请求接口url

<span style="color:#4b4b4b"><span style="background-color:#ffffff"><code class="language-bash">http://localhost:8080/findAllUser
</code></span></span>

4、接口返回数据

<span style="color:#4b4b4b"><span style="background-color:#ffffff"><code class="language-json">[
    {
        <span style="color:#f92672">"id"</span>:<span style="color:#ae81ff">1</span>,
        <span style="color:#f92672">"age"</span>:<span style="color:#ae81ff">10</span>,
        <span style="color:#f92672">"name"</span>:<span style="color:#e6db74">"z*****u"</span>,
        <span style="color:#f92672">"email"</span>:<span style="color:#e6db74">"45*******@qq.com"</span>,
        <span style="color:#f92672">"mobile"</span>:<span style="color:#e6db74">"137****2222"</span>,
        <span style="color:#f92672">"address"</span>:<span style="color:#e6db74">"宁波市慈溪市***********鸣鹤古镇"</span>
    },
    {
        <span style="color:#f92672">"id"</span>:<span style="color:#ae81ff">2</span>,
        <span style="color:#f92672">"age"</span>:<span style="color:#ae81ff">20</span>,
        <span style="color:#f92672">"name"</span>:<span style="color:#e6db74">"l**i"</span>,
        <span style="color:#f92672">"email"</span>:<span style="color:#e6db74">"xu****@outlook.com"</span>,
        <span style="color:#f92672">"mobile"</span>:<span style="color:#e6db74">"139****5678"</span>,
        <span style="color:#f92672">"address"</span>:<span style="color:#e6db74">"西安市未央区************100米"</span>
    },
    {
        <span style="color:#f92672">"id"</span>:<span style="color:#ae81ff">8</span>,
        <span style="color:#f92672">"age"</span>:<span style="color:#ae81ff">30</span>,
        <span style="color:#f92672">"name"</span>:<span style="color:#e6db74">"w****a"</span>,
        <span style="color:#f92672">"email"</span>:<span style="color:#e6db74">"wa****@163.com"</span>,
        <span style="color:#f92672">"mobile"</span>:<span style="color:#e6db74">"137****5678"</span>,
        <span style="color:#f92672">"address"</span>:<span style="color:#e6db74">"西安市未央区************100米"</span>
    }
]
</code></span></span>

5、表中原始数据

<span style="color:#4b4b4b"><span style="background-color:#ffffff"><code class="language-java"><span style="color:#ae81ff">1</span>	<span style="color:#ae81ff">10</span>	zhaoliu	<span style="color:#ae81ff">450760999</span><span style="color:#75715e">@qq</span>.com	<span style="color:#ae81ff">13722222222</span>	宁波市慈溪市观海卫镇禹皇路<span style="color:#ae81ff">999</span>号鸣鹤古镇
<span style="color:#ae81ff">2</span>	<span style="color:#ae81ff">20</span>	lisi	xu5555<span style="color:#75715e">@outlook</span>.com	<span style="color:#ae81ff">13912345678</span>	西安市未央区凤城二路与连心路交叉口南<span style="color:#ae81ff">100</span>米
<span style="color:#ae81ff">8</span>	<span style="color:#ae81ff">30</span>	wangba	wangba@<span style="color:#ae81ff">163.</span>com	<span style="color:#ae81ff">13712345678</span>	西安市未央区凤城二路与连心路交叉口南<span style="color:#ae81ff">100</span>米
</code></span></span>

项目地址mybatis-desensitization-plugin

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值