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/

Spring Boot 配置文件密码加密两种方案的更多相关文章

  1. Spring Boot 整合 Shiro ,两种方式全总结!

    在 Spring Boot 中做权限管理,一般来说,主流的方案是 Spring Security ,但是,仅仅从技术角度来说,也可以使用 Shiro. 今天松哥就来和大家聊聊 Spring Boot ...

  2. Spring Boot配置过滤器的两种方式

    过滤器(Filter)是Servlet中常用的技术,可以实现用户在访问某个目标资源之前,对访问的请求和响应进行拦截,常用的场景有登录校验.权限控制.敏感词过滤等,下面介绍下Spring Boot配置过 ...

  3. spring boot 集成 Filter 的两种方式

    两种方式:(两种方式同时存在时,@Bean优先@ServletComponentScan实例化,生成两个对象) 1)@ServletComponentScan注解+@WebFilter注解 2)@Be ...

  4. spring boot 集成 Listener 的两种方式

    1)@ServletComponentScan注解+@WebListener注解 2)@Bean注解+ServletListenerRegistrationBean类

  5. spring boot返回Josn的两种方式

    1.Controller类上加@RestController注解 2.Controller类上加@Controller注解,Action接口上加@ResponseBody注解 @Responsebod ...

  6. Spring Boot配置文件大全

    Spring Boot配置文件大全 ############################################################# # mvc ############## ...

  7. Spring Boot 配置文件 bootstrap vs application 到底有什么区别?

    用过 Spring Boot 的都知道在 Spring Boot 中有以下两种配置文件 bootstrap (.yml 或者 .properties) application (.yml 或者 .pr ...

  8. Spring Boot 配置文件详解

    Spring Boot配置文件详解 Spring Boot提供了两种常用的配置文件,分别是properties文件和yml文件.他们的作用都是修改Spring Boot自动配置的默认值.相对于prop ...

  9. Springboot 系列(二)Spring Boot 配置文件

    注意:本 Spring Boot 系列文章基于 Spring Boot 版本 v2.1.1.RELEASE 进行学习分析,版本不同可能会有细微差别. 前言 不管是通过官方提供的方式获取 Spring ...

随机推荐

  1. CUDA 到底什么玩意

    * CUDA与cuDNN * 什么是CUDA * CUDA(ComputeUnified Device Architecture),是显卡厂商NVIDIA推出的运算平台. CUDA是一种由NVIDIA ...

  2. 解决Spring和SpringMVC扫描注解类的冲突问题

    原文地址:https://blog.csdn.net/xiaobao5214/article/details/52042041 最正确的配置方式:在主容器中applicationContext.xml ...

  3. phpcms发布文章自定义字段

    phpcms发布文章自定义字段1 进入后台 模型管理 新建模型 里面可以自定义字段了

  4. AKKA Inbox收件箱

    起因 得到ActorRef就可以给actor发送消息,但无法接收多回复,也不知道actor是否停止 Inbox收件箱出现就是解决这两个问题 示例 package akka.demo.actor imp ...

  5. slf4j log4j logback

    最先大家写日志都用log4j,后来作者勇于创新,又搞了个logback,又为了统一江湖,来了个slf4j,所以目前在代码中进行日志输出,推荐使用slf4j,这样在运行时,你可以决定到底是用log4j还 ...

  6. jquery.i18n.properties前端国际化方案

    如果新项目要做系统国际化, 时下热门的任何一种技术选型都有成熟的方案,比如: vue + vue-i18n angular + angular-translate react + react-intl ...

  7. php中命名空间namespace和use

    对于面向对象编程而言,命名空间namespace和use的概念非常重要. 1.根命名空间是反斜线 \ ,有点类似linux中的根目录 / 的那种感觉,但使用var_dump()函数打印时其实是空字符串 ...

  8. Java笔记_静态变量和实例变量的区别

    这里简单做一下笔记,区分Java全局变量里的静态变量与实例变量. 1.Java里的全局变量 首先了解Java里的全局变量,也叫成员变量. 特点: (1).一个类中既不在方法体内,也不在程序块内定义的变 ...

  9. 【题解】Luogu P4091 [HEOI2016/TJOI2016]求和

    原题传送门 \[\begin{aligned} a n s &=\sum_{i=0}^{n} \sum_{j=0}^{i}\left\{\begin{array}{c}{i} \\ {j}\e ...

  10. c#按指定长度分解数组

    在操作数据库时,我们需要注意一点,就是in查询的参数不能超过1000个,否则会报错,所以我们在进行in查询的时候需要对参数数量进行控制: 用于分解数组的扩展方法: /// <summary> ...