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. linux部署go

    一.下载tar包并解压 yum -y install wget glibc.i686 #后面一个是依赖包,不安装会报错: /lib/ld-linux.so.2: bad ELF interpreter ...

  2. sts问题合集

    背景:用来记录在使用sts过程中遇到的相关问题 Version 当前jdk版本号 of the JVM is not suitable for the this product.Version:1.8 ...

  3. Jenkins工具学习(一)

    Jenkins的下载及安装 Jenkins下载地址:https://jenkins.io/download/ 下载后的直接解压安装 根据自己的喜好选择一种方式安装: 如果选择推荐安装,会自动下载一些插 ...

  4. Metasploaitable和侦察httrack-安全牛课堂网络安全之Web渗透测试练习记录

    环境配置 首先在网上下载kali的镜像以及Metasploaitable虚拟机,打开按照网上教程安装好kali虚拟机,另一边打开Metasploaitable虚拟机,进入输入初始账户msfadmin, ...

  5. docker image 镜像导入导出

    docker image save -o webv6.tar techcn/noble.web:v6docker image load -i webv6.tar -q

  6. 由一个问题引起的思考:WEB开发中,使用JSON-RPC好,还是RESTful API好?

    起因: 研究zabbix的API设计风格.查看zabbix官网API文档,可以看到使用的是json-rpc:2.0 随后搜索到知乎上的一个问题讨论:https://www.zhihu.com/ques ...

  7. QT之类型转换

    Qt在进行数据类型转换时,容易忘记如何使用,或者是早已厌倦了百度QString转QByteArray,QByteArray转char,QString转string....... 现在分享一篇QT数据类 ...

  8. - Permission 运行时权限 总结 翻译 MD

    目录 目录 对运行时权限的一些理解 运行时权限使用案例 开源库:PermissionsDispatcher 注解 使用案例 使用步骤 测试代码 自动生成的类 官方文档:请求权限 Add permiss ...

  9. ASP.NET 异步编程之Async await

    本文重点介绍的是.NET Framework4.5 推出的异步编程方案  async await 请先看个5分钟的微软演示的视频:视频地址: https://channel9.msdn.com/Blo ...

  10. kylin2.4.1订单案例详细构建流程

    一.Hive订单数据仓库构建: hive表创建可以在命令行中直接完成,也可以在Hue中完成,本文在Hue中的完成,如下图: 下文的样例文本文件下载地址:https://files-cdn.cnblog ...