Let’s take a look at this configuration:
spring: # 数据库链接配置 datasource: url: jdbc:mysql://xx.xx.xx.xx:3306/database driver-class-name: com.mysql.cj.jdbc.Driver username: root password: "123456"
The corresponding value of our above configuration spring.datasource.password
is 123456
, it is inappropriate to put such sensitive information directly in the configuration file. What we have to do is to change the corresponding value to an encrypted ciphertext, as follows:
spring: # 数据库链接配置 datasource: url: jdbc:mysql://xx.xx.xx.xx:3306/database driver-class-name: com.mysql.cj.jdbc.Driver username: root password: "AES(DzANBAhBWXxZqAOsagIBCoaw8FV4gYRbid7G70UEM24=)"
In this case, even if the configuration file is intentionally If someone takes it, they don’t know what the real database password is, so they cannot pose a risk of infringement to the project;
In order to implement this function, we need to understand The relevant extension points of Spring
and the corresponding data encryption and decryption knowledge, let’s first take a look at which extension point of Spring
we should cut through;
We want to intercept the configuration data If so, it can be handled by implementing a custom BeanFactoryPostProcessor
:
public class PropertySourcePostProcessor implements BeanFactoryPostProcessor { private ConfigurableEnvironment environment; public PropertySourcePostProcessor(ConfigurableEnvironment environment) { this.environment = environment; } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { // 从ConfigurableEnvironment中取出所有的配置数据 MutablePropertySources propertySources = this.environment.getPropertySources(); propertySources.stream() // 过滤不需要包装的对象 .filter(s -> !noWrapPropertySource(s)) // 包装所有的PropertySource .map(s -> new EncryPropertySource(s)) .collect(Collectors.toList()) // 替换掉propertySources中的PropertySource .forEach(wrap -> propertySources.replace(wrap.getName(), wrap)); } private boolean noWrapPropertySource(PropertySource propertySource) { return propertySource instanceof EncryPropertySource || StringUtils.equalsAny(propertySource.getClass().getName(), "org.springframework.core.env.PropertySource$StubPropertySource", "org.springframework.boot.context.properties.source.ConfigurationPropertySourcesPropertySource"); } }
The basic principle is analyzed as follows:
1. Take out all # through ConfigurableEnvironment
##PropertySource and traverse in sequence;
PropertySource that does not meet our requirements, because
PropertySource has many subclasses, not all of them
PropertySource instances all meet our packaging requirements;
PropertySource that meets the requirements, which is actually a static proxy;
PropertySource instance with the packaged
PropertySource;
PropertySource When getting the value, do some custom operations, such as decrypting the ciphertext password;
Asymmetric encryptionWe must avoid putting the ciphertext and the private key in the same place;
jar tool specifically for this need, it is
jasypt, we can go to
maven Find the relevant package in the warehouse:
<dependency> <groupId>com.github.ulisesbocchio</groupId> <artifactId>jasypt-spring-boot-starter</artifactId> <version>3.0.5</version> </dependency>Its implementation principle is actually what we described above, by customizing
BeanFactoryPostProcessor to
ConfigurableEnvironment
PropertySourceThe instance is intercepted and packaged, and a layer of decryption operation is performed on the implementation of the packaging class, thus realizing the decryption of the ciphertext password;
jasypt: encryptor: # 密钥 password: "" property: # 密文前缀 prefix: "" # 密文后缀 suffix: ""In the above configuration,
jasypt.encryptor.password must be configured Yes, this is the encryption and decryption key. The default encryption algorithm is
PBEWITHHMACSHA512ANDAES_256; in addition,
jasypt.encryptor.property.prefix and
jasypt.encryptor.property.suffix are the ciphertext prefix and ciphertext suffix respectively, which are used to mark the ciphertext that needs to be decrypted. If not configured, the default ciphertext prefix is
ENC(, the ciphertext suffix is
); By default, our ciphertext is as follows:
spring: datasource: password: "ENC(DzANBAhBWXxZqAOsagIBCoaw8FV4gYRbid7G70UEM24=)"Another point to note is that
jasypt.encryptor.password cannot be placed together with the ciphertext, we It can be passed through system properties, command line parameters or environment variables in the project;
jasypt cannot meet our needs For project requirements, we can also implement encryption and decryption ourselves:
@Bean("jasyptStringEncryptor") public StringEncryptor jasyptStringEncryptor(){ return new StringEncryptor() { @Override public String encrypt(String s) { // TODO 加密 return null; } @Override public String decrypt(String s) { // TODO 解密 return null; } }; }Note that our
BeanName must be set to
jasyptStringEncryptor by default, otherwise it will not take effect. If If you want to change this
BeanName, you can also customize the
BeanName corresponding to the
StringEncryptor instance by modifying this configuration parameter:
jasypt: encryptor: # 自定义StringEncryptor的BeanName bean: ""How to generate the password TextThe operation of generating ciphertext still requires encrypting and generating it by calling the
StringEncryptor instance. You can refer to the following code:
@Component public class StringEncryptorUtil{ @Autowired private StringEncryptor encryptor; public void encrypt(){ String result = encryptor.encrypt("123456"); System.out.println(result); } }After all, the operation that requires encryption only requires It is executed once in the project life cycle, so we only need to simply write a tool class and call it.
The above is the detailed content of How Springboot implements encryption of plaintext passwords in configuration files. For more information, please follow other related articles on the PHP Chinese website!