Spring Boot - 配置信息後處理

最近在作項目的過程當中,PSS提出配置文件中相似數據庫鏈接須要的用戶名、密碼等敏感信息須要加密處理(以前一直是明文的)。git

爲了快速完成任務,網上搜刮到jasypt包,也有相應的starter,使用方法能夠參考bloggithub

可是仍是想具體弄清楚背後的實現。偶然看到Spring Boot中有個EnvironmentPostProcessor接口。看名字,它的實現類應該在配置文件加載完和Spring容器開始初始化以前起做用。這樣的話,咱們就能夠實現該接口用來定製化配置信息,包括解密。spring

話很少說,show code,數據庫

 1 @Component
 2 public class DecryptAESConfigProcessor implements EnvironmentPostProcessor {
 3 
 4     private static short INDEX = 0;
 5     private static String ITEM_FORMAT = "spring.config.decrypt-items[%d]";
 6     private static Pattern PATTERN = Pattern.compile("AES\\((.+)\\)");
 7     private static StringBuffer SB = new StringBuffer();
 8 
 9     @Override
10     public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
11         MutablePropertySources propertySources = environment.getPropertySources();
12         for (PropertySource propertySource: propertySources) {
13             if (propertySource instanceof OriginTrackedMapPropertySource){
14                 INDEX = 0;
15                 OriginTrackedMapPropertySource otmps = (OriginTrackedMapPropertySource)propertySource;
16                 //System.out.println("property name = " + otmps.getName());
17                 Map<String, Object> source = otmps.getSource();
18                 String secretSalt = source.getOrDefault("spring.config.secret-salt", "").toString();
19                 if (!"".equals(secretSalt)){
20                     String salt = CommonUtil.decrypt(secretSalt, "sns");
21                     while (INDEX > -1){
22                         String item = String.format(ITEM_FORMAT, INDEX);
23                         if (source.containsKey(item)){
24                             String itemValue = source.get(item).toString();
25                             String propertyValue = source.getOrDefault(itemValue, "").toString();
26                             Matcher matcher = PATTERN.matcher(propertyValue);
27                             boolean findAES = false;
28                             while (matcher.find()){
29                                 //decrypt each AES()
30                                 findAES = true;
31                                 String decryptStr = CommonUtil.decrypt(matcher.group(1), salt);
32                                 matcher.appendReplacement(SB, decryptStr);
33                             }
34                             if (!findAES){
35                                 //decrypt entire item
36                                 source.put(itemValue, CommonUtil.decrypt(propertyValue, salt));
37                             } else {
38                                 matcher.appendTail(SB);
39                                 source.put(itemValue, SB.toString());
40                             }
41                             SB.delete(0, SB.length());
42                             INDEX++;
43                         } else {
44                             INDEX = -1;
45                         }
46                     }
47                 }
48             }
49         }
50 
51     }
52 }

注意:須要將DecryptAESConfigProcessor聲明到spring.factories中。

org.springframework.boot.env.EnvironmentPostProcessor=\
org.chris.springboot.config_encrypt.config.DecryptAESConfigProcessor
  
public class CommonUtil {

    private static final String KEY_ALGORITHM = "AES";
    private static final String DEFAULT_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";
    private static Cipher CIPHER;
    private static KeyGenerator KEY_GENERATOR;

    static {
        try {
            CIPHER = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
            KEY_GENERATOR = KeyGenerator.getInstance(KEY_ALGORITHM);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        }
    }


    /**
     * Decrypt by AES
     * @param content
     * @param salt
     * @return
     */
    public static String decrypt(String content, String salt) {
        if (Objects.nonNull(content)) {
            try {
                byte[] decrypted = Base64.getDecoder().decode(content.getBytes("UTF-8"));
                CIPHER.init(Cipher.DECRYPT_MODE, getSecretKey(salt));
                return new String(CIPHER.doFinal(decrypted));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * Encrypt by AES
     * @param content
     * @param salt
     * @return
     */
    public static String encrypt(String content, String salt) {
        if (Objects.nonNull(content)) {
            try {
                CIPHER.init(Cipher.ENCRYPT_MODE, getSecretKey(salt));
                byte[] encrypted = CIPHER.doFinal(content.getBytes("UTF-8"));
                return Base64.getEncoder().encodeToString(encrypted);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * Generate encrypted salt
     * @param salt
     * @return
     */
    private static SecretKeySpec getSecretKey(final String salt) {
        KEY_GENERATOR.init(128, new SecureRandom(salt.getBytes()));
        SecretKey secretKey = KEY_GENERATOR.generateKey();
        return new SecretKeySpec(secretKey.getEncoded(), KEY_ALGORITHM);
    }
}

最後,配置文件中須要有相似如下配置項springboot

 

解密規則以下,app

1. 採用AES加密解密;dom

2. 在配置文件中ide

 (1) 經過spring.config.decrypt-items指定須要解密的配置spring-boot

 (2) 經過spring.config.secret-salt指定AES的key(最好加密)post

3. 若是須要解密的配置項中存在AES()模式的字符串,將會解密 () 中的內容,不然解密整個配置項

相關文章
相關標籤/搜索