實踐:使用Jasypt加密SpringBoot配置文件加密springboot配置文件


小試牛刀
git

1.構建一個springboot項目,而且引入jasypt依賴github

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

2.編寫一個單元測試,用於獲取加密後的帳號密碼算法

StringEncryptor是jasypt-spring-boot-starter自動配置的加密工具,加密算法咱們選擇PBEWithHmacSHA512AndAES_128,password爲123abcspring

jasypt.encryptor.password=123abcjasypt.encryptor.algorithm=PBEWithHmacSHA512AndAES_128
@SpringBootTestclass SpringbootPropertiesEncApplicationTests {    @Autowired    private StringEncryptor stringEncryptor;    @Test    void contextLoads() {        String sunshujie = stringEncryptor.encrypt("sunshujie");        String qwerty1234 = stringEncryptor.encrypt("qwerty1234");        System.out.println(sunshujie);        System.out.println(qwerty1234);    }}

3.在application.properties中配置加密後的帳號密碼數據庫

jasypt.encryptor.password=123abcjasypt.encryptor.algorithm=PBEWithHmacSHA512AndAES_128username=ENC(pXDnpH3GdMDBHdxraKyAt7IKCeX8mVlM9A9PeI9Ow2VUoBHRESQ5m8qhrbp45vH+)password=ENC(qD55H3EKYpxp9cGBqpOfR2pqD/AgqT+IyClWKdW80MkHx5jXEViaJTAx6Es4/ZJt)

4.觀察在程序中是否可以拿到解密後的帳號密碼springboot

@SpringBootApplicationpublic class SpringbootPropertiesEncApplication implements CommandLineRunner {    private static final Logger logger = LoggerFactory.getLogger(SpringbootPropertiesEncApplication.class);    public static void main(String[] args) {        SpringApplication.run(SpringbootPropertiesEncApplication.class, args);    }    @Value("${password}")    private String password;    @Value("${username}")    private String username;    @Override    public void run(String... args) throws Exception {        logger.info("username: {} , password: {} ", username, password);    }}

原理解析markdown

加密原理app

首先看jasypt相關的配置,分別是password和加密算法運維

jasypt.encryptor.password=123abcjasypt.encryptor.algorithm=PBEWithHmacSHA512AndAES_128

PBEWithHmacSHA512AndAES_128是這次咱們選用的加密算法.ide

123abc是PBEWithHmacSHA512AndAES_128加密過程當中用的加密密碼.

PBE是基於密碼的加密算法,密碼和祕鑰相比有什麼好處呢?好處就是好記…

PBE加密流程以下
1.
密碼加鹽

2.
密碼加鹽結果作摘要獲取祕鑰

3.
用祕鑰對稱加密原文,而後和鹽拼在一塊兒獲得密文

PBE解密流程以下
1.
從密文獲取鹽

2.
密碼+鹽摘要獲取祕鑰

3.
密文經過祕鑰解密獲取原文

再來看PBEWithHmacSHA512AndAES_128,名字就是加密過程當中用的具體算法

PBE是指用的是PBE加密算法


HmacSHA512是指摘要算法,用於獲取祕鑰


AES_128是對稱加密算法

jasypt-spring-boot-starter原理

先從spring.factories文件入手查看自動配置類

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.ulisesbocchio.jasyptspringboot.JasyptSpringBootAutoConfiguration

JasyptSpringBootAutoConfiguration配置僅僅使用@Import註解引入另外一個配置類EnableEncryptablePropertiesConfiguration.
@Configuration@Import({EnableEncryptablePropertiesConfiguration.class})public class JasyptSpringBootAutoConfiguration { public JasyptSpringBootAutoConfiguration() { }}
從配置類EnableEncryptablePropertiesConfiguration能夠看到有兩個操做

1.@Import了EncryptablePropertyResolverConfiguration.class, CachingConfiguration.class

2.註冊了一個BeanFactoryPostProcessor -> EnableEncryptablePropertiesBeanFactoryPostProcessor

@Configuration@Import({EncryptablePropertyResolverConfiguration.class, CachingConfiguration.class})public class EnableEncryptablePropertiesConfiguration {    private static final Logger log = LoggerFactory.getLogger(EnableEncryptablePropertiesConfiguration.class);    public EnableEncryptablePropertiesConfiguration() {    }    @Bean    public static EnableEncryptablePropertiesBeanFactoryPostProcessor enableEncryptablePropertySourcesPostProcessor(ConfigurableEnvironment environment) {        boolean proxyPropertySources = (Boolean)environment.getProperty("jasypt.encryptor.proxy-property-sources", Boolean.TYPE, false);        InterceptionMode interceptionMode = proxyPropertySources ? InterceptionMode.PROXY : InterceptionMode.WRAPPER;        return new EnableEncryptablePropertiesBeanFactoryPostProcessor(environment, interceptionMode);    }}

先看EncryptablePropertyResolverConfiguration.class

lazyEncryptablePropertyDetector這裏有配置文件中ENC()寫法的出處.從名稱來看是用來找到哪些配置須要解密.

從代碼來看,不必定非得用ENC()把密文包起來, 也能夠經過配置來指定其餘前綴和後綴

jasypt.encryptor.property.prefixjasypt.encryptor.property.suffix    @Bean(        name = {"lazyEncryptablePropertyDetector"}    )    public EncryptablePropertyDetector encryptablePropertyDetector(EncryptablePropertyResolverConfiguration.EnvCopy envCopy, BeanFactory bf) {        String prefix = envCopy.get().resolveRequiredPlaceholders("${jasypt.encryptor.property.prefix:ENC(}");        String suffix = envCopy.get().resolveRequiredPlaceholders("${jasypt.encryptor.property.suffix:)}");        String customDetectorBeanName = envCopy.get().resolveRequiredPlaceholders(DETECTOR_BEAN_PLACEHOLDER);        boolean isCustom = envCopy.get().containsProperty("jasypt.encryptor.property.detector-bean");        return new DefaultLazyPropertyDetector(prefix, suffix, customDetectorBeanName, isCustom, bf);    }

另外還配置了不少bean,先記住這兩個重要的bean.帶着疑問日後看.

lazyEncryptablePropertyResolver 加密屬性解析器


lazyEncryptablePropertyFilter 加密屬性過濾器

@Bean(        name = {"lazyEncryptablePropertyResolver"}    )    public EncryptablePropertyResolver encryptablePropertyResolver(@Qualifier("lazyEncryptablePropertyDetector") EncryptablePropertyDetector propertyDetector, @Qualifier("lazyJasyptStringEncryptor") StringEncryptor encryptor, BeanFactory bf, EncryptablePropertyResolverConfiguration.EnvCopy envCopy, ConfigurableEnvironment environment) {        String customResolverBeanName = envCopy.get().resolveRequiredPlaceholders(RESOLVER_BEAN_PLACEHOLDER);        boolean isCustom = envCopy.get().containsProperty("jasypt.encryptor.property.resolver-bean");        return new DefaultLazyPropertyResolver(propertyDetector, encryptor, customResolverBeanName, isCustom, bf, environment);    }    @Bean(        name = {"lazyEncryptablePropertyFilter"}    )    public EncryptablePropertyFilter encryptablePropertyFilter(EncryptablePropertyResolverConfiguration.EnvCopy envCopy, ConfigurableBeanFactory bf, @Qualifier("configPropsSingleton") Singleton<JasyptEncryptorConfigurationProperties> configProps) {        String customFilterBeanName = envCopy.get().resolveRequiredPlaceholders(FILTER_BEAN_PLACEHOLDER);        boolean isCustom = envCopy.get().containsProperty("jasypt.encryptor.property.filter-bean");        FilterConfigurationProperties filterConfig = ((JasyptEncryptorConfigurationProperties)configProps.get()).getProperty().getFilter();        return new DefaultLazyPropertyFilter(filterConfig.getIncludeSources(), filterConfig.getExcludeSources(), filterConfig.getIncludeNames(), filterConfig.getExcludeNames(), customFilterBeanName, isCustom, bf);    }

再看EnableEncryptablePropertiesBeanFactoryPostProcessor這個類
1.
是一個BeanFactoryPostProcessor

2.
實現了Ordered,是最低優先級,會在其餘BeanFactoryPostProcessor執行以後再執行

3.
postProcessBeanFactory方法中獲取了上面提到的兩個重要的bean, lazyEncryptablePropertyResolver lazyEncryptablePropertyFilter

4.
從environment中獲取了PropertySources

5.
調用工具類進行轉換PropertySources, 也就是把密文轉換爲原文

public class EnableEncryptablePropertiesBeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered {        // ignore some code    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {        LOG.info("Post-processing PropertySource instances");        EncryptablePropertyResolver propertyResolver = (EncryptablePropertyResolver)beanFactory.getBean("lazyEncryptablePropertyResolver", EncryptablePropertyResolver.class);        EncryptablePropertyFilter propertyFilter = (EncryptablePropertyFilter)beanFactory.getBean("lazyEncryptablePropertyFilter", EncryptablePropertyFilter.class);        MutablePropertySources propSources = this.environment.getPropertySources();        EncryptablePropertySourceConverter.convertPropertySources(this.interceptionMode, propertyResolver, propertyFilter, propSources);    }    public int getOrder() {        return 2147483547;    }}

再看工具類EncryptablePropertySourceConverter

1.過濾全部已是EncryptablePropertySource的PropertySource

2.轉換爲EncryptablePropertySource

3.用EncryptablePropertySource從PropertySources中替換原PropertySource

public static void convertPropertySources(InterceptionMode interceptionMode, EncryptablePropertyResolver propertyResolver, EncryptablePropertyFilter propertyFilter, MutablePropertySources propSources) {        ((List)StreamSupport.stream(propSources.spliterator(), false).filter((ps) -> {            return !(ps instanceof EncryptablePropertySource);        }).map((ps) -> {            return makeEncryptable(interceptionMode, propertyResolver, propertyFilter, ps);        }).collect(Collectors.toList())).forEach((ps) -> {            propSources.replace(ps.getName(), ps);        });    }

關鍵方法在makeEncryptable中,調用鏈路很長, 這裏選取一條鏈路跟一下
1.
.ulisesbocchio.jasyptspringboot.EncryptablePropertySourceConverter#makeEncryptable

2.
com.ulisesbocchio.jasyptspringboot.EncryptablePropertySourceConverter#convertPropertySource

3.
com.ulisesbocchio.jasyptspringboot.EncryptablePropertySourceConverter#proxyPropertySource

4.
com.ulisesbocchio.jasyptspringboot.aop.EncryptablePropertySourceMethodInterceptor#invoke

5.
com.ulisesbocchio.jasyptspringboot.caching.CachingDelegateEncryptablePropertySource#getProperty

6.
com.ulisesbocchio.jasyptspringboot.EncryptablePropertySource#getProperty()

看到最後豁然開朗,發現就是用的最開始配置的DefaultLazyPropertyResolver進行密文解析.

直接看最終的實現 DefaultPropertyResolver
1.據lazyEncryptablePropertyDetector過濾須要解密的配置
2.用lazyEncryptablePropertyDetector去掉前綴後綴
3.替換佔位符
4.解密

public String resolvePropertyValue(String value) {        Optional var10000 = Optional.ofNullable(value);        Environment var10001 = this.environment;        var10001.getClass();        var10000 = var10000.map(var10001::resolveRequiredPlaceholders);        EncryptablePropertyDetector var2 = this.detector;        var2.getClass();        return (String)var10000.filter(var2::isEncrypted).map((resolvedValue) -> {            try {                String unwrappedProperty = this.detector.unwrapEncryptedValue(resolvedValue.trim());                String resolvedProperty = this.environment.resolveRequiredPlaceholders(unwrappedProperty);                return this.encryptor.decrypt(resolvedProperty);            } catch (EncryptionOperationNotPossibleException var5) {                throw new DecryptionException("Unable to decrypt: " + value + ". Decryption of Properties failed,  make sure encryption/decryption passwords match", var5);            }        }).orElse(value);    }

解惑

1.加密配置文件可否使用摘要算法,例如md5?

不能, 配置文件加密是須要解密的,例如數據庫鏈接信息加密,若是不解密,springboot程序沒法讀取到真正的數據庫鏈接信息,也就沒法創建鏈接.

2.加密配置文件可否直接使用對稱加密,不用PBE?

能夠, PBE的好處就是密碼好記.

3.jasypt.encryptor.password能夠泄漏嗎?

不能, 泄漏了等於沒有加密.

4.例子中jasypt.encryptor.password配置在配置文件中不就等於泄漏了嗎?

是這樣的,須要在流程上進行控制.springboot打包時千萬不要把jasypt.encryptor.password打入jar包內.

在公司具體的流程多是這樣的:

運維人員持有jasypt.encryptor.password,加密原文得到密文


運維人員將密文發給開發人員


開發人員在配置文件中只配置密文,不配置jasypt.encryptor.password


運維人員啓動應用時再配置jasypt.encryptor.password

若是有其餘疑惑歡迎留言提問, 另外因爲做者水平有限不免有疏漏, 歡迎留言糾錯。

相關文章
相關標籤/搜索