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

安全实现SpringBoot配置文件自动加解密

需求背景

应用程序开发的时候,往往会存在一些敏感的配置属性

  • 数据库账号、密码
  • 第三方服务账号密码
  • 内置加密密码
  • 其他的敏感配置

对于安全性要求比较高的公司,往往不允许敏感配置以明文的方式出现。
通常做法是对这些敏感配置进行加密,然后在使用的地方进行解密。但是有一些第三方的配置可能未提供解密注入点如数据库密码,这时要实现起来就比较麻烦。有没有比较方便的方法可以自动识别并解密。
本次主要针对这个问题,解决敏感配置的加密问题

实现思路

  • 使用已有的第三方包:如jasypt-spring-boot
    • 这是一个针对SpringBoot项目配置进行加解密的包,可以在项目里通过引入依赖来实现。具体使用方式自行搜索
  • 参考官方文档利用官方提供的扩展点自己实现
    • 实现EnvironmentPostProcessor
      • EnvironmentPostProcessor在配置文件解析后,bean创建前调用
    • 实现BeanFactoryPostProcessor(优先考虑)
      • BeanFactoryPostProcessor在配置文件解析后,bean创建前调用
      • 实现方式同EnvironmentPostProcessor基本一致,注入时机更靠后。

通过实现EnvironmentPostProcessor方式解决

实现EnvironmentPostProcessor可自定义环境配置处理逻辑。实现示例如下

    @Overridepublic void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {MutablePropertySources mutablePropertySources = environment.getPropertySources();for (PropertySource<?> propertySource : mutablePropertySources) {if (propertySource instanceof OriginTrackedMapPropertySource) {mutablePropertySources.replace(propertySource.getName(),// 实现一个包装类,动态判断new PropertySourceWrapper(propertySource, initSimpleEncryptor("reduck-project"), new EncryptionWrapperDetector("$ENC{", "}")));}}}

EnvironmentPostProcessor 也可以自动扩展配置文件,如果有些项目自己在这个扩展点实现了自己的配置加载逻辑,可能就需要考虑顺序问题。这里比较推荐实现BeanFactoryPostProcessor,他在EnvironmentPostProcessor相关实例处理后调用,且在Bean创建前。可以更好满足需求。

通过实现BeanFactoryPostProcessor解决

  • 实现EncryptionBeanPostProcessor
    • 一般OriginTrackedMapPropertySource是我们自定义的配置加载实例,通过一个包装类替换原先的实例
@RequiredArgsConstructor
public class EncryptionBeanPostProcessor implements BeanFactoryPostProcessor, Ordered {private final ConfigurableEnvironment environment;@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {MutablePropertySources mutablePropertySources = environment.getPropertySources();String secretKey = environment.getProperty("configuration.crypto.secret-key");if(secretKey ==  null) {return;}for (PropertySource<?> propertySource : mutablePropertySources) {if (propertySource instanceof OriginTrackedMapPropertySource) {mutablePropertySources.replace(propertySource.getName(),new PropertySourceWrapper(propertySource, new AesEncryptor(PrivateKeyFinder.getSecretKey(secretKey)), new EncryptionWrapperDetector("$ENC{", "}")));}}}@Overridepublic int getOrder() {return Ordered.LOWEST_PRECEDENCE - 100;}
}
  • 定义一个 PropertySource包装类
    • PropertySource只有一个方法 public Object getProperty(String name),只需要实现这个方法,如果是加密配置就解密
public class PropertySourceWrapper<T> extends PropertySource<T> {private final String prefix = "$ENC{";private final String suffix = "}";private final Encryptor encryptor;private final PropertySource<T> originalPropertySource;private final EncryptionWrapperDetector detector;public PropertySourceWrapper(PropertySource<T> originalPropertySource, Encryptor encryptor, EncryptionWrapperDetector detector) {super(originalPropertySource.getName(), originalPropertySource.getSource());this.originalPropertySource = originalPropertySource;this.encryptor = encryptor;this.detector = detector;}@Overridepublic Object getProperty(String name) {if (originalPropertySource.containsProperty(name)) {Object value = originalPropertySource.getProperty(name);if (value != null) {String property = value.toString();if (detector.detected(property)) {return encryptor.decrypt(detector.unWrapper(property));}}}return originalPropertySource.getProperty(name);}
}
  • 定义一个加解密帮助类EncryptionWrapperDetector
    • 根据前后缀判断是否是加密属性
    • 对加密属性进行包装
    • 对加密属性去除包装
public class EncryptionWrapperDetector {private final String prefix;private final String suffix;public EncryptionWrapperDetector(String prefix, String suffix) {this.prefix = prefix;this.suffix = suffix;}public boolean detected(String property) {return property != null && property.startsWith(prefix) && property.endsWith(suffix);}public String wrapper(String property) {return prefix + property + suffix;}public String unWrapper(String property) {return property.substring(prefix.length(), property.length() - suffix.length());}
}
  • 定义一个加解密类
    • 对配置文件进行加密
    • 对配置问价进行解密
public class AesEncryptor implements Encryptor {private final byte[] secretKey;private final byte[] iv = new byte[16];public AesEncryptor(byte[] secretKey) {this.secretKey = secretKey;System.arraycopy(secretKey, 0, iv, 0, 16);}@Overridepublic String encrypt(String message) {return Base64.getEncoder().encodeToString(_AesUtils.encrypt(secretKey, iv, message.getBytes()));}@Overridepublic String decrypt(String message) {return new String(_AesUtils.decrypt(secretKey, iv, Base64.getDecoder().decode(message)));}
}
  • 密钥加密存储
    • 采用非对称加密方式对密钥进行加密,用公钥加密后的密钥可以直接写在配置文件中
    • 在进行解密的时候先通过内置的私钥解密获取原始加密密钥
    • 注意细节
      • 私钥存储的时候可以再进行一次加密
      • 私钥可放在META-INF路径下,通过Classloader获取
public class PrivateKeyFinder {private static final String PRIVATE_KEY_RESOURCE_LOCATION = "META-INF/configuration.crypto.private-key";private static final String PUBLIC_KEY_RESOURCE_LOCATION = "META-INF/configuration.crypto.public-key";private final byte[] keyInfo = new byte[]{(byte) 0xD0, (byte) 0x20, (byte) 0xDA, (byte) 0x92, (byte) 0xC8, (byte) 0x0B, (byte) 0x6D, (byte) 0x57,(byte) 0x48, (byte) 0x7B, (byte) 0x15, (byte) 0x3A, (byte) 0x44, (byte) 0xA0, (byte) 0x98, (byte) 0xC2,(byte) 0xF1, (byte) 0x6F, (byte) 0xB6, (byte) 0x09, (byte) 0x2F, (byte) 0x6D, (byte) 0x69, (byte) 0xFB,(byte) 0x2D, (byte) 0x02, (byte) 0x00, (byte) 0xCB, (byte) 0xBE, (byte) 0x48, (byte) 0xDD, (byte) 0xD5,(byte) 0x90, (byte) 0xC2, (byte) 0x95, (byte) 0x98, (byte) 0x60, (byte) 0x59, (byte) 0x24, (byte) 0xE2,(byte) 0xB7, (byte) 0x84, (byte) 0x12, (byte) 0x5D, (byte) 0xB9, (byte) 0xC1, (byte) 0x19, (byte) 0xFF,(byte) 0x4F, (byte) 0x01, (byte) 0xB9, (byte) 0xC5, (byte) 0xD8, (byte) 0xD2, (byte) 0x99, (byte) 0xEE,(byte) 0xAA, (byte) 0x0D, (byte) 0x59, (byte) 0xF8, (byte) 0x37, (byte) 0x49, (byte) 0x91, (byte) 0xAB};static byte[] getSecretKey(String encKey) {byte[] key = loadPrivateKey();return RsaUtils.decrypt(Base64.getDecoder().decode(encKey), new PrivateKeyFinder().decrypt(Base64.getDecoder().decode(key)));}static String generateSecretKey() {return Base64.getEncoder().encodeToString(RsaUtils.encrypt(new SecureRandom().generateSeed(16), Base64.getDecoder().decode(loadPublicKey())));}static String generateSecretKeyWith256() {return Base64.getEncoder().encodeToString(RsaUtils.encrypt(new SecureRandom().generateSeed(32), Base64.getDecoder().decode(loadPublicKey())));}@SneakyThrowsstatic byte[] loadPrivateKey() {return loadResource(PRIVATE_KEY_RESOURCE_LOCATION);}@SneakyThrowsstatic byte[] loadPublicKey() {return loadResource(PUBLIC_KEY_RESOURCE_LOCATION);}@SneakyThrowsprivate static byte[] loadResource(String location) {// just lookup from current jar  pathClassLoader classLoader = new URLClassLoader(new URL[]{PrivateKeyFinder.class.getProtectionDomain().getCodeSource().getLocation()}, null);
//        classLoader = PrivateKeyFinder.class.getClassLoader();Enumeration<URL> enumeration = classLoader.getResources(location);// should only find onewhile (enumeration.hasMoreElements()) {URL url = enumeration.nextElement();UrlResource resource = new UrlResource(url);return FileCopyUtils.copyToByteArray(resource.getInputStream());}return null;}private final String CIPHER_ALGORITHM = "AES/CBC/NoPadding";private final String KEY_TYPE = "AES";@SneakyThrowspublic byte[] encrypt(byte[] data) {byte[] key = new byte[32];byte[] iv = new byte[16];System.arraycopy(keyInfo, 0, key, 0, 32);System.arraycopy(keyInfo, 32, iv, 0, 16);Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, KEY_TYPE), new IvParameterSpec(iv));return cipher.doFinal(data);}@SneakyThrowspublic byte[] decrypt(byte[] data) {byte[] key = new byte[32];byte[] iv = new byte[16];System.arraycopy(keyInfo, 0, key, 0, 32);System.arraycopy(keyInfo, 32, iv, 0, 16);Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, KEY_TYPE), new IvParameterSpec(iv));return cipher.doFinal(data);}
}

综上既可以实现敏感配置文件的加解密,同时可以保障加密密钥的安全传入

相关文章:

安全实现SpringBoot配置文件自动加解密

需求背景 应用程序开发的时候&#xff0c;往往会存在一些敏感的配置属性 数据库账号、密码第三方服务账号密码内置加密密码其他的敏感配置 对于安全性要求比较高的公司&#xff0c;往往不允许敏感配置以明文的方式出现。 通常做法是对这些敏感配置进行加密&#xff0c;然后在…...

数据结构--队列2--双端队列--java双端队列

介绍 双端队列&#xff0c;和前面学的队列和栈的区别在于双端队列2端都可以进行增删&#xff0c;其他2个都是只能一端可以增/删。 实现 链表 因为2端都需要可以操作所以我们使用双向链表 我们也需要一共头节点 所以节点设置 static class Node<E>{E value;Node<E…...

网络安全:信息收集专总结【社会工程学】

前言 俗话说“渗透的本质也就是信息收集”&#xff0c;信息收集的深度&#xff0c;直接关系到渗透测试的成败&#xff0c;打好信息收集这一基础可以让测试者选择合适和准确的渗透测试攻击方式&#xff0c;缩短渗透测试的时间。 一、思维导图 二、GoogleHacking 1、介绍 利用…...

Linux 命令总结

基本操作 Linux关机,重启 # 关机 shutdown -h now# 重启 shutdown -r now 查看系统,CPU信息 # 查看系统内核信息 uname -a# 查看系统内核版本 cat /proc/version# 查看当前用户环境变量 envcat /proc/cpuinfo# 查看有几个逻辑cpu, 包括cpu型号 cat /proc/cpuinfo | grep na…...

使用腾讯手游助手作为开发测试模拟器的方案---以及部分问题的解决方案

此文主要介绍使用第三方模拟器(这里使用腾讯手游助手)作为开发工具&#xff0c;此模拟器分为两个引擎&#xff0c;一个与其他模拟器一样基于virtualbox的标准引擎&#xff0c;不过优化不太好&#xff0c;一个是他们主推的aow引擎&#xff0c;此引擎。关于aow没有太多的技术资料…...

牛客网论坛最具争议的Linux内核成神笔记,GitHub已下载量已过百万

原文地址&#xff1a;牛客网论坛最具争议的Linux内核成神笔记&#xff0c;GitHub已下载量已过百万 1、前言 Linux内核是一个操作系统&#xff08;OS&#xff09;内核&#xff0c;本质上定义为类Unix。它用于不同的操作系统&#xff0c;主要是以不同的Linux发行版的形式。Linu…...

docker如何容器迁移(实战)

手把手教你如何做容器迁移 第一步准备数据 假设要迁移一个 mysql 服务&#xff08;docker部署&#xff09;&#xff0c;由于数据库过大&#xff08;超过50 GB&#xff09;&#xff0c;用mysqldump备份和还原则太过耗时&#xff0c;下面尝试拷贝目录的方式来迁移&#xff0c;详…...

Android kotlin序列化之Parcelable详解与使用(二)

一、介绍 注解序列化篇&#xff1a;Android kotlin序列化之Parcelize详解与使用_蜗牛、Z的博客-CSDN博客 通过上一篇注解序列化&#xff0c;我们已了解的kotlin的序列化比Java复杂了很多。而且有好多问题&#xff0c;注解虽好&#xff0c;但是存在一些问题。 一般在大型商业…...

C++ 类设计的实践与理解

前言 C代码提供了足够的灵活性&#xff0c;因此对于大部分工程师来说都很难把握。本文介绍了写好C代码需要遵循的最佳实践方法&#xff0c;并在最后提供了一个工具可以帮助我们分析C代码的健壮度。 1. 尽可能尝试使用新的C标准 到2023年&#xff0c;C已经走过了40多个年头。新…...

循环链表的创建

循环链表的介绍及创建&#xff08;C语言代码实现&#xff09; 点击打开在线编译器&#xff0c;边学边练 循环链表概念 对于单链表以及双向链表&#xff0c;其就像一个小巷&#xff0c;无论怎么样最终都能从一端走到另一端&#xff0c;然而循环链表则像一个有传送门的小巷&…...

如何让GPT的回答令人眼前一亮,不再刻板回复!

我们平常在使用GPT的时候&#xff0c;是否觉得它的回复太过于死板、官方化&#xff0c;特别是用于创作、写论文分析的时候&#xff0c;内容往往让读者提不起兴趣、没有吸引人的地方&#xff0c;甚至有些内容百度都可以搜到。 举个例子&#xff0c;如下图: 问GPT&#xff0c;AI…...

JMeter测试笔记(四):逻辑控制器

引言&#xff1a; 进行性能测试时&#xff0c;我们需要根据不同的情况来设置不同的执行流程&#xff0c;而逻辑控制器可以帮助我们实现这个目的。 在本文中&#xff0c;我们将深入了解JMeter中的逻辑控制器&#xff0c;包括简单控制器、循环控制器等&#xff0c;并学习如何正…...

【计算机组成原理·笔记】I/O接口

I/O接口 概述I/O接口的功能和组成 I/O接口的组成I/O接口的功能 I/O接口类型 按数据传送方式按功能灵活性按通用性按数据传输的控制方式 概述 I/O接口通常是指主机与I/O设备之间设置的硬件电路以及相应的软件控制&#xff0c;主机通过I/O接口和I/O设备相连接。 I/O接口的功…...

MIT6.024学习笔记(二)——图论(1)

学习不是为了竞争和战胜他人&#xff0c;而是为了更好地了解自己和世界。 - 达赖喇嘛 文章目录 图的相关概念涂色问题基础涂色方法&#xff08;贪婪算法&#xff09;证明 二分图匹配问题应用&#xff1a;稳定婚烟问题算法性质及其证明 图的相关概念 图的定义&#xff1a;一组&…...

饼状图使用属性时,使用驼峰命名法

饼状图是使用D3.js等JavaScript库来绘制的&#xff0c;而JavaScript中的属性名通常采用驼峰式命名法&#xff0c;即第一个单词的首字母小写&#xff0c;后面单词的首字母大写&#xff0c;例如fontSize、fontWeight等。而CSS中的属性名采用连字符命名法&#xff0c;即单词之间用…...

使用Spring Boot、Spring Security和Thymeleaf的整合示例

使用Spring Boot、Spring Security和Thymeleaf的整合示例 大纲&#xff1a; 创建Spring Boot项目 集成Thymeleaf作为模板引擎 配置Spring Security实现身份验证和授权 创建登录页面和主页 创建管理员页面和普通用户页面 实现用户角色和权限管理 详细步骤&#xff1a; 创建Sprin…...

Linux--ServerProgramming--(7)IPC

1.管道 2.信号量 2.1 概念 信号量 是一个计数器&#xff0c;用于实现进程间互斥和同步。 信号量的取值可以是任何自然数。 最简单的信号量是只能取 0 和 1 的变量&#xff0c;这也是信号量最常见的一种形式&#xff0c;叫做二进制信号量&#xff08;Binary Semaphore&#…...

最优化理论-KKT定理的推导与实现

目录 一、引言 二、最优化问题的基本概念 三、KKT条件的引入 1. 梯度条件 2. 原始可行性条件 3. 对偶可行性条件 四、KKT定理的表述 五、KKT定理的证明 1. 构造拉格朗日函数 2. 构造拉格朗日对偶函数 3. 推导KKT条件 4. 解释KKT条件 六、KKT定理的应用 七、总结 …...

chatgpt赋能python:Python中引入其他包的指南

Python中引入其他包的指南 Python是一种流行的编程语言&#xff0c;拥有丰富的开源软件包和库。许多Python程序将使用其他包来增强其功能。在本文中&#xff0c;我们将探讨如何在Python项目中使用和引入其他包。 什么是Python包和库&#xff1f; Python包是一组可重复使用的…...

设计模式-组合模式

应用场景 实现规则匹配的逻辑 比如> <,同时支持 and or 多个条件组合 新增一个条件就增加一个实现类 说明 对于这种需要实现规则匹配的逻辑&#xff0c;可以考虑使用策略模式。策略模式可以将不同的算法封装成不同的策略类&#xff0c;让它们可以相互替换&#xff0c;…...

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…...

微信小程序之bind和catch

这两个呢&#xff0c;都是绑定事件用的&#xff0c;具体使用有些小区别。 官方文档&#xff1a; 事件冒泡处理不同 bind&#xff1a;绑定的事件会向上冒泡&#xff0c;即触发当前组件的事件后&#xff0c;还会继续触发父组件的相同事件。例如&#xff0c;有一个子视图绑定了b…...

CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型

CVPR 2025 | MIMO&#xff1a;支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题&#xff1a;MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者&#xff1a;Yanyuan Chen, Dexuan Xu, Yu Hu…...

大型活动交通拥堵治理的视觉算法应用

大型活动下智慧交通的视觉分析应用 一、背景与挑战 大型活动&#xff08;如演唱会、马拉松赛事、高考中考等&#xff09;期间&#xff0c;城市交通面临瞬时人流车流激增、传统摄像头模糊、交通拥堵识别滞后等问题。以演唱会为例&#xff0c;暖城商圈曾因观众集中离场导致周边…...

CMake基础:构建流程详解

目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…...

【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力

引言&#xff1a; 在人工智能快速发展的浪潮中&#xff0c;快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型&#xff08;LLM&#xff09;。该模型代表着该领域的重大突破&#xff0c;通过独特方式融合思考与非思考…...

Axios请求超时重发机制

Axios 超时重新请求实现方案 在 Axios 中实现超时重新请求可以通过以下几种方式&#xff1a; 1. 使用拦截器实现自动重试 import axios from axios;// 创建axios实例 const instance axios.create();// 设置超时时间 instance.defaults.timeout 5000;// 最大重试次数 cons…...

什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南

文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/55aefaea8a9f477e86d065227851fe3d.pn…...

mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包

文章目录 现象&#xff1a;mysql已经安装&#xff0c;但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时&#xff0c;可能是因为以下几个原因&#xff1a;1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...

【笔记】WSL 中 Rust 安装与测试完整记录

#工作记录 WSL 中 Rust 安装与测试完整记录 1. 运行环境 系统&#xff1a;Ubuntu 24.04 LTS (WSL2)架构&#xff1a;x86_64 (GNU/Linux)Rust 版本&#xff1a;rustc 1.87.0 (2025-05-09)Cargo 版本&#xff1a;cargo 1.87.0 (2025-05-06) 2. 安装 Rust 2.1 使用 Rust 官方安…...