当前位置: 首页 > news >正文

Springboot AOP实现指定敏感字段数据加密 (数据加密篇 二)

前言

最近项目组开始关注一些敏感数据的明文相关的事宜 , 其实这些东西也是都有非常成熟的解决方案。 既然最近着手去解决这些事情,那么也顺便给还未了解的大伙普及一下。

这个系列就暂短的分成三篇 :

 第一篇    yml配置文件里敏感数据的加密
Springboot yml配置参数数据加密 (数据加密篇 一)_默默不代表沉默-CSDN博客

 第二篇    传入数据敏感数据的加密存储

第三篇     使用mysql加解密函数轻松实现  

Springboot 使用mysql加密解密函数 (数据加密篇 三)_默默不代表沉默-CSDN博客

本篇是第二篇 ,基于第一篇已经整合了jasypt 的基础上 。

正文

内容:

1. 插入数据 自定义注解方式  对 指定接口方法 的 参数的指定字段进行 加密存储;

2.对数据内的加密数据,进行解密返回

先看看效果 : 

数据存入数据库表内, 手机号phone和邮箱email 属于敏感数据,我们需要密文存储 : 

查询解密返回:

1.  自定义注解 加密标识注解  NeedEncrypt.java :

import java.lang.annotation.*;

/**
 * @Author JCccc
 * @Description 需加密
 * @Date 2021/7/23 11:55
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface NeedEncrypt {


}

2.自定义注解 需加密字段标识注解 EncryptField.java :

/**
 * @Author JCccc
 * @Description
 * @Date 2021/7/23 11:55
 */
@Target({ElementType.FIELD,ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptField {

    String[] value() default "";
}

3.加密逻辑的aop处理器  EncryptAspect.java :

import com.elegant.dotest.aop.annotation.EncryptField;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.jasypt.encryption.StringEncryptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.util.Objects;

/**
 * @Author JCccc
 * @Description
 * @Date 2021/9/14 8:55
 */
@Slf4j
@Aspect
@Component
public class EncryptAspect {

    @Autowired
    private StringEncryptor stringEncryptor;

    @Pointcut("@annotation(com.elegant.dotest.aop.annotation.NeedEncrypt)")
    public void pointCut() {
    }

    @Around("pointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        //加密
        encrypt(joinPoint);

        return joinPoint.proceed();
    }

    public void encrypt(ProceedingJoinPoint joinPoint)  {
        Object[] objects=null;
        try {
             objects = joinPoint.getArgs();
            if (objects.length != 0) {
                for (int i = 0; i < objects.length; i++) {
                    //抛砖引玉 ,可自行扩展其他类型字段的判断
                    if (objects[i] instanceof String) {
                        objects[i] = encryptValue(objects[i]);
                    } else {
                        encryptObject(objects[i]);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 加密对象
     * @param obj
     * @throws IllegalAccessException
     */
    private void encryptObject(Object obj) throws IllegalAccessException {

        if (Objects.isNull(obj)) {
            log.info("当前需要加密的object为null");
            return;
        }
        Field[] fields = obj.getClass().getDeclaredFields();
        for (Field field : fields) {
            boolean containEncryptField = field.isAnnotationPresent(EncryptField.class);
            if (containEncryptField) {
                //获取访问权
                field.setAccessible(true);
                String value = stringEncryptor.encrypt(String.valueOf(field.get(obj)));
                field.set(obj, value);
            }
        }
    }

    /**
     * 加密单个值
     * @param realValue
     * @return
     */
    public String encryptValue(Object realValue) {
        try {
            realValue = stringEncryptor.encrypt(String.valueOf(realValue));
        } catch (Exception e) {
            log.info("加密异常={}",e.getMessage());
        }
        return String.valueOf(realValue);
    }


}

4. 插入user表 使用的 User.java :
 

import com.elegant.dotest.aop.annotation.EncryptField;
import lombok.Data;
import lombok.experimental.Accessors;

/**
 * @Author JCccc
 * @Description
 * @Date 2021/9/14 8:55
 */
@Data
@Accessors(chain = true)
public class User {

    private Integer id;
    private String name;
    @EncryptField
    private String phone;
    @EncryptField
    private String email;
    private Integer age;

}

可以看到,手机号phone 和 邮箱 email 两个字段,我们做了注解 @EncryptField 标识:

ok,我们写个测试接口,使用 @NeedEncrypt 注解标识这个接口需要进行加密拦截 :

使用postman调用一下测试接口:

 可以看下数据库,数据已经加密存储成功:

接下来是查询解密环节:

解密这里其实有些小讲究。 因为查询出来的数据有可能是单个实体,也可能是List (其实甚至是Map或者Set,又或者是 分页数据类)

所以本文将会以 最常用的 单个实体 、 List<实体> 为例子,去做解密。

1.解密自定义注解 NeedDecrypt.java :
 

/**
 * @Author JCccc
 * @Description 需解密
 * @Date 2021/7/23 11:55
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface NeedDecrypt {

}

2. 解密逻辑的aop处理器  DecryptAspect.java :
 

import com.elegant.dotest.aop.annotation.EncryptField;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.jasypt.encryption.StringEncryptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

/**
 * @Author JCccc
 * @Description
 * @Date 2021/9/14 8:55
 */
@Slf4j
@Aspect
@Component
public class DecryptAspect {

    @Autowired
    private StringEncryptor stringEncryptor;

    @Pointcut("@annotation(com.elegant.dotest.aop.annotation.NeedDecrypt)")
    public void pointCut() {
    }

    @Around("pointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        //解密
        Object result = decrypt(joinPoint);
        return result;
    }

    public Object decrypt(ProceedingJoinPoint joinPoint) {
        Object result = null;
        try {
            Object obj = joinPoint.proceed();
            if (obj != null) {
                //抛砖引玉 ,可自行扩展其他类型字段的判断
                if (obj instanceof String) {
                    decryptValue(obj);
                } else {
                    result = decryptData(obj);
                }
            }
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return result;
    }

    private Object decryptData(Object obj) throws IllegalAccessException {

        if (Objects.isNull(obj)) {
            return null;
        }
        if (obj instanceof ArrayList) {
            decryptList(obj);
        } else {
            decryptObj(obj);
        }


        return obj;
    }

    /**
     * 针对单个实体类进行 解密
     * @param obj
     * @throws IllegalAccessException
     */
    private void decryptObj(Object obj) throws IllegalAccessException {
        Field[] fields = obj.getClass().getDeclaredFields();
        for (Field field : fields) {
            boolean hasSecureField = field.isAnnotationPresent(EncryptField.class);
            if (hasSecureField) {
                field.setAccessible(true);
                String realValue = (String) field.get(obj);
                String value = stringEncryptor.decrypt(realValue);
                field.set(obj, value);
            }
        }
    }

    /**
     * 针对list<实体来> 进行反射、解密
     * @param obj
     * @throws IllegalAccessException
     */
    private void decryptList(Object obj) throws IllegalAccessException {
        List<Object> result = new ArrayList<>();
        if (obj instanceof ArrayList) {
            for (Object o : (List<?>) obj) {
                result.add(o);
            }
        }
        for (Object object : result) {
            decryptObj(object);
        }
    }


    public String decryptValue(Object realValue) {
        try {
            realValue = stringEncryptor.encrypt(String.valueOf(realValue));
        } catch (Exception e) {
            log.info("解密异常={}", e.getMessage());
        }
        return String.valueOf(realValue);
    }


}

然后我们对一个查询方法进行测试 :

 我们先试一下查询单条数据的:

使用@NeedDecrypt注解标记这个接口需要数据解密:

调用接口看看结果:

 然后是多条数据List<User> 返回的接口:


 调用接口测试看看结果:


相关文章:

  • Springboot 使用mysql加密解密函数 (数据加密篇 三)
  • Java List数据量大, 需要分片批次操作
  • Springboot yml配置参数加密 ,jasypt自定义解密器(拓展篇)
  • Springboot 自定义mybatis 拦截器,实现我们要的扩展
  • Eureka 一直刷 Running the evict task with compensationTime 0ms
  • Eureka 注册、下线、续约事件的监听使用
  • Java Thread.sleep(),结合例子只学一次
  • Java ArrayList new出来,默认的容量到底是0还是10 ?
  • Mysql 关于 int(1) 和 int(11) , 我必须要说一下了。
  • SpringCloud 整合注册中心,配置中心 Nacos (九)
  • Springboot 自定义注解AOP实现时间参数格式转换
  • 看什么看啊,你不会还不会抓HTTPS请求报文吧?
  • 做一个合格的开发,从玩转Apipost开始
  • Springboot 整合 企业微信机器人助手推送消息
  • Springboot 同一次调用日志怎么用ID串起来,方便最终查找
  • 3.7、@ResponseBody 和 @RestController
  • CentOS7简单部署NFS
  • Linux CTF 逆向入门
  • Material Design
  • Mysql5.6主从复制
  • Perseus-BERT——业内性能极致优化的BERT训练方案
  • Python_网络编程
  • Python进阶细节
  • spring cloud gateway 源码解析(4)跨域问题处理
  • Tornado学习笔记(1)
  • 技术胖1-4季视频复习— (看视频笔记)
  • 力扣(LeetCode)21
  • 那些被忽略的 JavaScript 数组方法细节
  • 用简单代码看卷积组块发展
  • 《码出高效》学习笔记与书中错误记录
  • #大学#套接字
  • (13)Hive调优——动态分区导致的小文件问题
  • (cljs/run-at (JSVM. :browser) 搭建刚好可用的开发环境!)
  • (Pytorch框架)神经网络输出维度调试,做出我们自己的网络来!!(详细教程~)
  • (windows2012共享文件夹和防火墙设置
  • (超详细)2-YOLOV5改进-添加SimAM注意力机制
  • (附源码)node.js知识分享网站 毕业设计 202038
  • (附源码)springboot电竞专题网站 毕业设计 641314
  • (附源码)springboot助农电商系统 毕业设计 081919
  • (附源码)计算机毕业设计SSM教师教学质量评价系统
  • (十三)Java springcloud B2B2C o2o多用户商城 springcloud架构 - SSO单点登录之OAuth2.0 根据token获取用户信息(4)...
  • .net framework 4.0中如何 输出 form 的name属性。
  • .NET 设计一套高性能的弱事件机制
  • .NET单元测试
  • .NET轻量级ORM组件Dapper葵花宝典
  • @Transaction注解失效的几种场景(附有示例代码)
  • [ vulhub漏洞复现篇 ] ECShop 2.x / 3.x SQL注入/远程执行代码漏洞 xianzhi-2017-02-82239600
  • [CareerCup] 17.8 Contiguous Sequence with Largest Sum 连续子序列之和最大
  • [CDOJ 838]母仪天下 【线段树手速练习 15分钟内敲完算合格】
  • [CSS]浮动
  • [Git 1]基本操作与协同开发
  • [HOW TO]怎么在iPhone程序中实现可多选可搜索按字母排序的联系人选择器
  • [java] 23种设计模式之责任链模式
  • [javaSE] GUI(Action事件)
  • [LeetCode]-使用特殊算法的题目-2