• Spring Boot 保护敏感配置的 4 种方法,让你的系统不再裸奔。。。


    0、问题背景

    用 Spring Boot 框架的小伙伴应该都知道,Spring Boot 有个主要的 applicaiton 配置文件,那就会涉及到敏感配置信息,比如各种中间件的连接用户名密码信息、以及各种第三方的 KEY、密钥等。

    这种敏感信息如果直接放在配置文件中肯定是不安全的,甚至在很多行业及领域(比如:支付领域)都是不合规的,所以需要保护 Spring Boot 中的敏感配置信息。

    所以,你还在让你的 Spring Boot 系统裸奔吗?如果是,那不妨看看本文中栈长分享的 4 种方法,让你的系统不再裸奔!

    1、配置中心(支持自动解密)

    我觉得还得看大家的架构情况,如果使用了外置的第三方配置中心(支持自动解密的那种),就可以把所有的配置信息存储在配置中心,比如 Spring Cloud 生态中的配置中心,那么可以使用自带的加、解密机制保护敏感信息:

    spring:   
      datasource:     
        username: '{cipher}t1s293294187a31f35dea15e8bafaf7774532xxcc20d6d6dd0dfa5ae753d6836'
    

    需要加密的内容以 {cipher} 开头标识,并注意要使用单引号包起来,具体的细节可以参考《Spring Cloud 配置中心内容加密》这篇文章,Spring Boot 配置文件就只存储一些无关紧要的配置。

    大家用的哪款配置中心呢?支付配置加解密吗?欢迎分享!

    如果没有用到配置中心呢

    比如说传统的 Spring Boot 的 MVC 项目,所有的代码、配置都几乎在同一个项目中,Spring Boot 中的核心配置文件就是 application.yml(.properties)文件,那要怎么保护敏感配置信息呢?继续往下看!

    2、数据库机制

    可以把所有配置信息存储到数据库,系统启动的时候全部加载进内存。存储的时候,敏感信息以对称加密算法进行加密存储,然后加载的时候自动解密到内存。

    这是最传统的配置管理方法,其实你也可以理解为一个原始的、简易的配置中心,只是功能不那么强大而已,因为现在很多配置中心就是把配置放在数据库中进行存储,然后提供一系列的配置管理功能。

    这里的数据库可以是关系数据库(MySQL、Oracle)、内存数据库(Redis、Zookeeper)等,这是普遍用的比较多的中间件技术。

    3、自定义加解密机制

    这时候也要看使用的程度,如果只是简单的数据库连接池信息,那么可以考虑使用现有系统中的对称加密算法,再结合连接池数据源类实现自定义加解密机制,比如我们可以模仿 Spring Cloud 加密机制:

    先用系统已有的对称加密算法对数据库连接信息加密:

    spring:   
      datasource:     
        username: '{cipher}t1s293294187a31f35dea15e8bafaf7774532xxcc20d6d6dd0dfa5ae753d6836'
    

    排除 Spring Boot 系统自带的数据源自动配置,然后自行组装数据源 Spring Bean。

    判断获取的配置值是否以 {cipher} 这个标识开头,如果是,则用系统约定的对称加密算法进行解密,然后再设置数据源,比如:

    // 示例代码
    @Bean
    public DataSource dataSource(){
    	DataSource dataSource = new DruidDataSource();
    	
    	// 解密
    	String username = this.getUsername();
    	if (username.startWith('{cipher}')){
    		username = Encrypt.decrypt(username, this.getKey()))
    	} 
    	dataSource.setUsername(username);
    	
    	...
    	
    	return dataSource;
    }
    

    Spring Boot 基础就不介绍了,推荐下这个实战教程,教程和示例源码都已经传了:https://github.com/javastacks/spring-boot-best-practice

    这种使用简单,不用额外引入任何第三方包,如果大家也是使用的自定义数据源,或者这种手动加解密机制可以满足保护其他敏感配置的需求,那么这种方案供大家参考。

    上面介绍的自定义的加解密机制可以满足一般的需求,如果是 Spring Boot 自动配置的场景,比如数据源自动配置,Redis 自动配置,等等,这种在系统启动的时候就会默认自动配置,我们人工解密干预不到。

    像这种情况,我们就需要考虑介入框架层了,在 Spring Boot 框架读取配置的时候进行拦截解密,或者使用第三方的框架,用的比较多是:Jasypt Spring Boot

    4、Jasypt Spring Boot

    Jasypt Spring Boot 是一个专门为 Spring Boot 项目中的属性提供加密支持的框架,支持的版本为 Spring Boot 1.x ~ 2.x,栈长写文之时,现在已经有 1.8K+ 的 Star 数了,还是挺受欢迎的。

    开源地址:

    https://github.com/ulisesbocchio/jasypt-spring-boot

    这个开源项目更新也挺及时的,最新更新的,已支持 Spring Boot 2.5.4!

    这里栈长再免费分享你一份 Spring Boot 学习笔记,理论和实战都非常齐全,助你快速搞定 Spring Boot。

    4.1 Jasypt Spring Boot 实战

    Jasypt Spring Boot 有 3 种集成方法:

    1、如果开启了 Spring Boot 的自动配置(使用了 @SpringBootApplication 或者 @EnableAutoConfiguration 注解): 只需要添加 jasypt-spring-boot-starter 依赖即可,这种会在整个 Spring Environment 中启用可加密属性;

    2、添加 jasypt-spring-boot 依赖,同时在 Spring 主要配置类上添加 @EnableEncryptableProperties 注解,这种会在整个 Spring Environment 中启用可加密属性;

    3、添加 jasypt-spring-boot 依赖,使用 @EncrytablePropertySource 注解声明各个可加密的参数上,这种只适用于独立配置参数加解密;

    一般的 Spring Boot 都会开启自动配置,然后再排除个别的自动配置,所以很少会有全部禁用自动配置的情况,不然使用 Spring Boot 的意义不大,这里我们使用第 1 种集成方式进行演示。

    4.1.1 引入依赖

    核心依赖:

    <dependency>
        <groupId>com.github.ulisesbocchio</groupId>
        <artifactId>jasypt-spring-boot-starter</artifactId>
        <version>3.0.4</version>
    </dependency>
    

    Maven 插件(可选)

    <build>
        <plugins>
            <plugin>
                <groupId>com.github.ulisesbocchio</groupId>
                <artifactId>jasypt-maven-plugin</artifactId>
                <version>${jasypt-spring-boot.version}</version>
            </plugin>
        </plugins>
    </build>
    

    4.1.2 添加密钥

    jasypt:
      encryptor:
        password: G9w0BAQEFAASCBKYwggSiAgEAAoIBAQC
        property:
          prefix: "ENC@["
          suffix: "]"
    

    这个 jasypt.encryptor.password 参数是必须的,相当于 Salt(盐),以保证密码安全性,prefixprefix 是自定义的密码串标识,不配置默认为:ENC(...)

    4.1.3 敏感信息加密

    /**
     * 来源微信公众号:Java技术栈
     * 作者:栈长
     */
    @Slf4j
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class JasyptTest {
    
        @Autowired
        private StringEncryptor stringEncryptor;
    
        /**
         * 来源微信公众号:Java技术栈
         * 作者:栈长
         */
        @Test
        public void encrypt() {
            String usernameEnc = stringEncryptor.encrypt("javastack");
            String passwordEnc = stringEncryptor.encrypt("javastack.cn");
    
            log.info("test username encrypt is {}", usernameEnc);
            log.info("test password encrypt is {}", passwordEnc);
    
            log.info("test username is {}", stringEncryptor.decrypt(usernameEnc));
            log.info("test password is {}", stringEncryptor.decrypt(passwordEnc));
        }
    }
    

    这里我注入了一个 StringEncryptor,其类结构图如下:

    如果当前没有自定义 StringEncryptor,Jasypt Spring Boot 的自动配置会默认创建一个 StringEncryptor 实例,直接用就行了,其构造器默认值如下:

    Key Required Default Value
    jasypt.encryptor.password True -
    jasypt.encryptor.algorithm False PBEWITHHMACSHA512ANDAES_256
    jasypt.encryptor.key-obtention-iterations False 1000
    jasypt.encryptor.pool-size False 1
    jasypt.encryptor.provider-name False SunJCE
    jasypt.encryptor.provider-class-name False null
    jasypt.encryptor.salt-generator-classname False org.jasypt.salt.RandomSaltGenerator
    jasypt.encryptor.iv-generator-classname False org.jasypt.iv.RandomIvGenerator
    jasypt.encryptor.string-output-type False base64
    jasypt.encryptor.proxy-property-sources False false
    jasypt.encryptor.skip-property-sources False empty list

    然后运行测试用例来看下测试结果:

    加解密成功!!

    另外,通过 DEBUG 调试可以看到是一个 DefaultLazyEncryptor 实例:

    当然也支持自定义的 Encryptor,有需要的可以自行定制。

    如果不想用测试这种方法生成密文,也可以使用 Maven 插件,这就是前面为什么要加 Maven 插件(可选)的原因,使用方式如下:

    mvn jasypt:encrypt-value -Djasypt.encryptor.password="G9w0BAQEFAASCBKYwggSiAgEAAoIBAQC" -Djasypt.plugin.value="javastack"

    4.1.4 敏感信息解密

    将上一步生成的密文填充到 application 配置文件中:

    javastack:
      username: ENC@[K4DsOasic/5Cvu2Y6Ca5dyaw2+eejgqRfhDWB0itMWRONrIN+wLy3xkGbSfYxQ1b]
      password: ENC@[UeZWoPt3ZhSs2wPUAKTF21dgnhzimB+FNNiQjpJoPEhwYzI5WH3IWboZ5Wn+5Rgf]
    

    注意 ENC@[] 这个占位符是上面进行自定义配置的。

    然后再写一个程序尝试打印出来:

    /** * 来源微信公众号:Java技术栈 * 作者:栈长 */@Slf4j@SpringBootApplicationpublic class Application {    @Value("${javastack.username}")    private String username;    @Value("${javastack.password}")    private String password;    /**     * 来源微信公众号:Java技术栈     * 作者:栈长     */    public static void main(String[] args) {        SpringApplication.run(Application.class);    }    /**     * 来源微信公众号:Java技术栈     * 作者:栈长     */    @Bean    public CommandLineRunner commandLineRunner() {        return (args) -> {            log.info("javastack.username = {}", username);            log.info("javastack.password = {}", password);        };    }}
    

    栈长写了一个 CommandLineRunner,在系统启动之后将密文的原文打印出来,不需要做任何处理,直接注入、打印就行,看是不是明文。

    系统启动之后:

    结果正常,自动解密成功。

    本节教程所有实战源码已上传到这个仓库:

    https://github.com/javastacks/spring-boot-best-practice

    大家可以一键 Star,持续更新~

    4.2 密钥安全性

    我们把 Jasypt 密钥(password)存放在 application 配置文件中,这样敏感信息还是在项目代码中,也不是太安全,建议通过命令行参数的方式传入,如在 IDEA 中这样设置:

    如果是生产环境,可以通过命令的方式传入:

    java -Djasypt.encryptor.password=password -jar xx.jar

    甚至还可以配置在服务器环境变量中,因为 StringEncryptor 可以通过 系统参数、配置文件、命令行参数、环境变量 等等方式进行构造。

    这样 Spring Boot 中的配置信息就彻底安全了!

    Jasypt Spring Boot 功能远不止如此,实际功能要更强大,这里栈长只是介绍了简单的运用,更多的自定义的需求大家可以参考官方文档,那里有更详细的教程。

    4.3 Jasypt Spring Boot 原理

    Jasypt Spring Boot 它注册了一个 Spring 后处理器,它修饰包含在 Spring Environment 中的所有 PropertySource 对象,并按照 Jasypt 的配置约定对属性进行加解密。

    来跟一波源码:

    源码有点复杂,一路找到了DefaultPropertyResolver 这个解密器,然后它也是注入了 StringEncryptor 这个实例,获取配置时,会进行解密后再返回。

    另外,这个 Resolver 也是支持自定义的,有兴趣的可以深入研究下。

    总结

    好了,今天栈长介绍了 Spring Boot 保护敏感配置信息的 4 种方法,总结一下:

    • 配置中心(支持自动加解密)
    • 自定义加解密机制
    • 数据库机制
    • Jasypt Spring Boot(第三方加解密方案)

    总之敏感信息不要放在 Spring Boot 配置文件中,一定要放,就一定要加密,这 4 种方案各有各的应用场景,要结合公司现有的架构和系统规模作出权衡,另外之前写的这篇《分布式系统中处理参数配置的 4 种方案》也供大家参考下。

    本节教程所有实战源码已上传到这个仓库:

    https://github.com/javastacks/spring-boot-best-practice

    欢迎 Star 关注学习,后续会持续更新 Spring Boot 实战教程。

    好了,今天的分享就到这里了,后面栈长会分享更多好玩的 Java 技术和最新的技术资讯,关注公众号Java技术栈第一时间推送,我也将主流 Java 面试题和参考答案都整理好了,在公众号后台回复关键字 "面试" 进行刷题。

    版权声明: 本文系公众号 "Java技术栈" 原创,转载、引用本文内容请注明出处,抄袭、洗稿一律投诉侵权,后果自负,并保留追究其法律责任的权利。

    近期热文推荐:

    1.1,000+ 道 Java面试题及答案整理(2022最新版)

    2.劲爆!Java 协程要来了。。。

    3.Spring Boot 2.x 教程,太全了!

    4.别再写满屏的爆爆爆炸类了,试试装饰器模式,这才是优雅的方式!!

    5.《Java开发手册(嵩山版)》最新发布,速速下载!

    觉得不错,别忘了随手点赞+转发哦!

  • 相关阅读:
    【数据结构第二周】队列知识点整理
    【数据结构第二周】堆栈知识点整理
    【数据结构第二周】线性表知识点整理
    【数据结构第一周】最大子列和问题整理
    网络设置
    QT 安装教程
    C# 复制粘贴板 多行粘贴
    设置网络适配器IP优先级
    MySQL 查重复单号和删重复单号
    Mysql 10053 SocketException 你的主机中的软件中止了一个已建立的连接。
  • 原文地址:https://www.cnblogs.com/javastack/p/16004330.html
Copyright © 2020-2023  润新知