好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

Quarkus中ConfigSourceInterceptor的加密配置实现

前言

加密配置是一个很常见的需求,在spring boot生态中,已经有非常多的第三方starter实现了,博主所在公司也有这种强制要求,一些敏感配置信息必须加密,比如第三方账号,数据库密码等等。所以研究了下怎么在Quarkus中实现类似的配置加密功能。在前文  Quarkus集成apollo配置中心  中,已经有介绍过Quarkus中的配置架构了,配置加密功能也是基于smallrye-config来实现。

Eclipse MicroProfile Config: https://github测试数据/eclipse/microprofile-config/

smallrye-config: https://github测试数据/smallrye/smallrye-config

配置拦截器 ConfigSourceInterceptor

在实现功能前,先看下smallrye-config1.8版本新增的配置拦截器功能。ConfigSourceInterceptor拦截器定义如下:

?

1

2

3

4

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

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

/**

  * 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中添加如下配置:

?

1

config.encrypt.aeskey = xxxxxxxxxxxxxx

配置值一定要16位,然后将需要加密的值,使用AesEncyptUtil.encrypt(final Object plaintext, String key)方法先得到加密的值,然后做如下配置,以数据库密码为例:

?

1

2

quarkus.datasource.username=mobile_office

quarkus.datasource.password=Encrypt=>/8wYwbxokEleEZzT4niJew==

使用Encrypt=>标记了这个值是加密的,应用程序加载时会被拦截到,然后做解密处理

结语

总的来说,Quarkus中使用的一些api设计是非常优秀的的,通过预留的这种扩展机制,可以非常轻松的实现扩展功能。

以上就是Quarkus中ConfigSourceInterceptor的加密配置实现的详细内容,更多关于Quarkus中ConfigSourceInterceptor加密的资料请关注其它相关文章!

原文链接:http://HdhCmsTestkailing.pub/article/index/arcid/289.html

查看更多关于Quarkus中ConfigSourceInterceptor的加密配置实现的详细内容...

  阅读:25次