zoukankan      html  css  js  c++  java
  • Spring Boot 配置文件密码加密两种方案

    Spring Boot 配置文件密码加密两种方案

    jasypt 加解密

    jasypt 是一个简单易用的加解密Java库,可以快速集成到 Spring 项目中。可以快速集成到 Spring Boot 项目中,并提供了自动配置,使用非常简单。
    jasypt 库已上传到 Maven 中央仓库, 在 GitHub 上有更详细的使用说明
    jasypt 的实现原理是实现了 ApplicationContextInitializer 接口,重写了获取环境变量的方法,在容器初始化时对配置文件中的属性进行判断,若包含前后缀(ENC())表示是加密属性值,则进行解密并返回。

    你的配置文件是不是还在使用下面这种落后的配置暴露一些密码:

    jdbc.url=jdbc:mysql://127.0.0.1:3305/afei
    jdbc.username=afei
    jdbc.password=123456

    如果是,那么继续往下看。笔者今天介绍史上最优雅加密接入方式:jasypt

    使用方式

    • 用法一

    先看用法有多简单,以springboot为例:

    1. Application.java上增加注解@EnableEncryptableProperties(jasypt-spring-boot-starter包不需要该配置);

    2. 增加配置文件jasypt.encryptor.password = Afei@2018,这是加密的秘钥;

    3. 所有明文密码替换为ENC(加密字符串),例如ENC(XW2daxuaTftQ+F2iYPQu0g==);

    4. 引入一个MAVEN依赖;

    maven坐标如下:

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

    简答的4步就搞定啦,是不是超简单?完全不需要修改任何业务代码。    其中第三步的加密字符串的生成方式为:
    java -cp jasypt-1.9.2.jar org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI input="123456" password=Afei@2018 algorithm=PBEWithMD5AndDES

    其中:

    • input的值就是原密码。

    • password的值就是参数jasypt.encryptor.password指定的值,即秘钥。


    • 用法二

    其实还有另一种更简单的姿势:

    1. 增加配置文件jasypt.encryptor.password = Afei@2018,这是加密的秘钥;

    2. 所有明文密码替换为ENC(加密字符串),例如ENC(XW2daxuaTftQ+F2iYPQu0g==);

    3. 引入一个MAVEN依赖;

    maven坐标如下:

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

    相比第一种用法,maven坐标有所变化。但是不需要显示增加注解@EnableEncryptableProperties;

    github地址

    github:https://github.com/ulisesbocchio/jasypt-spring-boot
    它github首页有详细的用法说明,以及一些自定义特性,例如使用自定义的前缀和后缀取代ENC():

    jasypt.encryptor.property.prefix=ENC@[
    jasypt.encryptor.property.suffix=]

    原理解密

    既然是springboot方式集成,那么首先看jasypt-spring-boot的spring.factories的申明:

    org.springframework.context.ApplicationListener=
    com.ulisesbocchio.jasyptspringboot.configuration.EnableEncryptablePropertiesBeanFactoryPostProcessor

    这个类的部分核心源码如下:

    public class EnableEncryptablePropertiesBeanFactoryPostProcessor implements BeanFactoryPostProcessor, ApplicationListener<ApplicationEvent>, Ordered {
        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
            // 得到加密字符串的处理类(已经加密的密码通过它来解密)
            EncryptablePropertyResolver propertyResolver = beanFactory.getBean(RESOLVER_BEAN_NAME, EncryptablePropertyResolver.class);
            // springboot下的Environment里包含了所有我们定义的属性, 也就包含了application.properties中所有的属性
            MutablePropertySources propSources = environment.getPropertySources();
            // 核心,PropertySource的getProperty(String)方法委托给EncryptablePropertySourceWrapper
            convertPropertySources(interceptionMode, propertyResolver, propSources);
        }

        @Override
        public int getOrder() {
            // 让这个jasypt定义的BeanFactoryPostProcessor的初始化顺序最低,即最后初始化
            return Ordered.LOWEST_PRECEDENCE;
        }
    }

    PropertySource的getProperty(String)方法委托给EncryptablePropertySourceWrapper,那么当获取属性时,实际上就是调用EncryptablePropertySourceWrapper的getProperty()方法,在这个方法里我们就能对value进行解密了。

    EncryptablePropertySourceWrapper实现了接口EncryptablePropertyResolver,该定义如下:

    // An interface to resolve property values that may be encrypted.
    public interface EncryptablePropertyResolver {

        String resolvePropertyValue(String value);
    }

    接口描述:
    Returns the unencrypted version of the value provided free on any prefixes/suffixes or any other metadata surrounding the encrypted value. Or the actual same String if no encryption was detected.

    • 如果通过prefixes/suffixes包裹的属性,那么返回解密后的值;

    • 如果没有被包裹,那么返回原生的值;

    实现类的实现如下:

    @Override
    public String resolvePropertyValue(String value) {
        String actualValue = value;
        // 如果value是加密的value,则进行解密。
        if (detector.isEncrypted(value)) {
            try {
                // 解密算法核心实现
                actualValue = encryptor.decrypt(detector.unwrapEncryptedValue(value.trim()));
            } catch (EncryptionOperationNotPossibleException e) {
                // 如果解密失败,那么抛出异常。
                throw new DecryptionException("Decryption of Properties failed,  make sure encryption/decryption passwords match", e);
            }
        }
        // 没有加密的value,返回原生value即可
        return actualValue;
    }

    判断是否是加密的逻辑很简单:(trimmedValue.startsWith(prefix) && trimmedValue.endsWith(suffix)),即只要value是以prefixe/suffixe包括,就认为是加密的value。

    总结

    通过对源码的分析可知jasypt的原理很简单,就是讲原本spring中PropertySource的getProperty(String)方法委托给我们自定义的实现。然后再自定义实现中,判断value是否是已经加密的value,如果是,则进行解密。如果不是,则返回原value。注意该方式由于会在bean初始化前做一些操作, 和dubbo混用是容易导致对dubbo的初始化进行预操作, 导致dubbo加载失败

    druid 非对称加密

    数据库连接池 Druid 自身支持对数据库密码的加密解密, 是通过 ConfigFilter 实现的,在 GitHub 有官方的指导说明

    1. 添加依赖,

      1
      2
      3
      4
      5
      <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid-spring-boot-starter</artifactId>
      <version>1.1.10</version>
      </dependency>
    2. 配置数据源,先用明文密码连接数据库

      1
      2
      spring.datasource.username=root
      spring.datasource.password=123456
    3. 编写测试代码生成非对称加密的公钥和私钥

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      @RunWith(SpringRunner.class)
      @SpringBootTest
      public class ApplicationTests {

      @Test
      public void druidEncrypt() throws Exception {
      //密码明文
      String password = "123456";
      System.out.println("明文密码: " + password);
      String[] keyPair = ConfigTools.genKeyPair(512);
      //私钥
      String privateKey = keyPair[0];
      //公钥
      String publicKey = keyPair[1];

      //用私钥加密后的密文
      password = ConfigTools.encrypt(privateKey, password);

      System.out.println("privateKey:" + privateKey);
      System.out.println("publicKey:" + publicKey);

      System.out.println("password:" + password);

      String decryptPassword = ConfigTools.decrypt(publicKey, password);
      System.out.println("解密后:" + decryptPassword);
      }
      }

      #------------结果------------------
      明文密码: 123456
      privateKey:MIIBVgIBADANBgkqhkiG9w0BAQEFAASCAUAwggE8AgEAAkEAlgDJ+BjPrmzXfnZ3DYddy7LyVqvyWkbDkVuw+hhsKPZNJRpuCjAGj9omHoj4EJ5ZMsW8emKapCPZaKKUtw1DhQIDAQABAkAgpdtPnFbXZ+kfJTmUQDox86i7JIGDFJPMN2C1jks8PsoKRuMwbSSXd3owdGyEQ28bJa3EOEdkGex+2IqsfZwBAiEAx7aclTD+MVsx9dkOcp5oWpCDpQCK0gbnyIeS5arUcyECIQDAR5Czh8ejceRRcG7yH13+FcC2GIgtLxYmi691hrBn5QIhAJuRCcPFGByGNxKUc4ahEhSJwaIEHB6iNmakBK9WNItBAiEAtXBSmTadKhxEyJyB9LOorCS2rp5Dke+GxWS2cv5f5AkCIQCwhGIq7dmtg12cK4S63zD9/SIbLMTW89ph4rgQFEsoMg==

      publicKey:MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJYAyfgYz65s1352dw2HXcuy8lar8lpGw5FbsPoYbCj2TSUabgowBo/aJh6I+BCeWTLFvHpimqQj2WiilLcNQ4UCAwEAAQ==

      password:CFJ5PUOf0GLY56E27pCPI12eHFqtFzVk/XcBN49qr1e/ya/X1eN4FtGLnaEe/7VPefF40UKPgSqFMbnfPLKAiA==

      生成公钥和私钥,还可使用命令生成:java -cp druid-1.0.16.jar com.alibaba.druid.filter.config.ConfigTools you_password

    4. 配置文件增加解密支持,并替换明文密码

      1
      2
      3
      4
      5
      6
      7
      8
      9
      #---------密码加密------------------------
      spring.datasource.username=panda
      spring.datasource.password=CFJ5PUOf0GLY56E27pCPI12eHFqtFzVk/XcBN49qr1e/ya/X1eN4FtGLnaEe/7VPefF40UKPgSqFMbnfPLKAiA==
      #---------开启ConfigFilter支持-----------
      spring.datasource.druid.filter.config.enabled=true
      #---------设置公钥------------------------
      spring.datasource.publicKey=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAINRom1IY639dDMD0FFw7zMsxRVABYGJnKxSpO84dyJgXaIkoTZkE1JaWE2/gtgli28vgM72UHf2EGhxbLZwzhsCAwEAAQ==
      #---------设置连接属性---------------------
      spring.datasource.druid.connection-properties=config.decrypt=true;config.decrypt.key=${spring.datasource.publicKey}
    5. 重启应用, 查看数据源初始化时的连接是否成功。
      再谨慎点是把测试生成密文的java文件也删除。

    完整配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    #=============jdbc dataSource=========================
    spring.datasource.name=druidDataSource
    spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
    spring.datasource.url=jdbc:mysql://localhost:3306/sakila?characterEncoding=utf-8&allowMultiQueries=true&autoReconnect=true

    #账号密码明文显示
    #spring.datasource.username=panda
    #spring.datasource.password=123456

    #方案一:jasypt加解密
    #spring.datasource.username=ENC(ocj4Go8I46th0NOUs2BdGg==)
    #spring.datasource.password=ENC(QA8zJh3woJEjyJjaKCpsiQ==)
    #jasypt加密
    #jasypt.encryptor.password=vh^onsYFUx^DMCKK

    #方案二:druid自带非对称加密
    spring.datasource.username=root
    spring.datasource.password=ai9lB7h4oR9AHrQzU8H38umcelX9dBmx4aSycDOgJWa/2sv5U0GzbyI9sx54sL3nJ0kGayGrTHl3N/Bp1sSJ4w==

    spring.datasource.driver-class-name=com.mysql.jdbc.Driver
    spring.datasource.druid.initial-size=5
    spring.datasource.druid.max-active=20
    spring.datasource.druid.min-idle=5
    spring.datasource.druid.max-wait=10
    spring.datasource.druid.validationQuery=SELECT 1
    spring.datasource.druid.filter.config.enabled=true
    #spring.datasource.druid.filters=stat,wall,log4j2,config
    spring.datasource.publicKey=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAINRom1IY639dDMD0FFw7zMsxRVABYGJnKxSpO84dyJgXaIkoTZkE1JaWE2/gtgli28vgM72UHf2EGhxbLZwzhsCAwEAAQ==
    spring.datasource.druid.connection-properties=config.decrypt=true;config.decrypt.key=${spring.datasource.publicKey}

    注意:最好将密钥或私钥作为环境变量参数在执行应用的启动命令时传入,而不是放在配置文件中。

    示例源码 -> GitHubhttp://www.gxitsky.com/2018/09/19/springboot-app-32-password-encryptor/

  • 相关阅读:
    【算法】LeetCode算法题-Count And Say
    【算法】LeetCode算法题-Search Insert Position
    使用POI设置excel背景色
    Ubuntu中开启MySQL远程访问功能,并将另一个数据库服务器中的数据迁移到新的服务器中
    利用mybatis_generator自动生成Dao、Model、Mapping相关文件
    Meven笔记
    js调用百度地图API创建地图
    MySQL中日期与字符串相互转换,并进行日期比较查询
    java中将汉字转换成16进制
    Java中将16进制字符串转换成汉字
  • 原文地址:https://www.cnblogs.com/kexianting/p/11689289.html
Copyright © 2011-2022 走看看