0、参考文章
1、配置分析
使用jasypt-spring-boot-starter来进行配置文件密码加密
走个流程,分析一波他的自动配置过程
<!--用于给配置文件中的密码等加密-->
<!-- https://mvnrepository.com/artifact/com.github.ulisesbocchio/jasypt-spring-boot-starter -->
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
看到这个依赖很简洁哇,没有额外的依赖
分析自动配置原理
流程就是从自动配置类xxxAutoConfiguration-->自动配置类里面注册或者导入了组件 -->xxxProperties里面有属性值-->绑定了配置文件中以xxx.xxxx开头的配置项
//自动配置类
@Configuration
@Import(EnableEncryptablePropertiesConfiguration.class)
public class JasyptSpringBootAutoConfiguration {
}
//导入的组件的 EnableEncryptablePropertiesConfiguration
//这里还是没有出现配置绑定,继续找,从导入的这两个类中看,先看EncryptablePropertyResolverConfiguration
@Configuration
@Import({EncryptablePropertyResolverConfiguration.class, CachingConfiguration.class})
@Slf4j
public class EnableEncryptablePropertiesConfiguration {
//里面只注册了这一个组件
@Bean
public static EnableEncryptablePropertiesBeanFactoryPostProcessor enableEncryptablePropertySourcesPostProcessor(final ConfigurableEnvironment environment, EncryptablePropertySourceConverter converter) {
return new EnableEncryptablePropertiesBeanFactoryPostProcessor(environment, converter);
}
}
EncryptablePropertyResolverConfiguration,这个类里面注册了很多组件,其中有一个组件为:
@Configuration
public class EncryptablePropertyResolverConfiguration {
//其中有一个bean为,看一下这个单例的泛型JasyptEncryptorConfigurationProperties
//这个名字比较明显了,就是配置类,所以点进去
@Bean(name = CONFIG_SINGLETON)
public Singleton<JasyptEncryptorConfigurationProperties> configProps(
final EnvCopy envCopy) {
return new Singleton<>(() -> JasyptEncryptorConfigurationProperties.bindConfigProps(envCopy.get()));
}
}
JasyptEncryptorConfigurationProperties:激动人心吧,终于找到了配置绑定类。可以看到绑定了配置文件jasypt.encryptor开头的属性,所以直接在application.yaml文件中该属性就可以了
@ConfigurationProperties(prefix = "jasypt.encryptor", ignoreUnknownFields = true)
@Data
public class JasyptEncryptorConfigurationProperties {
//这里简单列一下几个属性
/**
* Specify the name of bean to override jasypt-spring-boot's default properties based
* {@link org.jasypt.encryption.StringEncryptor}. Default Value is {@code jasyptStringEncryptor}.
*/
//指定加密器对应的bean名称
private String bean = "jasyptStringEncryptor";
//里面默认的算法为:
private String algorithm = "PBEWITHHMACSHA512ANDAES_256";
//密匙没有默认值,需要我们在配置文件中指定
private String password;
//字符串输出的编码格式
private String stringOutputType = "base64";
//嵌套的配置属性,在jasypt.encryptor基础上再加上.property
@NestedConfigurationProperty
private PropertyConfigurationProperties property = new PropertyConfigurationProperties();
@Data
public static class PropertyConfigurationProperties {
//前缀
private String prefix = "ENC(";
//后缀
private String suffix = ")";
}
}
那么我们看一下生成密匙的工具类
在EncryptablePropertyResolverConfiguration中进行的注册,所以我们使用的时候直接@Autowired。
返回的加密器是DefaultLazyEncryptor类型,简单看一下他的构造器参数:
envCopy.get()获取了配置文件中的参数customEncryptorBeanName用户自定义加密器的bean名称isCustom用户是否自定义了加密器,jasypt.encryptor.bean默认有值为jasyptStringEncryptor,也可以在配置文件中指定bfBean工厂
@Configuration
public class EncryptablePropertyResolverConfiguration {
private static final String ENCRYPTOR_BEAN_PROPERTY = "jasypt.encryptor.bean";
private static final String ENCRYPTOR_BEAN_PLACEHOLDER = String.format("${%s:jasyptStringEncryptor}", ENCRYPTOR_BEAN_PROPERTY);
private static final String ENCRYPTOR_BEAN_NAME = "lazyJasyptStringEncryptor";
@Bean(name = ENCRYPTOR_BEAN_NAME)
public StringEncryptor stringEncryptor(
final EnvCopy envCopy,
final BeanFactory bf) {
final String customEncryptorBeanName = envCopy.get().resolveRequiredPlaceholders(ENCRYPTOR_BEAN_PLACEHOLDER);
final boolean isCustom = envCopy.get().containsProperty(ENCRYPTOR_BEAN_PROPERTY);
return new DefaultLazyEncryptor(envCopy.get(), customEncryptorBeanName, isCustom, bf);
}
}
2、开始使用
使用的大致流程是,我们根据加密规则生成明文的密码,然后用这些密码代替原来的明文。那么系统启动的时候会根据我们的配置进行解密。
2.1 配置文件
jasypt:
encryptor:
# 加密密匙
password: zylai
在这个配置文件中指定密匙
2.2 加密明文
我们直接在测试类中获取配置文件中的明文,然后加密
@SpringBootTest
public class EncryptTest {
//加密的工具
@Autowired
private StringEncryptor stringEncryptor;
@Autowired
private ApplicationContext applicationContext;
@Test
public void getEncryptedPwd(){
Environment environment = applicationContext.getBean(Environment.class);
//获取配置文件中的明文密码
String mysqlPassword = environment.getProperty("spring.datasource.password");
String redisPassword = environment.getProperty("spring.redis.password");
System.out.println("明文信息为如下:");
System.out.println("mysqlPassword:"+mysqlPassword+"\nredisPassword:"+redisPassword);
System.out.println("========加密之后的信息如下===============");
System.out.println("mysqlPassword:"+encrypt(mysqlPassword));
System.out.println("redisPassword:"+encrypt(redisPassword));
}
public String encrypt(String str){
return stringEncryptor.encrypt(str);
}
}
2.3 修改配置文件
将上面的加密字符串代替配置文件中的明文即可,注意需要将加密的结果放到ENC()里面,为什么是ENC呢?这个是默认的设置,程序启动时会去解析这个括号里面的密码。
spring.redis.password=ENC(T5os7CbvjZ3N33Ns96ikjfSIFByMRhxhBNb+G6qWLhbn3vpZ0miP+HKfRQDiyhts)
spring.datasource.password=ENC(6eAFfiW1eGeYH+enb1A0rmcsI+Jw5hFZe5Earb2Fu3SrwaDQaXbZ8DASDTbjI5R7)
其实我们可以自定义ENC(),只需要添加属性即可
jasypt:
encryptor:
# 加密密匙
password: zylai
property:
prefix: ZYLai(
suffix: )
在第一部分自动配置分析中,从配置绑定类的属性就可以看出我们可以自定义
接下来直接启动程序验证一下即可,然后在主启动类中,我们再去获取一下这些密码看看程序是否能够自动解密(只要程序启动成功一般就是成功了)
@SpringBootApplication
public class Boot04WebAdminApplication {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(Boot04WebAdminApplication.class, args);
Environment environment = run.getBean(Environment.class);
//查看一下加密的密匙,测试用
System.out.println(environment.getProperty("jasypt.encryptor.password"));
//在程序启动时,jasypt会解密ENC()中的内容
String mysqlPassword = environment.getProperty("spring.datasource.password");
String mysqlUsername = environment.getProperty("spring.datasource.username");
System.out.println("测试数据加密,解密之后的mysql账号密码为:");
System.out.println("mysqlPassword:"+mysqlPassword+"\nmysqlUsername:"+mysqlUsername);
}
}
大功告成!
2.4 启动程序的日志
同时在启动的日志中可以看到,如果没有进行配置值,就会使用默认的
这个日志是在加密器DefaultLazyEncryptor中执行的:
@Slf4j
public class DefaultLazyEncryptor implements StringEncryptor {
private final Singleton<StringEncryptor> singleton;
public DefaultLazyEncryptor(final ConfigurableEnvironment e, final String customEncryptorBeanName, boolean isCustom, final BeanFactory bf) {
singleton = new Singleton<>(() ->
Optional.of(customEncryptorBeanName)
.filter(bf::containsBean)
.map(name -> (StringEncryptor) bf.getBean(name))
.map(tap(bean -> log.info("Found Custom Encryptor Bean {} with name: {}", bean, customEncryptorBeanName)))
.orElseGet(() -> {
if (isCustom) {
throw new IllegalStateException(String.format("String Encryptor custom Bean not found with name '%s'", customEncryptorBeanName));
}
log.info("String Encryptor custom Bean not found with name '{}'. Initializing Default String Encryptor", customEncryptorBeanName);
return createDefault(e);
}));
}
public DefaultLazyEncryptor(final ConfigurableEnvironment e) {
singleton = new Singleton<>(() -> createDefault(e));
}
private StringEncryptor createDefault(ConfigurableEnvironment e) {
return new StringEncryptorBuilder(JasyptEncryptorConfigurationProperties.bindConfigProps(e), "jasypt.encryptor").build();
}
@Override
public String encrypt(final String message) {
return singleton.get().encrypt(message);
}
@Override
public String decrypt(final String encryptedMessage) {
return singleton.get().decrypt(encryptedMessage);
}
}
那么如何自定义这个属性呢?在第四部分详细展开
3、配置文件加密了?但又没完全加密
虽然在配置文件中没有密码等敏感信息了,但是他又加密的密匙jasypt.encryptor.password哇,别人仍然可以拿着这个密匙进行解密来获取你的敏感信息。所以密匙可以不写在配置文件中!!!
三种方式
方式一:直接作为程序启动时的命令行参数来带入
java -jar yourproject.jar --jasypt.encryptor.password=zylai
- 方式二:直接作为程序启动时的应用环境变量来带入
java -Djasypt.encryptor.password=zyali -jar yourproject.jar
复制代码
对于已经完成的项目进行加密可以使用以上两种方式,但对于个人正在开发的项目,可以试试使用系统的环境变量。
- 方式三:甚至可以作为系统环境变量的方式来带入
我们提前设置好系统环境变量JASYPT_ENCRYPTOR_PASSWORD = zylai,则直接在Spring Boot的项目配置文件中做如下配置即可:
jasypt.encryptor.password=${JASYPT_ENCRYPTOR_PASSWORD}
4、自定义功能-分析源码
加密器分析
在配置类JasyptEncryptorConfigurationProperties中可以看到指定了加密器对应的bean名称
/**
* Specify the name of bean to override jasypt-spring-boot's default properties based
* {@link org.jasypt.encryption.StringEncryptor}. Default Value is {@code jasyptStringEncryptor}.
*/
//指定加密器对应的bean名称
private String bean = "jasyptStringEncryptor";
官方的注释说指定bean名称来重写默认的加密器,所以我们自己配置的加密器的名称最好也是这个名字,或者在配置文件中显式指定这个名字
那么这个bean是在哪里注册的呢?我们再回到EncryptablePropertyResolverConfiguration,这个类里面注册了很多组件
值得注意的是,这个StringEncryptor接口bean的名称为lazyJasyptStringEncryptor,并不是jasyptStringEncryptor,但是他的返回值(即实现类)是一个DefaultLazyEncryptor,其构造器中指定了这个bean的名称
分析一下这个方法的返回值内容new DefaultLazyEncryptor(envCopy.get(), customEncryptorBeanName, isCustom, bf);
customEncryptorBeanName指定了用户定义加密器的bean名称,名称为String.format("${%s:jasyptStringEncryptor}", ENCRYPTOR_BEAN_PROPERTY),就是从ENCRYPTOR_BEAN_PROPERTY中获取,如果没有值就使用jasyptStringEncryptor。isCustom:用户是否自定义了,判断方式为:envCopy.get().containsProperty(ENCRYPTOR_BEAN_PROPERTY);,在配置类中有默认值,我们也可以在配置文件中指定
@Configuration
public class EncryptablePropertyResolverConfiguration {
private static final String ENCRYPTOR_BEAN_PROPERTY = "jasypt.encryptor.bean";
private static final String ENCRYPTOR_BEAN_PLACEHOLDER = String.format("${%s:jasyptStringEncryptor}", ENCRYPTOR_BEAN_PROPERTY);
private static final String ENCRYPTOR_BEAN_NAME = "lazyJasyptStringEncryptor";
@Bean(name = ENCRYPTOR_BEAN_NAME)
public StringEncryptor stringEncryptor(
final EnvCopy envCopy,
final BeanFactory bf) {
final String customEncryptorBeanName = envCopy.get().resolveRequiredPlaceholders(ENCRYPTOR_BEAN_PLACEHOLDER);
final boolean isCustom = envCopy.get().containsProperty(ENCRYPTOR_BEAN_PROPERTY);
return new DefaultLazyEncryptor(envCopy.get(), customEncryptorBeanName, isCustom, bf);
}
}
我们再看一下DefaultLazyEncryptor构造器的执行为:
@Slf4j
public class DefaultLazyEncryptor implements StringEncryptor {
private final Singleton<StringEncryptor> singleton;
public DefaultLazyEncryptor(final ConfigurableEnvironment e, final String customEncryptorBeanName, boolean isCustom, final BeanFactory bf) {
singleton = new Singleton<>(() ->
Optional.of(customEncryptorBeanName)
.filter(bf::containsBean)
.map(name -> (StringEncryptor) bf.getBean(name))
.map(tap(bean -> log.info("Found Custom Encryptor Bean {} with name: {}", bean, customEncryptorBeanName)))
.orElseGet(() -> {
if (isCustom) {
throw new IllegalStateException(String.format("String Encryptor custom Bean not found with name '%s'", customEncryptorBeanName));
}
log.info("String Encryptor custom Bean not found with name '{}'. Initializing Default String Encryptor", customEncryptorBeanName);
return createDefault(e);
}));
}
public DefaultLazyEncryptor(final ConfigurableEnvironment e) {
singleton = new Singleton<>(() -> createDefault(e));
}
private StringEncryptor createDefault(ConfigurableEnvironment e) {
return new StringEncryptorBuilder(JasyptEncryptorConfigurationProperties.bindConfigProps(e), "jasypt.encryptor").build();
}
@Override
public String encrypt(final String message) {
return singleton.get().encrypt(message);
}
@Override
public String decrypt(final String encryptedMessage) {
return singleton.get().decrypt(encryptedMessage);
}
}
一套分析下来应该也明白了,就是我们可以自定义加密器(bean的返回值类型要是StringEncryptor),加密器bean的默认名称为jasyptStringEncryptor,如果我们自定了名称,需要在配置文件中指定jasypt.encryptor.bean
自定义的加密器
密匙一定要从配置文件中获取,要是自己再写明文,那不是白忙活了吗
@Configuration
public class EncryptorConfig {
@Bean("jasyptStringEncryptor")
public StringEncryptor zylaiEncryptor(@Value("${jasypt.encryptor.password}") String password){
PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
SimpleStringPBEConfig config = new SimpleStringPBEConfig();
config.setPassword(password);
config.setAlgorithm("PBEWITHHMACSHA512ANDAES_256");
config.setKeyObtentionIterations("1000");
config.setPoolSize("1");
// config.setProviderName("zylai");
config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator");
config.setIvGeneratorClassName("org.jasypt.iv.RandomIvGenerator");
config.setStringOutputType("base64");
encryptor.setConfig(config);
return encryptor;
}
}
5、关于解密
是否需要自定义解密器
是否有个问题,我们自定义了加密器,那么解密器是不是也要自己定义一个,来对应加密器?答案是完全不用哈
首先,加密器的接口StringEncryptor,长这个样子。看见没,不仅仅只是加密,也有解密的方法
public interface StringEncryptor {
String encrypt(String var1);
String decrypt(String var1);
}
再看一下如何向容器中注册的解密器:
这个方法和注册加密器的方法几乎一样,支持用户的自定义,返回DefaultLazyPropertyResolver实例,但是他的构造器多了一个参数为encryptor,这个参数就是从容器中获取的@Qualifier(ENCRYPTOR_BEAN_NAME) final StringEncryptor encryptor,哈哈经典的ENCRYPTOR_BEAN_NAME,又回来了吧,所以说加密和解密都是基于容器中名为ENCRYPTOR_BEAN_NAME,类型为StringEncryptor 的组件
@Bean(name = RESOLVER_BEAN_NAME)
public EncryptablePropertyResolver encryptablePropertyResolver(
@Qualifier(DETECTOR_BEAN_NAME) final EncryptablePropertyDetector propertyDetector,
@Qualifier(ENCRYPTOR_BEAN_NAME) final StringEncryptor encryptor, final BeanFactory bf,
final EnvCopy envCopy, final ConfigurableEnvironment environment) {
final String customResolverBeanName = envCopy.get().resolveRequiredPlaceholders(RESOLVER_BEAN_PLACEHOLDER);
final boolean isCustom = envCopy.get().containsProperty(RESOLVER_BEAN_PROPERTY);
return new DefaultLazyPropertyResolver(propertyDetector, encryptor, customResolverBeanName, isCustom, bf, environment);
}
解密的方法
上面看到容器中会注册一个DefaultLazyPropertyResolver组件,构造器中单例最终创建的是DefaultPropertyResolver类型
EncryptablePropertyResolver 接口 就俩实现类,除了这一个就是DefaultPropertyResolver,在DefaultPropertyResolver中真正实现了解密
@Slf4j
public class DefaultLazyPropertyResolver implements EncryptablePropertyResolver {
private Singleton<EncryptablePropertyResolver> singleton;
public DefaultLazyPropertyResolver(EncryptablePropertyDetector propertyDetector, StringEncryptor encryptor, String customResolverBeanName, boolean isCustom, BeanFactory bf, Environment environment) {
singleton = new Singleton<>(() ->
Optional.of(customResolverBeanName)
.filter(bf::containsBean)
.map(name -> (EncryptablePropertyResolver) bf.getBean(name))
.map(tap(bean -> log.info("Found Custom Resolver Bean {} with name: {}", bean, customResolverBeanName)))
.orElseGet(() -> {
if (isCustom) {
throw new IllegalStateException(String.format("Property Resolver custom Bean not found with name '%s'", customResolverBeanName));
}
log.info("Property Resolver custom Bean not found with name '{}'. Initializing Default Property Resolver", customResolverBeanName);
return createDefault(propertyDetector, encryptor, environment);
}));
}
public DefaultLazyPropertyResolver(EncryptablePropertyDetector propertyDetector, StringEncryptor encryptor, Environment environment) {
singleton = new Singleton<>(() -> createDefault(propertyDetector, encryptor, environment));
}
//创建DefaultPropertyResolver对象
private DefaultPropertyResolver createDefault(EncryptablePropertyDetector propertyDetector, StringEncryptor encryptor, Environment environment) {
return new DefaultPropertyResolver(encryptor, propertyDetector, environment);
}
@Override
public String resolvePropertyValue(String value) {
return singleton.get().resolvePropertyValue(value);
}
}
DefaultPropertyResolver类中主要看resolvePropertyValue他会根据StringEncryptor实例中的decrypt方法进行解密,并将解密之后的结果再放回配置环境中,也就有了之前我们看到程序启动之后,在启动类中直接获取配置文件中加密的属性的明文。
public class DefaultPropertyResolver implements EncryptablePropertyResolver {
private final Environment environment;
private StringEncryptor encryptor;
private EncryptablePropertyDetector detector;
public DefaultPropertyResolver(StringEncryptor encryptor, Environment environment) {
this(encryptor, new DefaultPropertyDetector(), environment);
}
public DefaultPropertyResolver(StringEncryptor encryptor, EncryptablePropertyDetector detector, Environment environment) {
this.environment = environment;
Assert.notNull(encryptor, "String encryptor can't be null");
Assert.notNull(detector, "Encryptable Property detector can't be null");
this.encryptor = encryptor;
this.detector = detector;
}
@Override
public String resolvePropertyValue(String value) {
return Optional.ofNullable(value)
.map(environment::resolvePlaceholders)
.filter(detector::isEncrypted)
.map(resolvedValue -> {
try {
String unwrappedProperty = detector.unwrapEncryptedValue(resolvedValue.trim());
String resolvedProperty = environment.resolvePlaceholders(unwrappedProperty);
return encryptor.decrypt(resolvedProperty);
} catch (EncryptionOperationNotPossibleException e) {
throw new DecryptionException("Unable to decrypt: " + value + ". Decryption of Properties failed, make sure encryption/decryption " +
"passwords match", e);
}
})
.orElse(value);
}
}