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

这篇具有很好参考价值的文章主要介绍了安全实现SpringBoot配置文件自动加解密。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

需求背景

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

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

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

实现思路

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

通过实现EnvironmentPostProcessor方式解决

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

    @Override
    public 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;

    @Override
    public 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{", "}"))
                );
            }
        }
    }

    @Override
    public 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;
    }

    @Override
    public 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);
    }

    @Override
    public String encrypt(String message) {
        return Base64.getEncoder().encodeToString(_AesUtils.encrypt(secretKey, iv, message.getBytes()));
    }

    @Override
    public 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())));
    }

    @SneakyThrows
    static byte[] loadPrivateKey() {
        return loadResource(PRIVATE_KEY_RESOURCE_LOCATION);
    }

    @SneakyThrows
    static byte[] loadPublicKey() {
        return loadResource(PUBLIC_KEY_RESOURCE_LOCATION);
    }

    @SneakyThrows
    private static byte[] loadResource(String location) {
        // just lookup from current jar  path
        ClassLoader classLoader = new URLClassLoader(new URL[]{PrivateKeyFinder.class.getProtectionDomain().getCodeSource().getLocation()}, null);
//        classLoader = PrivateKeyFinder.class.getClassLoader();
        Enumeration<URL> enumeration = classLoader.getResources(location);

        // should only find one
        while (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";

    @SneakyThrows
    public 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);
    }

    @SneakyThrows
    public 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);
    }
}

综上既可以实现敏感配置文件的加解密,同时可以保障加密密钥的安全传入文章来源地址https://www.toymoban.com/news/detail-457712.html

到了这里,关于安全实现SpringBoot配置文件自动加解密的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • SpringBoot——原理(自动配置_案例(自定义阿里云文件上starter))

    本文同步更新于鼠鼠之家 starter就是springboot中的起步依赖,虽然springboot已经提供了很多的起步依赖,但是在实际项目开发中可能会用到和第三方的技术,不是所有第三方在springboot中都有收录。 比如之前文章中有用到过的阿里云OSS,阿里云并没有提供起步依赖,导致每次使用

    2024年02月06日
    浏览(44)
  • SpringBoot——原理(自动配置_案例(自定义阿里云文件上传starter))

    本文同步更新于鼠鼠之家 starter就是springboot中的起步依赖,虽然springboot已经提供了很多的起步依赖,但是在实际项目开发中可能会用到和第三方的技术,不是所有第三方在springboot中都有收录。 比如之前文章中有用到过的阿里云OSS,阿里云并没有提供起步依赖,导致每次使用

    2024年02月07日
    浏览(37)
  • SpringBoot实现文件记录日志,日志文件自动归档和压缩

    😊 @ 作者: Eric 💖 @ 主页: https://blog.csdn.net/weixin_47316183?type=blog 🎉 @ 主题: SpringBoot实现文件记录日志,日志文件自动归档和压缩 ⏱️ @ 创作时间: 2023年08月06日 Logback 是一个Java日志框架,它是 log4j 的后继者,被广泛用于应用程序中记录日志。 Logger(日志记录器): L

    2024年02月14日
    浏览(33)
  • 【Springboot】| 从深入自动配置原理到实现 自定义Springboot starter

    Springboot starter 是SpringBoot的一个重要概念,是“一站式服务 (one-stop)”的依赖 Jar 包包含 Spring 以及相关技术(比如 Redis)的所有依赖提供了自动配置的功能,开箱即用提供了良好的依赖管理,避免了包遗漏、版本冲突等问题。 简单来说, Springboot starter 提供了一种自动配置的机制

    2024年02月11日
    浏览(34)
  • C#集成数据加密算法,包含DES、RSA、Base64、SHA、MD5算法,轻松实现数据加密解密需求

    在需要使用配置文件的工控软件中,往往需要在配置文件和数据库中对一些数据加密,即对一串数据进行加密算法后输出复杂符号和字符的形式,让非相关人员无法识别原有数据,从而对数据或数据库进行相应的保护,这往往也是公司安全部门的基本要求。 网上写加密算法的

    2024年02月03日
    浏览(79)
  • 接口安全防线加解密:springboot 全局/指定接口解密(同时支持参数在body和param)

    1.1.过滤器,过滤所有请求,利用HttpServletRequestWrapper解决request中流读取一次的处理,方便后续修改请求内容 1.2.自定义注解,通过自定义注解可以标识,指定哪些接口在拦截器中处理数据 1.3.拦截器,拦截带有指定注解的请求,把数据进行加密解密后返回处理 2.1.实际可以自己

    2024年02月15日
    浏览(38)
  • SpringBoot进阶-SpringBoot如何实现配置文件脱敏

    SpringBoot进阶-SpringBoot如何实现配置文件脱敏? SpringBoot集成jasypt配置信息加密以及采坑 在很多开发场景中我们的SpringBoot应用是被打包成了一个Jar文件来使用的,利用解压缩工具可以将这个Jar包解压出来并且在对应的配置路径下找到数据库的访问地址以及数据库的登录密码等等

    2024年02月08日
    浏览(39)
  • springboot的配置项ENC加解密

           在web项目中我们看到application文件中很多出现配置项是ENC(xxxxx),这就表示xxx这个参数是经过加密之后的结果。         我们想要在其他地方使用参数必须要做解密。以下是实现方法。 加解密的实现依赖jasypt。所以需要引入以下jar包 加解密方法  其中 password是必须提

    2024年03月17日
    浏览(34)
  • 一键解密PDF,让文件安全无忧

    忘记PDF密码?在线找回、解密、去除密码,百度搜索“密码帝官网”一步搞定!     如果您经常使用PDF文档,可能会遇到无法编辑、打印或复制的情况。此时,您不必担心!为您提供了一种安全、简单易操作、实惠、不用下载软件的解决方案,无论是使用手机还是电脑都可以

    2024年02月12日
    浏览(40)
  • SpringBoot+Vue 实现图片验证码功能需求

    文章底部有个人公众号: 热爱技术的小郑 。主要分享开发知识、有兴趣的可以关注一下。为何分享? 踩过的坑没必要让别人在再踩,自己复盘也能加深记忆。利己利人、所谓双赢。 写过验证码保存到Redis中的需求开发、也写过验证码调用第三方接口直接发送到手机的需求开

    2024年02月14日
    浏览(35)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包