Quarkus中ConfigSourceInterceptor的加密配置實(shí)現(xiàn)
前言
加密配置是一個(gè)很常見(jiàn)的需求,在spring boot生態(tài)中,已經(jīng)有非常多的第三方starter實(shí)現(xiàn)了,博主所在公司也有這種強(qiáng)制要求,一些敏感配置信息必須加密,比如第三方賬號(hào),數(shù)據(jù)庫(kù)密碼等等。所以研究了下怎么在Quarkus中實(shí)現(xiàn)類(lèi)似的配置加密功能。在前文 Quarkus集成apollo配置中心 中,已經(jīng)有介紹過(guò)Quarkus中的配置架構(gòu)了,配置加密功能也是基于smallrye-config來(lái)實(shí)現(xiàn)。
Eclipse MicroProfile Config:https://github.com/eclipse/microprofile-config/
smallrye-config:https://github.com/smallrye/smallrye-config
配置攔截器 ConfigSourceInterceptor
在實(shí)現(xiàn)功能前,先看下smallrye-config1.8版本新增的配置攔截器功能。ConfigSourceInterceptor攔截器定義如下:
public interface ConfigSourceInterceptor extends Serializable {
ConfigValue getValue(ConfigSourceInterceptorContext context, String name);
//省略、、、
}實(shí)現(xiàn)這個(gè)接口,可以在配置加載的時(shí)候通過(guò)context拿到當(dāng)前配置的值,然后進(jìn)行任意邏輯操作。
攔截器是通過(guò)java.util.ServiceLoader機(jī)制加載的,可以通過(guò)提供名為io.smallrye.config.ConfigSourceInterceptor的文件進(jìn)行注冊(cè),該資源META-INF/services/io.smallrye.config.ConfigSourceInterceptor包含完全限定的ConfigSourceInterceptor實(shí)現(xiàn)類(lèi)名稱(chēng)作為其內(nèi)容。
前文 Quarkus集成apollo配置中心 中,我們已了解Quarkus的配置基于Eclipse MicroProfile Config的規(guī)范和smallrye-config的實(shí)現(xiàn),但是ConfigSourceInterceptor的接口設(shè)計(jì)卻沒(méi)有包含在MicroProfile Config的配置規(guī)范中,smallrye團(tuán)隊(duì)正在努力參與規(guī)范的制定,所以后期這個(gè)接口很有可能會(huì)遷移到 MicroProfile Config包中,不過(guò)目前來(lái)看,你可以放心的使用smallrye-config1.8版本體驗(yàn)配置攔截器功能
內(nèi)置的實(shí)現(xiàn)
smallrye-config內(nèi)置了如下配置攔截器實(shí)現(xiàn):
RelocateConfigSourceInterceptor
ProfileConfigSourceInterceptor
ExpressionConfigSourceInterceptor
FallbackConfigSourceInterceptor
LoggingConfigSourceInterceptor
SecretKeyConfigSourceInterceptor
默認(rèn)情況下,并非每個(gè)攔截器都已注冊(cè)。只有ProfileConfigSourceInterceptor, ExpressionConfigSourceInterceptor、SecretKeyConfigSourceInterceptor默認(rèn)已注冊(cè)。
其他攔截器需要通過(guò)ServiceLoader機(jī)制進(jìn)行手動(dòng)注冊(cè)。配置中的${}表達(dá)式功能正是ExpressionConfigSourceInterceptor來(lái)實(shí)現(xiàn)的
加密配置實(shí)現(xiàn)
基于ConfigSourceInterceptor的機(jī)制,實(shí)現(xiàn)一個(gè)加密的攔截器,在配置時(shí),標(biāo)記需要被解密的配置,在應(yīng)用啟動(dòng)時(shí),攔截配置加載,做解密處理即可。這里使用了AES加解密算法,將aesKey配置在配置文件中,將vi向量直接寫(xiě)死在代碼里,這樣,即使別人拿到了你的完整配置,不知道vi向量值,也無(wú)法解密。
ConfigSourceInterceptor實(shí)現(xiàn)類(lèi)可以通過(guò)標(biāo)準(zhǔn)javax.annotation.Priority 注釋指定優(yōu)先級(jí)。如果未明確指定優(yōu)先級(jí),則采用io.smallrye.config.Priorities.APPLICATION默認(rèn)優(yōu)先級(jí)值 。指定優(yōu)先級(jí)時(shí),value值越小,優(yōu)先級(jí)越高,這里指定為PLATFORM早期攔截,代碼如下:
/**
* 1、使用方式為 正常配置值的前面拼接Encrypt=>字符串,如
* quarkus.datasource.password = Encrypt=>xxxx
* 2、配置解密的aeskey值,如
* config.encrypt.aeskey = 11111111111111111
*
* @author kl : http://kailing.pub
* @version 1.0
* @date 2020/7/10 9:46
*/
//value 值越低優(yōu)先級(jí)越高
@Priority(value = Priorities.PLATFORM)
public class EncryptConfigInterceptor implements ConfigSourceInterceptor {
private static final String CONFIG_ENCRYPT_KEY = "config.encrypt.aeskey";
private static final int AES_KEY_LENGTH = 16;
/**
* 需要加密值的前綴標(biāo)記
*/
private static final String ENCRYPT_PREFIX_NAME = "Encrypt=>";
/**
* AES加密模式
*/
private static final String AES_MODE = "AES/CBC/PKCS5Padding";
/**
* AES的iv向量值
*/
private static final String AES_IV = "1234567890123456";
@Override
public ConfigValue getValue(ConfigSourceInterceptorContext context, String name) {
ConfigValue config = context.proceed(name);
if (config != null && config.getValue().startsWith(ENCRYPT_PREFIX_NAME)) {
String encryptValue = config.getValue().replace(ENCRYPT_PREFIX_NAME, "");
String aesKey = context.proceed(CONFIG_ENCRYPT_KEY).getValue();
String value = AesEncyptUtil.decrypt(encryptValue, aesKey);
return config.withValue(value);
}
return config;
}
public static void main(String[] args) {
System.out.println("加密后的配置:"+ AesEncyptUtil.encrypt("office#123", "1111111111111111"));
}
static class AesEncyptUtil{
public static Cipher getCipher(int mode, String key) {
if (key == null || key.length() != AES_KEY_LENGTH) {
throw new RuntimeException("config.encrypt.key不能為空,且長(zhǎng)度為16位");
}
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes(), "AES");
//使用CBC模式,需要一個(gè)向量iv,可增加加密算法的強(qiáng)度
IvParameterSpec iv = new IvParameterSpec(AES_IV.getBytes());
Cipher cipher = null;
try {
cipher = Cipher.getInstance(AES_MODE);
cipher.init(mode, skeySpec, iv);
} catch (InvalidKeyException | InvalidAlgorithmParameterException | NoSuchPaddingException | NoSuchAlgorithmException e) {
e.printStackTrace();
}
return cipher;
}
/**
* AES加密函數(shù)
* @param plaintext 被加密的字符串
* @param key AES key
* @return 加密后的值
*/
public static String encrypt(final Object plaintext, String key) {
if (null == plaintext) {
return null;
}
byte[] encrypted = new byte[0];
try {
Cipher encryptCipher = getCipher(Cipher.ENCRYPT_MODE, key);
encrypted = encryptCipher.doFinal(String.valueOf(plaintext).getBytes(StandardCharsets.UTF_8));
} catch (IllegalBlockSizeException | BadPaddingException e) {
e.printStackTrace();
}
//此處使用BASE64做轉(zhuǎn)碼。
return Base64.getEncoder().encodeToString(encrypted);
}
/**
* AES 解密函數(shù)
*
* @param ciphertext 被解密的字符串
* @param key AES key
* @return 解密后的值
*/
public static String decrypt(final String ciphertext, String key) {
if (null == ciphertext) {
return null;
}
try {
Cipher decryptCipher = getCipher(Cipher.DECRYPT_MODE, key);
//先用base64解密
byte[] encrypted1 = Base64.getDecoder().decode(ciphertext);
byte[] original = decryptCipher.doFinal(encrypted1);
return new String(original, StandardCharsets.UTF_8);
} catch (Exception ex) {
ex.printStackTrace();
return null;
}
}
}
}記得將完整的類(lèi)名寫(xiě)入到META-INF/services/io.smallrye.config.ConfigSourceInterceptor這個(gè)文件中。使用時(shí)先配置好加密的key,在application.properties中添加如下配置:
config.encrypt.aeskey = xxxxxxxxxxxxxx
配置值一定要16位,然后將需要加密的值,使用AesEncyptUtil.encrypt(final Object plaintext, String key)方法先得到加密的值,然后做如下配置,以數(shù)據(jù)庫(kù)密碼為例:
quarkus.datasource.username=mobile_office quarkus.datasource.password=Encrypt=>/8wYwbxokEleEZzT4niJew==
使用Encrypt=>標(biāo)記了這個(gè)值是加密的,應(yīng)用程序加載時(shí)會(huì)被攔截到,然后做解密處理
結(jié)語(yǔ)
總的來(lái)說(shuō),Quarkus中使用的一些api設(shè)計(jì)是非常優(yōu)秀的的,通過(guò)預(yù)留的這種擴(kuò)展機(jī)制,可以非常輕松的實(shí)現(xiàn)擴(kuò)展功能。
以上就是Quarkus中ConfigSourceInterceptor的加密配置實(shí)現(xiàn)的詳細(xì)內(nèi)容,更多關(guān)于Quarkus中ConfigSourceInterceptor加密的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java java.lang.InstantiationException異常案例詳解
這篇文章主要介紹了Java java.lang.InstantiationException異常案例詳解,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08
使用feign發(fā)送http請(qǐng)求解析報(bào)錯(cuò)的問(wèn)題
這篇文章主要介紹了使用feign發(fā)送http請(qǐng)求解析報(bào)錯(cuò)的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03
Mybatis把返回結(jié)果封裝成map類(lèi)型的實(shí)現(xiàn)
本文主要介紹了Mybatis把返回結(jié)果封裝成map類(lèi)型的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-03-03
SpringBoot-RestTemplate實(shí)現(xiàn)調(diào)用第三方API的方式
RestTemplate?是由?Spring?提供的一個(gè)?HTTP?請(qǐng)求工具,它提供了常見(jiàn)的REST請(qǐng)求方案的模版,例如?GET?請(qǐng)求、POST?請(qǐng)求、PUT?請(qǐng)求、DELETE?請(qǐng)求以及一些通用的請(qǐng)求執(zhí)行方法?exchange?以及?execute,下面看下SpringBoot?RestTemplate調(diào)用第三方API的方式2022-12-12
sprintboot使用spring-security包,緩存內(nèi)存與redis共存方式
這篇文章主要介紹了sprintboot使用spring-security包,緩存內(nèi)存與redis共存方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10
Java實(shí)現(xiàn)同步枚舉類(lèi)數(shù)據(jù)到數(shù)據(jù)庫(kù)
這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)同步枚舉類(lèi)數(shù)據(jù)到數(shù)據(jù)庫(kù),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-08-08

