最近在做项目的过程中,PSS提出配置文件中类似数据库连接需要的用户名、密码等敏感信息需要加密处理(之前一直是明文的)。
为了快速完成任务,网上搜刮到jasypt包,也有相应的starter,使用方法可以参考blog
但是还是想具体弄清楚背后的实现。偶然看到Spring Boot中有个EnvironmentPostProcessor接口。看名字,它的实现类应该在配置文件加载完和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); } }
最后,配置文件中需要有类似以下配置项
解密规则如下,
1. 采用AES加密解密;
2. 在配置文件中
(1) 通过spring.config.decrypt-items指定需要解密的配置
(2) 通过spring.config.secret-salt指定AES的key(最好加密)
3. 如果需要解密的配置项中存在AES()模式的字符串,将会解密 () 中的内容,否则解密整个配置项