一、背景

1.1 项目运行环境

  1. spring-boot 2.0.6
  2. 数据库:mongodb 4.4.4
  3. orm框架:spring-data-mongodb
  4. java 10

1.2 关于四要素脱敏

所谓四要素是指姓名、身份证号、手机号、银行卡号四个比较重要的敏感信息,为防止用户信息泄密造成的损失,所以四要素在存储到数据库时,需要进行脱敏,即加密后落库。

1.3 脱敏数据的CRUD

要实现脱敏,最主要是要解决两个问题,一是加密后入库,二是出库后解密,spring-data-mongodb的事件监听顺路AbstractMongoEventListener提供了多种类型的事件监听,完成可以满足以上需求,开发者只需要继承该抽象类并重写对应的方法,就可以实现需要的功能。比如onBeforeConvert事件在 object 被MongoConverter转换为Document之前触发,在MongoTemplate insert,insertList和save操作中调用,所以想要实现加密后落库,只需要重写该方法,将四要素进行加密即可。类似的blog网上有很多,我这里就不再赘述了。

1.4 存在的问题

虽然AbstractMongoEventListener提供的事件回调有5种之多,几乎涵盖了所有可能的场景。但却不能实现使用明文进行查询,也就是说,当我需要使用四要素进行数据库查询时,必须先将参数加密,才能进行查询,直接使用明文将无法查到结果。举例说明:

public interface UserRepository extends MongoRepository<UserPO, String> {Optional<UserPO> findByIdNo(String idNo);
}

我这里使用身份证号idNo来查询用户信息,但是在实际调用该方法时,需要

String idNo = "310xxxxxxxxxxxxxxx";
idNo = CryptUtils.encrypt(idNo);
userRepository.findByIdNo(idNo);

也就是先加密,再查询。显然这种冗余的代码是不够优雅的,而且不够仔细或者对项目不够了解时,容易造成意料之外的big。

二、实现方案

经过以上分析,想必读者肯定在问,有没有一种优雅的,不必手动加密的实现方式,在对原Repository侵入最小的前提下,实现明文查询呢?答案是肯定的。

2.1 原理

使用AOP思想对UserRepository进行代理,获取到方法执行参数后,对其中指定的参数进行加密,然后再执行原方法。

2.2 放码过来

切面代码如下:

@Component
@Aspect
@SuppressWarnings("unchecked")
public class QueryAspect {@Pointcut(value = "execution( * com.cui.common.domain.*.*.*(..))")private void pt() {}/*** 前置通知*/@Before("pt()")public void before() {}/*** 后置通知*/@AfterReturning("pt()")public void afterReturning() {}/*** 异常通知*/@AfterThrowing("pt()")public void afterThrowing() {}/*** 最终通知*/@After("pt()")public void after() {}/*** 环绕通知* Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个proceed()方法,此方法就相当于明确调用切入点方法。* 该接口可以作为环绕通知的方法参数,在程序执行时,Spring框架会为我们提供该接口的实现类供我们使用。* <p>* Spring中的环绕通知:* 它是Spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。*/@Around("pt()")public Object around(ProceedingJoinPoint pjp) throws Throwable {// 得到方法执行所需的参数Object[] args = pjp.getArgs();EncryptParam encryptParam = ((MethodSignature) pjp.getSignature()).getMethod().getAnnotation(EncryptParam.class);if (encryptParam == null) {return pjp.proceed(args);}int[] indexes = encryptParam.indexes();for (int i : indexes) {if (args[i] instanceof List) {List<String> list = (List<String>) args[i];args[i] = list.stream().map(CryptUtils::encrypt).collect(Collectors.toList());} else if (args[i] instanceof String) {args[i] = CryptUtils.encrypt((String) args[i]);}}// 明确调用业务层方法(切入点方法)return pjp.proceed(args);}
}

其中EncryptParam是一个自定义的注解,

@Documented
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptParam {/*** 所在方法要加密的参数的下标集合* @return*/int[] indexes();
}

通过在目前方法上加上该注解并指定indexes为要加密的参数的下标集合,即可实现该方法的明文查询。改造后的UserRepository如下,

public interface UserRepository extends MongoRepository<UserPO, String> {@EncryptParam(indexes = {0})Optional<UserPO> findByIdNo(String idNo);
}

因为idNo是要加密的参数,所以indexes = {0},如果有多个参数以此类推。

Tips:这块需要基本的动态代理知识,不熟悉的同学可以去网上搜索。

三、方案分析

3.1 利

  1. 使用方便,对于使用四要素查询的方法,只需增加一个注解并指定参数下标即可,不再需要查询前加密参数。程序更加优雅。
  2. 避免漏洞产生,加密过程封装在动态代理程序中,开发人员使用时不需要了解其中的具体逻辑也不会将其改动。对于新手或者对项目不熟悉或者比较粗心的开发人员比较友好。

3.2 弊

  1. EncryptParam注解的indexes参数过于耦合,一旦指定就无法随意增删方法参数(需要同时修改indexes),可能会造成影响。

四、补充说明

除了使用EncryptParam的indexes参数来指定方法需要加密的参数以外,可能有部分读者想到通过参数名来确定哪个或者哪些参数是需要加密的。还是UserRepository以例,假设我事先配置idNo字段为加密字段,那么当切面获取到该参数时,即进行加密。使用这种方法,甚至不需要对原UserRepository作任何修改,伪代码如下:

@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {// 得到方法执行所需的参数Object[] args = pjp.getArgs();String[] names = ((CodeSignature) pjp.getSignature()).getParameterNames();List<String> configParams = loadConfig();for (int i = 0; i < names.length; i++) {if (configParams.contains(names[i])) {args[i] = CryptUtils.encrypt(args[i]);}}// 明确调用业务层方法(切入点方法)return pjp.proceed(args);
}

这种实现方案看似十分简捷且可行,但在这里却有一个非常致命的问题,就是无法获取到参数名(names)。在辨析动态代理的两种方式JDK代理和cglib代理时,二者一个重要的区别是前者无法通过getParameterNames方法获取到参数名称,而后者可以。

虽然springboot 2.0.0之前已经默认使用cglib方式实现动态代理,但我这里使用spring-data-mongo却仍旧使用的是JDK代理,所以就产生了上述的问题,只能退而求其次,使用EncryptParam注解来指定参数下标。站在程序员的角度上来看,真是一件遗憾的事情。what a pity!

springboot mongodb 脱敏数据的明文查询相关推荐

  1. java mongodb 模糊查询_Java操作MongoDB插入数据进行模糊查询与in查询功能的方法

    Java操作MongoDB插入数据进行模糊查询与in查询功能 由于需要用MongoDB缓存数据,所以自己写了一套公共的存放和读取方法 具体如下: 存放mongodb: /** * 公共方法:设置Obj ...

  2. AOP实现注解式脱敏数据明文查询

    最近又遇到了脱敏数据查询相关的问题,常规的脱敏数据比如用户身份证将中间位数抹去后加入数据库,那么查询时需要手动调用就比较麻烦,不过可以使用自定义注解,利用AOP解析后在切面将数据加密再作为参数注入运行 ...

  3. SpringBoot+MongoDB查询大数据字段优化

    记录一下 SpringBoot+MongoDB查询大数据字段,查询的单个字段或者总查询结果量太大 用 mongoTemplate.find(query, NewSnapshot.class, coll ...

  4. 时间序列数据和MongoDB:第三部分 - 查询,分析和呈现时间序列数据

    作者:Robert Walters 译者:刘东华 (Martin Liu) 在 时间序列数据和MongoDB中:第一部分 - 简介 我们回顾了您需要了解的关键问题,以了解数据库的查询访问模式.在 时间 ...

  5. springboot项目中mybatis实现数据的基本查询

    SpringBoot项目中mybatis实现数据的基本查询 本章内容概述: mapper 查询 xml 文件基本使用 通过 mybatis 实现一条数据的查询 1 用户数据表 2 用户信息对应的实体类 ...

  6. mongoDB大数据查询坑

    有一个30亿量级数据的库,如何全量爬取并分析? 因为量级过大无法一次性爬取至本地再分析,考虑使用limit().skip()混合的方法,一次读取1万条数据进行分析存储,30亿数据分成30万份后再合并分 ...

  7. idea+springboot+mongodb的实战使用分享

    昨天的时候我们先在网上找了测试类,测试了一下mongdb的简单使用,今天就来实地在项目中用一用 没安装mongodb的参考我上一篇文章:idea+springboot+mongodb的简单测试使用分享 ...

  8. SpringBoot+MongoDB GridFS文件上传、下载、预览实战

    SpringBoot + MongoDB GridFS 随着web 3.0的兴起,数据的形式不局限于文字,还有语音.视频.图片等.高效存储与检索二进制数据也成为web 3.0必须要考虑的问题.然而这种 ...

  9. 视频教程-SpringBoot+MongoDB+Vue前后分离-Java

    SpringBoot+MongoDB+Vue前后分离 10多年互联网一线实战经验,现就职于大型知名互联网企业,架构师, 有丰富实战经验和企业面试经验:曾就职于某上市培训机构数年,独特的培训思路,培训体 ...

最新文章

  1. 十大python开发软件-2017年最棒的七个Python图形应用GUI开发框架
  2. Shell 编程快速入门
  3. stripslashes和addslashes的区别
  4. 史上超强的学科碰撞,有生之年必看系列!
  5. 前端Chrome调试技巧汇总
  6. [Python] 字典 get(key, default=None):获取字典中相应键的对应值
  7. Spark Streaming 源码详解
  8. TF-tf.keras.layers.Conv1D
  9. java pdf 阅读器_纯Java文档阅读器(word、pdf等)
  10. T410i升级i3 380M,上测试对比图,附拆机心得
  11. 免费申请微软云教育服务器,自助免费申请Office365教育版,免费5TOneDrive云盘详细教程...
  12. 2017吉首大学新生赛周老师的区间问题
  13. 如何让mysql数据库支持超大图片
  14. 在NS2 AODV协议中添加blackhole attacker(黑洞攻击) [转载]
  15. 总结SSL/TLS协议运行机制
  16. 智能座舱之HUD-发展趋势深度解析
  17. C++成员变量指针和成员函数指针
  18. WIN10升级后无线网卡被禁用解决办法
  19. ADS1220的几种应用介绍(含源码)
  20. implementation和compile的区别

热门文章

  1. Unity3D中Layers和LayerMask解析
  2. c# 调用post 请求
  3. 在计算机中虚拟内存占用的是哪里的空间,电脑虚拟存储空间在哪个盘
  4. 教你怎样彻底删除SQL
  5. java输入枚举型_Java 枚举型为什么是静态的,以及是怎么实现的?
  6. 少儿创意学编程(Scratch基础篇):第6课——赛艇比赛
  7. reactjs初级学习
  8. Python负数的整除和取余
  9. WordPress自适应博客模板
  10. 基于mysql+php110思阅书城图书销售系统