SpringBoot项目集成数据脱敏(密码加密)功能

本文主要是介绍SpringBoot项目集成数据脱敏(密码加密)功能,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

代码连接【https://gitee.com/pengmqqq/sensitive-data-encryption】

介绍

后端敏感数据加密的一些解决方案,包括:

  1. 配置文件敏感数据加解密
  2. 前端传输敏感数据加解密
  3. 数据库获取的敏感数据加解密
软件架构

配置文件数据脱敏: Jasypt + AES

前后端传输以及数据库存储数据脱敏:AOP + AES

使用说明
  1. 配置文件数据脱敏

    将需要脱敏的数据进行加密之后再放入配置文件(注意要使用解密算法配套的加密算法)例如:

    test:password: Enc(TRBkk56w91m0bFOWzidFDQ==)
    
  2. 前后端传输以及数据库存储数据脱敏:

    在需要加密/解密的属性/参数上增加注解 @EncryptField

    @Data
    @Accessors(chain = true)
    public class User {private Integer id;private String name;@EncryptFieldprivate String phone;@EncryptFieldprivate String email;private Integer age;
    }
    

    在需要对参数加密的方法上增加注解 @NeedEncrypt

    @NeedEncrypt
    public void addAll(List<User> user) {users.addAll(user);System.out.println("");
    }
    

    在需要对返回值解密的方法上增加注解 @NeedDecrypt

    例如某些需要访问第三方平台的操作,从数据库取到的是加密的数据,代码中需要进行解密再发送给第三方平台进行认证

    @NeedDecrypt
    public List<User> findAll() {ArrayList<User> list = new ArrayList<>(users);return list;
    }
    
实现方案
配置文件数据脱敏:
  • pom文件引入依赖:

    <!-- 依赖aop -->
    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    <dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional>
    </dependency>
    <!--配置密码加密-->
    <dependency><groupId>com.github.ulisesbocchio</groupId><artifactId>jasypt-spring-boot-starter</artifactId><version>3.0.4</version>
    </dependency>
    
  • 配置文件 application.yml 新增jasypt相关配置:

    jasypt:encryptor:property:# 算法识别的前后缀,默认ENC(),包含在前后缀的加密信息,会使用指定算法解密prefix: Enc(suffix: )bean: desencrypt  # 指定自定义加密算法的beantest:password: Enc(TRBkk56w91m0bFOWzidFDQ==)  # 加密数据
    
  • 新增自定义算法类:

    @Component("desencrypt")
    public class JasyptAlgorithmConfig implements StringEncryptor {@Overridepublic String encrypt(String message) {return AESUtils.encrypt(message,AESUtils.getKey());}@Overridepublic String decrypt(String encryptedMessage) {return AESUtils.decrypt(encryptedMessage,AESUtils.getKey());}
    
  • 新增加密工具类:

    public class AESUtils {private static final String USER_PWD_KEY = "g32DHJC4x8MAOdER";private static final Charset CHARSET = Charset.forName("UTF-8");// 加密算法名称private static final String AES = "AES";// 偏移量-AES 128位数据块对应偏移量为16位字符串private static final String IV = "70f5zbDJK9xXAJ5C";// AES-加密方式, CBC-工作模式,PKCS5Padding-填充模式private static final String AES_CBC_PKCS5PADDING = "AES/CBC/PKCS5Padding";/*** AES 加密操作** @param content 待加密内容* @param key     加密密钥* @return 返回Base64转码后的加密数据*/public static String encrypt(String content, String key) {if (StringUtils.isEmpty(content)) {return content;}try {/** 新建一个密码编译器的实例,由三部分构成,用"/"分隔,分别代表如下* 1. 加密的类型(如AES,DES,RC2等)* 2. 模式(AES中包含ECB,CBC,CFB,CTR,CTS等)* 3. 补码方式(包含nopadding/PKCS5Padding等等)* 依据这三个参数可以创建很多种加密方式*/Cipher cipher = Cipher.getInstance(AES_CBC_PKCS5PADDING);//偏移量IvParameterSpec zeroIv = new IvParameterSpec(IV.getBytes(CHARSET));byte[] byteContent = content.getBytes(CHARSET);//使用加密秘钥SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes(CHARSET), AES);//SecretKeySpec skeySpec = getSecretKey(key);cipher.init(Cipher.ENCRYPT_MODE, skeySpec, zeroIv); // 初始化为加密模式的密码器byte[] result = cipher.doFinal(byteContent); // 加密return Base64.encodeBase64String(result); //通过Base64转码返回} catch (Exception ex) {throw new RuntimeException(ex);}}/*** AES 解密操作** @param content* @param key* @return*/public static String decrypt(String content, String key) {if (StringUtils.isEmpty(content)) {return content;}try {Cipher cipher = Cipher.getInstance(AES_CBC_PKCS5PADDING);IvParameterSpec zeroIv = new IvParameterSpec(IV.getBytes(CHARSET));SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes(CHARSET), AES);//SecretKeySpec skeySpec = getSecretKey(key);cipher.init(Cipher.DECRYPT_MODE, skeySpec, zeroIv);byte[] result = cipher.doFinal(Base64.decodeBase64(content));return new String(result, CHARSET);} catch (Exception ex) {throw new RuntimeException(ex);}}public static String getKey(){return USER_PWD_KEY;}
    }
    
前后但接口以及数据库存储数据加密:
  • 新增三个注解

    • @EncryptField

      /***  安全字段注解* 加在需要加密/解密的属性/参数上* 实现自动加密解密*/
      @Target({ElementType.FIELD,ElementType.PARAMETER})
      @Retention(RetentionPolicy.RUNTIME)
      public @interface EncryptField {String[] value() default "";
      }
      
    • @NeedDecrypt

      /***  安全字段注解* 加在需要解密的方法参数上* 实现自动解密*/
      @Target({ElementType.METHOD})
      @Retention(RetentionPolicy.RUNTIME)
      public @interface NeedDecrypt {
      }
      
    • @NeedEncrypt

      /***  安全字段注解* 加在需要加密的方法参数上* 实现自动加密*/
      @Target({ElementType.METHOD})
      @Retention(RetentionPolicy.RUNTIME)
      public @interface NeedEncrypt {
      }
      
  • 新增两个切面类

    • EncryptAspect

      /*** @author 17540* 对加了@NeedEncrypt注释的方法的参数进行扫描,参数中存在@EncryptFild修饰的加密字段,则进行加密* 当前只适配非嵌套对象参数,List参数,普通String参数*/
      @Slf4j
      @Aspect
      @Component
      public class EncryptAspect {@Pointcut("@annotation(com.example.encryption.common.anno.NeedEncrypt)")public void pointCut() {}@Around("pointCut()")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {//加密return joinPoint.proceed(encrypt(joinPoint));}public Object[] 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) {String value = encryptValue(objects[i]);objects[i] = value;} else {encryptData(objects[i]);}}}} catch (Exception e) {e.printStackTrace();}return objects;}private void encryptData(Object obj) throws IllegalAccessException {if (Objects.isNull(obj)) {return;}if (obj instanceof ArrayList) {encryptList(obj);} else {encryptObj(obj);}}/*** 加密对象* @param obj* @throws IllegalAccessException*/private void encryptObj(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);if (field.get(obj) == null) {continue;}String value = AESUtils.encrypt(String.valueOf(field.get(obj)), AESUtils.getKey());field.set(obj, value);}}}/*** 针对list<实体来> 进行反射、解密* @param obj* @throws IllegalAccessException*/private void encryptList(Object obj) throws IllegalAccessException {List<Object> result = new ArrayList<>();if (obj instanceof ArrayList) {result.addAll((List<?>) obj);}for (Object object : result) {encryptObj(object);}obj = result;}/*** 加密单个值* @param realValue* @return*/public String encryptValue(Object realValue) {if (Objects.isNull(realValue)) {return null;}try {realValue = AESUtils.encrypt(String.valueOf(realValue), AESUtils.getKey());} catch (Exception e) {log.info("加密异常={}",e.getMessage());}return String.valueOf(realValue);}
      }
      
    • DecryptAspect

      /*** @author 17540* 对加了@NeedEncrypt注释的方法的参数进行扫描,参数中存在@EncryptFild修饰的加密字段,则进行加密* 当前只适配非嵌套对象参数,List参数,普通String参数*/
      @Slf4j
      @Aspect
      @Component
      public class DecryptAspect {@Pointcut("@annotation(com.example.encryption.common.anno.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) {result = 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);if (Objects.isNull(realValue)) {continue;}try{String value = AESUtils.decrypt(realValue, AESUtils.getKey());field.set(obj, value);log.info("解密后={}", value);}catch (Exception e){log.error("解密{}异常=,{}",realValue, e.getMessage());}}}}/*** 针对list<实体来> 进行反射、解密* @param obj* @throws IllegalAccessException*/private void decryptList(Object obj) throws IllegalAccessException {List<Object> result = new ArrayList<>();if (obj instanceof ArrayList) {result.addAll((List<?>) obj);}for (Object object : result) {decryptObj(object);}obj = result;}public String decryptValue(Object realValue) {try {realValue = AESUtils.decrypt(String.valueOf(realValue), AESUtils.getKey());} catch (Exception e) {log.info("解密{}异常={}",realValue, e.getMessage());}return String.valueOf(realValue);}
      }
      

这篇关于SpringBoot项目集成数据脱敏(密码加密)功能的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/1111781

相关文章

Java通过驱动包(jar包)连接MySQL数据库的步骤总结及验证方式

《Java通过驱动包(jar包)连接MySQL数据库的步骤总结及验证方式》本文详细介绍如何使用Java通过JDBC连接MySQL数据库,包括下载驱动、配置Eclipse环境、检测数据库连接等关键步骤,... 目录一、下载驱动包二、放jar包三、检测数据库连接JavaJava 如何使用 JDBC 连接 mys

SpringBoot线程池配置使用示例详解

《SpringBoot线程池配置使用示例详解》SpringBoot集成@Async注解,支持线程池参数配置(核心数、队列容量、拒绝策略等)及生命周期管理,结合监控与任务装饰器,提升异步处理效率与系统... 目录一、核心特性二、添加依赖三、参数详解四、配置线程池五、应用实践代码说明拒绝策略(Rejected

SQL中如何添加数据(常见方法及示例)

《SQL中如何添加数据(常见方法及示例)》SQL全称为StructuredQueryLanguage,是一种用于管理关系数据库的标准编程语言,下面给大家介绍SQL中如何添加数据,感兴趣的朋友一起看看吧... 目录在mysql中,有多种方法可以添加数据。以下是一些常见的方法及其示例。1. 使用INSERT I

Qt使用QSqlDatabase连接MySQL实现增删改查功能

《Qt使用QSqlDatabase连接MySQL实现增删改查功能》这篇文章主要为大家详细介绍了Qt如何使用QSqlDatabase连接MySQL实现增删改查功能,文中的示例代码讲解详细,感兴趣的小伙伴... 目录一、创建数据表二、连接mysql数据库三、封装成一个完整的轻量级 ORM 风格类3.1 表结构

一文详解SpringBoot中控制器的动态注册与卸载

《一文详解SpringBoot中控制器的动态注册与卸载》在项目开发中,通过动态注册和卸载控制器功能,可以根据业务场景和项目需要实现功能的动态增加、删除,提高系统的灵活性和可扩展性,下面我们就来看看Sp... 目录项目结构1. 创建 Spring Boot 启动类2. 创建一个测试控制器3. 创建动态控制器注

Python使用vllm处理多模态数据的预处理技巧

《Python使用vllm处理多模态数据的预处理技巧》本文深入探讨了在Python环境下使用vLLM处理多模态数据的预处理技巧,我们将从基础概念出发,详细讲解文本、图像、音频等多模态数据的预处理方法,... 目录1. 背景介绍1.1 目的和范围1.2 预期读者1.3 文档结构概述1.4 术语表1.4.1 核

Java操作Word文档的全面指南

《Java操作Word文档的全面指南》在Java开发中,操作Word文档是常见的业务需求,广泛应用于合同生成、报表输出、通知发布、法律文书生成、病历模板填写等场景,本文将全面介绍Java操作Word文... 目录简介段落页头与页脚页码表格图片批注文本框目录图表简介Word编程最重要的类是org.apach

Spring Boot中WebSocket常用使用方法详解

《SpringBoot中WebSocket常用使用方法详解》本文从WebSocket的基础概念出发,详细介绍了SpringBoot集成WebSocket的步骤,并重点讲解了常用的使用方法,包括简单消... 目录一、WebSocket基础概念1.1 什么是WebSocket1.2 WebSocket与HTTP

SpringBoot+Docker+Graylog 如何让错误自动报警

《SpringBoot+Docker+Graylog如何让错误自动报警》SpringBoot默认使用SLF4J与Logback,支持多日志级别和配置方式,可输出到控制台、文件及远程服务器,集成ELK... 目录01 Spring Boot 默认日志框架解析02 Spring Boot 日志级别详解03 Sp

java中反射Reflection的4个作用详解

《java中反射Reflection的4个作用详解》反射Reflection是Java等编程语言中的一个重要特性,它允许程序在运行时进行自我检查和对内部成员(如字段、方法、类等)的操作,本文将详细介绍... 目录作用1、在运行时判断任意一个对象所属的类作用2、在运行时构造任意一个类的对象作用3、在运行时判断