首頁 > 軟體

Quarkus中ConfigSourceInterceptor的加密設定實現

2022-02-23 19:02:40

前言

加密設定是一個很常見的需求,在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加密的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com