背景
配置中心中的配置往往存在需要加密的配置,比如数据库用户名、密码等。由于项目中同时使用nacos&apollo,所以需要实现一下两种配置中心的自动化解密
1.封装jaspyt
1.1引用jar包
以jaspyt作为解密框架,这里jaspyt没有使用starter的原因是可能各项目springboot版本不同,可能存在兼容性问题。如果是新项目可以考虑使用统一版本的starter实现自动化解密
<dependency>
<groupId>org.jasypt</groupId>
<artifactId>jasypt</artifactId>
<version>1.9.3</version>
</dependency>
1.2增加工具类
从网上找一个简单的工具类:
/**
* 需解密的标记头
*/
private static final String ENCRYPTED_VALUE_PREFIX = "ENC(";
/**
* 需解密的标记尾
*/
private static final String ENCRYPTED_VALUE_SUFFIX = ")";
/**
* 解密密码
*/
public static final String JASYPT_PASSWORD = "xxx";
/**
* 判断是否是 prefixes/suffixes 包裹的属性
*
* @param value
* @return
*/
public static boolean isEncryptedValue(final String value) {
if (value == null) {
return false;
}
final String trimmedValue = value.trim();
return (trimmedValue.startsWith(ENCRYPTED_VALUE_PREFIX) &&
trimmedValue.endsWith(ENCRYPTED_VALUE_SUFFIX));
}
/**
* 如果通过 prefixes/suffixes 包裹的属性,那么返回密文的值;如果没有被包裹,返回原生的值。
*
* @param value
* @return
*/
private static String getInnerEncryptedValue(final String value) {
return value.substring(
ENCRYPTED_VALUE_PREFIX.length(),
(value.length() - ENCRYPTED_VALUE_SUFFIX.length()));
}
/**
* Jasypt生成加密结果
*
* @param password 配置文件中设定的加密密码 jasypt.encryptor.password
* @param value 待加密值
* @return
*/
public static String encryptPwd(String password, String value) {
PooledPBEStringEncryptor encryptOr = new PooledPBEStringEncryptor();
encryptOr.setConfig(cryptOr(password));
return encryptOr.encrypt(value);
}
/**
* 解密
*
* @param password 配置文件中设定的加密密码 jasypt.encryptor.password
* @param value 待解密密文
* @return
*/
public static String decyptPwd(String password, String value) {
PooledPBEStringEncryptor encryptOr = new PooledPBEStringEncryptor();
encryptOr.setConfig(cryptOr(password));
return encryptOr.decrypt(isEncryptedValue(value) ? getInnerEncryptedValue(value) : value);
}
/**
* @param password salt
* @return
*/
public static SimpleStringPBEConfig cryptOr(String password) {
SimpleStringPBEConfig config = new SimpleStringPBEConfig();
config.setPassword(password);
config.setAlgorithm(StandardPBEByteEncryptor.DEFAULT_ALGORITHM);
config.setKeyObtentionIterations("1000");
config.setPoolSize("1");
config.setProviderName(null);
config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator");
config.setStringOutputType("base64");
return config;
}
2.apollo
2.1 引用jar包
使用了apollo的1.3的客户端:
<dependency>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-client</artifactId>
<version>1.3.0</version>
</dependency>
2.2 增加自动化解密功能
经过断点调试,发现apollo更新配置时都会经过这个方法:
修改如下,使用了jaspyt包中的加解密方法,实现了配置在同步后的自动化解密:
Set<Object> keys = newConfigProperties.keySet();
for (Object k : keys) {
String key = k.toString();
String value = newConfigProperties.getProperty(key);
//判断是否是规则密文
if (JasyptUtils.isEncryptedValue(value)) {
try {
// 解密然后重新赋值
String decyptVal = JasyptUtils.decyptPwd(JasyptUtils.JASYPT_PASSWORD, value);
newConfigProperties.setProperty(key, decyptVal);
} catch (Exception e) {
logger.error("错误信息是:{}, 错误堆栈是:{}", ExceptionUtil.getMessage(e),
ExceptionUtil.stacktraceToString(e));
}
}
}
2.3 在apollo中装配自定义模块
需要替换下面的模块实现配置自动解密:
新建一个相同的类,覆盖掉这个工厂类即可。最后通过apollo提供的spi接口com.ctrip.framework.apollo.internals.Injector替换掉这个模块装配器
3.nacos
3.1 引用jar包
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2021.1</version>
</dependency>
3.2增加自动化解密功能
经过断点调试,发现nacos同步配置时都会经过这个方法:
该类是在这个配置器中自动装配的:
经过分析源码得知,这个配置器是通过springcloud上下文启动的,并且没有加@ConditionalOnMissingBean注解,在spring上下文中替换比较麻烦。所以最简便的方法是增加一个同名包和同名bean:
修改方法loadNacosDataIfPresent:
private void loadNacosDataIfPresent(final CompositePropertySource composite,
final String dataId, final String group, String fileExtension,
boolean isRefreshable) {
if (null == dataId || dataId.trim().length() < 1) {
return;
}
if (null == group || group.trim().length() < 1) {
return;
}
NacosPropertySource propertySource = this.loadNacosPropertySource(dataId, group, fileExtension, isRefreshable);
Map<String, Object> source = propertySource.getSource();
for (String key : source.keySet()) {
String value = source.get(key).toString();
if (JasyptUtils.isEncryptedValue(value)) {
try {
String decryptValue = JasyptUtils.decyptPwd(JasyptUtils.JASYPT_PASSWORD, value);
source.put(key, decryptValue);
} catch (Exception e) {
log.error("nacos解密失败!请检察是否包含正确的密文:错误信息是:{}, 错误堆栈是:{}", ExceptionUtil.getMessage(e),
ExceptionUtil.stacktraceToString(e));
}
}
}
this.addFirstPropertySource(composite, propertySource, false);
}