方法介绍

我利用了AOP和标签的原理来进行组合,实现脱敏的效果,不足之处就是在于如果查询条数多了会有轻微卡顿,使用分页时完全看不出来卡顿。

接下来我们来实现

  1. 第一步创建配合AOP使用的注解
    注解的含义
    这里有四种类型的元注解:

1@Documented —— 指明拥有这个注解的元素可以被javadoc此类的工具文档化。这种类型应该用于注解那些影响客户使用带注释的元素声明的类型。如果一种声明使用Documented进行注解,这种类型的注解被作为被标注的程序成员的公共API。

2@Target——指明该类型的注解可以注解的程序元素的范围。该元注解的取值可以为TYPE,METHOD,CONSTRUCTOR,FIELD等。如果Target元注解没有出现,那么定义的注解可以应用于程序的任何元素。

3@Inherited——指明该注解类型被自动继承。如果用户在当前类中查询这个元注解类型并且当前类的声明中不包含这个元注解类型,那么也将自动查询当前类的父类是否存在Inherited元注解,这个动作将被重复执行知道这个标注类型被找到,或者是查询到顶层的父类。

4@Retention——指明了该Annotation被保留的时间长短。RetentionPolicy取值为SOURCE,CLASS,RUNTIME。

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 属性标签,配合AOP使用,单独使用无效**/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface ReadableSensitiveVerify {// 枚举的值ReadableSensitiveTypeEnum value();}

建好标签之后我们需要为需要脱敏的字段创建相应的枚举

枚举是一种特殊的类,特殊在它的对象是有限的几个常量对象。它既是一种类(class)类型却又比类类型多了些特殊的约束,枚举的本质是一种受限制的类。

import lombok.AllArgsConstructor;
import lombok.Getter;@Getter
@AllArgsConstructor
public enum ReadableSensitiveTypeEnum {/*** 身份证编号*/ID_CARD,/*** 地址/住址*/ADDRESS,/*** 姓名*/NAME,/*** 手机号*/PHONE,/*** 手机号*/EMAIL,/*** 银行卡号*/BANK_CARD_NO;}

枚举创建完成之后我们就可以在实体属性上添加标签了,等AOP创建完成后有标签的属性就可以实现脱敏的效果了。

    /*** 证件号码*/@TableField("opIDCode")@ReadableSensitiveVerify(ReadableSensitiveTypeEnum.ID_CARD)private String opIDCode;
  1. 第二步创建AOP来实现数据脱敏
  • 首先我们要了解AOP是什么东西。
    AOP是Aspect Oriented Programming的缩写,意思是面向方面编程,与OOP(Object Oriented Programming)面向对象编程对等,都是一种编程思想。从OOP角度分析,我们关注业务的处理逻辑,是属于纵向的行为,从AOP角度分析,我们关注对象行为发生时的问题,是属于横向的行为。AOP有以下概念术语:
    Aspect(切面):一个关注点的模块化,这个关注点可能会横切多个对象。事务管理是J2EE应用中一个关于横切关注点的很好的例子。在Spring AOP中,切面可以使用基于模式)或者基于@Aspect注解的方式来实现。
    Joinpoint(连接点):在程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候。在Spring AOP中,一个连接点总是表示一个方法的执行。
    Advice(通知):在切面的某个特定的连接点上执行的动作。其中包括了“around”、“before”和“after”等不同类型的通知(通知的类型将在后面部分进行讨论)。许多AOP框架(包括Spring)都是以拦截器做通知模型,并维护一个以连接点为中心的拦截器链。
    Pointcut(切入点):匹配连接点的断言。通知和一个切入点表达式关联,并在满足这个切入点的连接点上运行(例如,当执行某个特定名称的方法时)。切入点表达式如何和连接点匹配是AOP的核心:Spring缺省使用AspectJ切入点语法。
    Introduction(引入):用来给一个类型声明额外的方法或属性(也被称为连接类型声明(inter-type declaration))。Spring允许引入新的接口(以及一个对应的实现)到任何被代理的对象。例如,你可以使用引入来使一个bean实现IsModified接口,以便简化缓存机制。
    Target Object(目标对象): 被一个或者多个切面所通知的对象。也被称做被通知(advised)对象。 既然Spring AOP是通过运行时代理实现的,这个对象永远是一个被代理(proxied)对象。
    Weaving(织入):把切面连接到其它的应用程序类型或者对象上,并创建一个被通知的对象。这些可以在编译时(例如使用AspectJ编译器),类加载时和运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。
  • 然后让我们创建我们的AOP
package com.lyxcn.witcity.service.village.Aspect;import com.lyxcn.smartcity.yq.commons.config.security.SecurityConfig.LoginConfig.SecurityUtils;
import com.lyxcn.witcity.service.village.utils.desensitization.DesensitizationUtils;
import com.lyxcn.witcity.service.village.utils.desensitization.ReadableSensitiveTypeEnum;
import com.lyxcn.witcity.service.village.utils.desensitization.ReadableSensitiveVerify;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;/*** DAO切面,插入创建人,创建时间,修改人,修改时间* @Auther LIUXIAO* @Date 2019/10/14*/
@Aspect
@Component
@Configuration
public class DaoAspectCommon {/*** @Author lx* @Description findPage分页脱敏* @Date 2022/6/22 17:45* @Param []* @return void*/@Pointcut("execution(* com.lyxcn.witcity.service.village.mapper.*.*.findPage(..))")public void findPagePointCut() {}/*** @Author lx* @Description info详情脱敏* @Date 2022/6/22 17:45* @Param []* @return void*/@Pointcut("execution(* com.lyxcn.witcity.service.village.mapper.*.*.info(..))")public void infoPointCut() {}/*** @Author lx* @Description findPage分页脱敏* @Date 2022/6/22 17:45* @Param []* @return void*/@AfterReturning(value = "findPagePointCut()",returning = "methodResult")public Object findPagePointCut(JoinPoint proceedingJoinPoint, Object methodResult)  throws Throwable  {// 获取当前访问权限的权限码List<String> module = null;try {module = SecurityUtils.getLoginInfo().getModule();} catch (Exception e) {return methodResult;}// 在权限码中找到脱敏的权限码并填充到list中List result =module.stream().filter(x -> x.contains("ROLE_display")).collect(Collectors.toList());int phone = 0,IDCode = 0, skip = 0, noSkip = 0;// 判定电话if (result.contains("ROLE_display.IDCode")){if (result.contains("ROLE_displayHalf.IDCode")){phone = 2;}phone = 1;}if (result.contains("ROLE_displayHalf.IDCode")){phone = 2;}// 判定身份证if (result.contains("ROLE_display.Phone")){if (result.contains("ROLE_displayHalf.Phone")){IDCode = 2;}IDCode = 1;}if (result.contains("ROLE_displayHalf.Phone")){IDCode = 2;}//获取执行方法的参数if (Objects.isNull(methodResult)) {return null;}List list = null;try {list = (List)methodResult;} catch (Exception e) {}for (Object map:list){Field[] fields = map.getClass().getDeclaredFields();for (Field field :fields){field.setAccessible(true);// 方法参数中属性的指定注解ReadableSensitiveVerify annotation = field.getAnnotation(ReadableSensitiveVerify.class);noSkip++;try {if (Objects.nonNull(annotation)) {if (Objects.nonNull(field)){ReadableSensitiveTypeEnum typeEnum = annotation.value();String valueStr = field.getName();// 利用BeanUtils.getProperty()取出参数String property = BeanUtils.getProperty(map, valueStr);switch (typeEnum) {case PHONE:if (phone == 2){// 脱敏工具类脱敏property = DesensitizationUtils.desensitizedPhoneNumber(property);}if (phone == 0){property = null;}break;case ID_CARD:if (IDCode == 2){property = DesensitizationUtils.desensitizedIdNumber(property);}if (IDCode == 0){property = null;}break;default:}// 利用BeanUtils.setProperty() 设置脱敏后的参数BeanUtils.setProperty(map ,valueStr, property);}}else{skip++;}}catch (Exception e){}}if (noSkip == skip){return methodResult;}else{skip = 0;noSkip = 0;}}return methodResult;}/*** @Author lx* @Description info详情脱敏* @Date 2022/6/22 17:45* @Param []* @return void*/@AfterReturning(value = "infoPointCut()",returning = "methodResult")public Object infoPointCut(JoinPoint proceedingJoinPoint, Object methodResult)  throws Throwable  {List<String> module = null;try {module = SecurityUtils.getLoginInfo().getModule();} catch (Exception e) {e.printStackTrace();return methodResult;}List result =module.stream().filter(x -> x.contains("ROLE_display")).collect(Collectors.toList());int phone = 0,IDCode = 0;// 判定电话if (result.contains("ROLE_display.IDCode")){if (result.contains("ROLE_displayHalf.IDCode")){phone = 2;}phone = 1;}if (result.contains("ROLE_displayHalf.IDCode")){phone = 2;}// 判定身份证if (result.contains("ROLE_display.Phone")){if (result.contains("ROLE_displayHalf.Phone")){IDCode = 2;}IDCode = 1;}if (result.contains("ROLE_displayHalf.Phone")){IDCode = 2;}//获取执行方法的参数Field[] fields = new Field[0];try {fields = methodResult.getClass().getDeclaredFields();} catch (Exception e) {//            e.printStackTrace();}for (Field field :fields){field.setAccessible(true);//方法参数中属性的指定注解ReadableSensitiveVerify annotation = field.getAnnotation(ReadableSensitiveVerify.class);try {if (Objects.nonNull(annotation)) {if (Objects.nonNull(field)){ReadableSensitiveTypeEnum typeEnum = annotation.value();String valueStr = field.getName();String property = BeanUtils.getProperty(methodResult, valueStr);switch (typeEnum) {case PHONE:if (phone == 2){property = DesensitizationUtils.desensitizedPhoneNumber(property);}if (phone == 0){property = null;}break;case ID_CARD:if (IDCode == 2){property = DesensitizationUtils.desensitizedIdNumber(property);}if (IDCode == 0){property = null;}break;default:}BeanUtils.setProperty(methodResult,valueStr, property);}}}catch (Exception e){}}return methodResult;}}
  • 最后就是我们的脱敏工具类了
    在这之前我们先来了解一下正则是什么?

正则表达式(regular expression)描述了一种字符串匹配的模式(pattern),可以用来检查一个串是否含有某种子串、将匹配的子串替换或者从某个串中取出符合某个条件的子串等。

这里的脱敏就有用到了正则匹配,通过上面的标签来匹配相应字段进行相应的脱敏处理。

import com.google.common.base.Strings;
import org.apache.commons.lang3.StringUtils;import java.util.regex.Matcher;
import java.util.regex.Pattern;public class DesensitizationUtils {/*** @description: 名字脱敏* 脱敏规则: 隐藏中中间部分,比如:李某人 置换为 李*人 , 李某置换为 *某,司徒司翘置换为 司**翘* @return:* @author: lx* @time: 2022/6/22*/public static String desensitizedName(String fullName){if (!Strings.isNullOrEmpty(fullName)) {int length = fullName.length();if(length == 2){return "*".concat(fullName.substring(1));}else if(length == 3){return StringUtils.left(fullName,1).concat("*").concat(StringUtils.right(fullName,1));}else if(length > 3){return StringUtils.left(fullName,1).concat(generateAsterisk(fullName.substring(1,length-1).length())).concat(StringUtils.right(fullName,1));}else {return fullName;}}return fullName;}/*** @description: 手机号脱敏,脱敏规则: 保留前三后四, 比如15566026528置换为155****6528* @return:* @author: lx* @time: 2022/6/22*/public static String desensitizedPhoneNumber(String phoneNumber){if(StringUtils.isNotEmpty(phoneNumber)){int length = phoneNumber.length();if(length == 11){return phoneNumber.replaceAll("(\\w{3})\\w*(\\w{4})", "$1****$2");}else if(length > 2){return StringUtils.left(phoneNumber,1).concat(generateAsterisk(phoneNumber.substring(1,length-2).length())).concat(StringUtils.right(phoneNumber,1));}else {return phoneNumber;}}return phoneNumber;}/*** @description: 身份证脱敏* 脱敏规则: 保留前六后三, 适用于15位和18位身份证号:* 原身份证号(15位):210122198401187,脱敏后的身份证号:210122******187* 原身份证号(18位):210122198401187672,脱敏后的身份证号:210122*********672* @return:* @author: lx* @time: 2022/6/22*/public static String desensitizedIdNumber(String idNumber){if (!Strings.isNullOrEmpty(idNumber)) {int length = idNumber.length();if (length == 15){return idNumber.replaceAll("(\\w{6})\\w*(\\w{3})", "$1******$2");}else if (length == 18){return idNumber.replaceAll("(\\w{6})\\w*(\\w{3})", "$1*********$2");}else if(length > 9){return StringUtils.left(idNumber,6).concat(generateAsterisk(idNumber.substring(6,length-3).length())).concat(StringUtils.right(idNumber,3));}}return idNumber;}/*** @description: 电子邮箱脱敏,脱敏规则:电子邮箱隐藏@前面的3个字符* @return:* @author: lx* @time: 2022/6/22*/public static String desensitizationEmail(String email) {if (StringUtils.isEmpty(email)) {return email;}String encrypt = email.replaceAll("(\\w+)\\w{3}@(\\w+)", "$1***@$2");if (email.equalsIgnoreCase(encrypt)) {encrypt = email.replaceAll("(\\w*)\\w{1}@(\\w+)", "$1*@$2");}return encrypt;}/*** @description: 地址脱敏,脱敏规则:从第4位开始隐藏,隐藏8位* @return:* @author: lx* @time: 2022/6/22*/public static String desensitizedAddress(String address){if (!Strings.isNullOrEmpty(address)) {int length = address.length();if(length > 4 && length <= 12){return StringUtils.left(address, 3).concat(generateAsterisk(address.substring(3).length()));}else if(length > 12){return StringUtils.left(address,3).concat("********").concat(address.substring(11));}else {return address;}}return address;}/*** @description: 银行账号脱敏, 脱敏规则:银行账号保留前六后四* @return:* @author: lx* @time: 2022/6/23*/public static String desensitizedAddressBankCardNum(String acctNo) {if (StringUtils.isNotEmpty(acctNo)) {String regex = "(\\w{6})(.*)(\\w{4})";Matcher m = Pattern.compile(regex).matcher(acctNo);if (m.find()) {String rep = m.group(2);StringBuilder sb = new StringBuilder();for (int i = 0; i < rep.length(); i++) {sb.append("*");}acctNo = acctNo.replaceAll(rep, sb.toString());}}return acctNo;}/*** @description: 返回指定长度*字符串* @return:* @author: lx* @time: 2022/6/22*/private static String generateAsterisk(int length){String result = "";for (int i = 0; i < length; i++) {result += "*";}return result;}}

这是最终的效果图

虽然有些美中不足,但足以适合我当前的项目环境,以后我还会进行修改的,谢谢大家的阅读。

Spring数据脱敏相关推荐

  1. Spring Boot基于注解方式处理接口数据脱敏

    1.定义注解 创建Spring Boot项目添加以下依赖 <dependencies><dependency><groupId>org.springframewor ...

  2. java 数据库数据脱敏_Sharding-JDBC-数据脱敏

    数据脱敏 该章节主要介绍如何使用数据脱敏功能,如何进行相关配置.数据脱敏功能即可与数据分片功能共同使用,又可作为单独功能组件,独立使用. 与数据分片功能共同使用时,会创建ShardingDataSou ...

  3. Springboot 配置文件、隐私数据脱敏的最佳实践(原理+源码)

    这几天公司在排查内部数据账号泄漏,原因是发现某些实习生小可爱居然连带着账号.密码将源码私传到GitHub上,导致核心数据外漏,孩子还是没挨过社会毒打,这种事的后果可大可小. 说起这个我是比较有感触的, ...

  4. Springboot 日志、配置文件、接口数据脱敏

    核心隐私数据无论对于企业还是用户来说尤其重要,因此要想办法杜绝各种隐私数据的泄漏.下面陈某带大家从以下三个方面讲解一下隐私数据如何脱敏,也是日常开发中需要注意的: 配置文件数据脱敏 接口返回数据脱敏 ...

  5. SpringBoot数据脱敏

    苏三说技术 2023-03-05 10:41 发表于四川 以下文章来源于程序员小富 ,作者程序员内点事 程序员小富. 大厂技术民工一枚,学技术.搞钱不扯没用的~ java突击队 技术经验分享 公众号 ...

  6. 3种常见的数据脱敏方案

    往期热门文章:1.BigDecimal使用不当,造成P0事故! 2.改造BeanUtils,优雅实现List数据拷贝 3.SpringBoot 启动时自动执行代码的几种方式,还有谁不会?? 4.让人上 ...

  7. 【SpringBoot高级篇】SpringBoot集成jasypt 配置脱敏和数据脱敏

    [SpringBoot高级篇]SpringBoot集成jasypt数据脱敏 配置脱敏 使用场景 配置脱敏实践 数据脱敏 pom yml EncryptMethod EncryptField Encry ...

  8. 安全-认证授权、数据脱敏

    一.认证授权 JWT :JWT(JSON Web Token)是一种身份认证的方式,JWT 本质上就一段签名的 JSON 格式的数据.由于它是带有签名的,因此接收者便可以验证它的真实性. SSO(单点 ...

  9. 改造了以前写的数据脱敏插件,更好用了

    以前用Mybatis插件的形式写了一个数据脱敏工具,但是发现有一定的局限性.很多时候我们从ORM查询到的数据有其它逻辑要处理,比如根据电话号查询用户信息,你脱敏了就没有办法来处理该逻辑了.所以脱敏这个 ...

最新文章

  1. 为什么要研究游戏 AI 呢?
  2. 未能找出类型或命名空间名称“T” 问题的解决方案
  3. No.3 clojure 调用 Java
  4. Android的IPC机制(一)——AIDL的使用
  5. oracle数据定义语句,oracle(sql)基础篇系列(3)——数据维护语句、数据定义语句、伪列...
  6. 灯光工厂滤镜插件knoll light factory
  7. 微软模拟飞行2020服务器多少内存,《微软模拟飞行2020》配置公开,想玩爽还需玩家加大投入...
  8. LiveVideoStackCon 2018公布优秀出品人与讲师
  9. JS调用后台方法大全
  10. n元n次方程求解c 语言,解n元一次方程
  11. 温度 数值模拟 matlab,西安交通大学——温度场数值模拟(matlab)
  12. Gallery:收集一些用于展示一组图片的javascript控件
  13. 继向日本捐赠100万只口罩后,马云又向这个国家捐了100万只!
  14. (一)NIST CSF-框架介绍
  15. SEO优化:站群的操作方法有哪些,网站集群系统是什么?
  16. C语言移动营业厅程序设计,c语言实现移动电话系统
  17. 计算机教学模式有待创新,计算机软件教学中教学评价模式的创新与探索
  18. office word 使用快捷键ctrl c ctrl v提示文件未找到:MathPage.WLL
  19. Python判断字符串中连续最长的递增英文字母串
  20. C++实现模重复平方计算法

热门文章

  1. Python代码登录新浪微博并自动发微博
  2. Hmac概念与实现(js和go实现)
  3. 提取路由器固件中的squashfs
  4. 独立游戏开发者FAQ
  5. COMSOL案例学习(4):水杯中的自然对流
  6. python String longer than 32767 characters
  7. 316C Tidying Up 费用流的完美匹配
  8. 前端机器学习--识别人脸在脸颊上画草莓
  9. 2021年统考计算机应用基础,2021年电大网考计算机应用基础统考答案-20210511065851.doc-原创力文档...
  10. 北航研究生计算机网络,实验4_北航研究生计算机网络实验