Quarkus中ConfigSourceInterceptor的加密配置實現
前言
加密配置是一個很常見的需求,在spring boot生態中,已經有非常多的第三方starter實現瞭,博主所在公司也有這種強制要求,一些敏感配置信息必須加密,比如第三方賬號,數據庫密碼等等。所以研究瞭下怎麼在Quarkus中實現類似的配置加密功能。在前文 Quarkus集成apollo配置中心 中,已經有介紹過Quarkus中的配置架構瞭,配置加密功能也是基於smallrye-config來實現。
Eclipse MicroProfile Config:https://github.com/eclipse/microprofile-config/
smallrye-config:https://github.com/smallrye/smallrye-config
配置攔截器 ConfigSourceInterceptor
在實現功能前,先看下smallrye-config1.8版本新增的配置攔截器功能。ConfigSourceInterceptor攔截器定義如下:
public interface ConfigSourceInterceptor extends Serializable { ConfigValue getValue(ConfigSourceInterceptorContext context, String name); //省略、、、 }
實現這個接口,可以在配置加載的時候通過context拿到當前配置的值,然後進行任意邏輯操作。
攔截器是通過java.util.ServiceLoader機制加載的,可以通過提供名為io.smallrye.config.ConfigSourceInterceptor的文件進行註冊,該資源META-INF/services/io.smallrye.config.ConfigSourceInterceptor包含完全限定的ConfigSourceInterceptor實現類名稱作為其內容。
前文 Quarkus集成apollo配置中心 中,我們已瞭解Quarkus的配置基於Eclipse MicroProfile Config的規范和smallrye-config的實現,但是ConfigSourceInterceptor的接口設計卻沒有包含在MicroProfile Config的配置規范中,smallrye團隊正在努力參與規范的制定,所以後期這個接口很有可能會遷移到 MicroProfile Config包中,不過目前來看,你可以放心的使用smallrye-config1.8版本體驗配置攔截器功能
內置的實現
smallrye-config內置瞭如下配置攔截器實現:
RelocateConfigSourceInterceptor
ProfileConfigSourceInterceptor
ExpressionConfigSourceInterceptor
FallbackConfigSourceInterceptor
LoggingConfigSourceInterceptor
SecretKeyConfigSourceInterceptor
默認情況下,並非每個攔截器都已註冊。隻有ProfileConfigSourceInterceptor, ExpressionConfigSourceInterceptor、SecretKeyConfigSourceInterceptor默認已註冊。
其他攔截器需要通過ServiceLoader機制進行手動註冊。配置中的${}表達式功能正是ExpressionConfigSourceInterceptor來實現的
加密配置實現
基於ConfigSourceInterceptor的機制,實現一個加密的攔截器,在配置時,標記需要被解密的配置,在應用啟動時,攔截配置加載,做解密處理即可。這裡使用瞭AES加解密算法,將aesKey配置在配置文件中,將vi向量直接寫死在代碼裡,這樣,即使別人拿到瞭你的完整配置,不知道vi向量值,也無法解密。
ConfigSourceInterceptor實現類可以通過標準javax.annotation.Priority 註釋指定優先級。如果未明確指定優先級,則采用io.smallrye.config.Priorities.APPLICATION默認優先級值 。指定優先級時,value值越小,優先級越高,這裡指定為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 值越低優先級越高 @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; /** * 需要加密值的前綴標記 */ 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不能為空,且長度為16位"); } SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes(), "AES"); //使用CBC模式,需要一個向量iv,可增加加密算法的強度 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加密函數 * @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做轉碼。 return Base64.getEncoder().encodeToString(encrypted); } /** * AES 解密函數 * * @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; } } } }
記得將完整的類名寫入到META-INF/services/io.smallrye.config.ConfigSourceInterceptor這個文件中。使用時先配置好加密的key,在application.properties中添加如下配置:
config.encrypt.aeskey = xxxxxxxxxxxxxx
配置值一定要16位,然後將需要加密的值,使用AesEncyptUtil.encrypt(final Object plaintext, String key)方法先得到加密的值,然後做如下配置,以數據庫密碼為例:
quarkus.datasource.username=mobile_office quarkus.datasource.password=Encrypt=>/8wYwbxokEleEZzT4niJew==
使用Encrypt=>標記瞭這個值是加密的,應用程序加載時會被攔截到,然後做解密處理
結語
總的來說,Quarkus中使用的一些api設計是非常優秀的的,通過預留的這種擴展機制,可以非常輕松的實現擴展功能。
以上就是Quarkus中ConfigSourceInterceptor的加密配置實現的詳細內容,更多關於Quarkus中ConfigSourceInterceptor加密的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- SpringBoot快速遷移至Quarkus的方法步驟
- java實現AES 32位加密解密的方案
- Python使用Crypto庫實現加密解密的示例詳解
- python實現AES算法及AES-CFB8加解密源碼
- 詳解PHP接口簽名驗證