SpringBoot 配置文件加密
本章將對SpringBoot配置文件中的數(shù)據(jù)加密做自定義開發(fā). 在SpringBoot開發(fā)過程中配置文件是明文存放在Application.yml或者application.properties文件中,這種配置方式會帶來一定的安全隱患,本章將對這個問題提出一個簡單的解決方案。
編碼
首先需要確定一個加密解密方式,本文采用RSA進(jìn)行加密解密,首先編寫加密解密的代碼,注意RSA加密解密需要使用到公鑰和私鑰,公鑰私鑰的生成代碼如下:
public static void generateKey() throws NoSuchAlgorithmException {
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(EncryptionType.RSA);
keyPairGen.initialize(1024, new SecureRandom());
KeyPair keyPair = keyPairGen.generateKeyPair();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); // 得到私鑰
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); // 得到公鑰
String publicKeyString = new String(Base64.encodeBase64(publicKey.getEncoded()));
String privateKeyString = new String(Base64.encodeBase64((privateKey.getEncoded())));
System.out.println("當(dāng)前生成的公鑰= " + publicKeyString);
System.out.println("當(dāng)前生成的私鑰= " + privateKeyString);
}
加密代碼如下:
public static String encrypt(String str, String publicKey) throws NoSuchAlgorithmException, BadPaddingException, IllegalBlockSizeException, NoSuchPaddingException, InvalidKeyException, InvalidKeySpecException {
byte[] decoded = Base64.decodeBase64(publicKey);
RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance(EncryptionType.RSA).generatePublic(new X509EncodedKeySpec(decoded));
Cipher cipher = Cipher.getInstance(EncryptionType.RSA);
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
return Base64.encodeBase64String(cipher.doFinal(str.getBytes(StandardCharsets.UTF_8)));
}
解密代碼如下:
public static String decrypt(String str, String privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, BadPaddingException, IllegalBlockSizeException, InvalidKeyException {
byte[] inputByte = Base64.decodeBase64(str.getBytes(StandardCharsets.UTF_8));
byte[] decoded = Base64.decodeBase64(privateKey);
RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance(EncryptionType.RSA).generatePrivate(new PKCS8EncodedKeySpec(decoded));
Cipher cipher = Cipher.getInstance(EncryptionType.RSA);
cipher.init(Cipher.DECRYPT_MODE, priKey);
return new String(cipher.doFinal(inputByte));
}
上述代碼為基本的加密解密工具,加下來需要在配置文件中確定哪些配置是需要進(jìn)行解密的,本例將采用自定義前綴+后綴的方式進(jìn)行匹配,前綴為PWD[,后綴為],如果在配置文件中屬性值是以前綴后綴包裹的那么這個數(shù)據(jù)會被進(jìn)行解密操作,
通過前文的加密工具可以先進(jìn)行一次密碼加密加密原文為1234qwer,公鑰:
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCBOkkkvjbOQ6UTCo8U4bRC/EcEtxz8haHg6lueM3NBbH3eIT7kfwQFOqj1h1qPGcQNeyn4vxzMWBAKzSQehjqVBL7/8GN7EZ7TEaUuWO+8qsuZnOdrztX7bNKACnks+SelmtbrbnFKUMAq2c2mS0o1V6iwyRxJYLGaHGXnz4KSkwIDAQAB
私鑰:
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAIE6SSS+Ns5DpRMKjxThtEL8RwS3HPyFoeDqW54zc0Fsfd4hPuR/BAU6qPWHWo8ZxA17Kfi/HMxYEArNJB6GOpUEvv/wY3sRntMRpS5Y77yqy5mc52vO1fts0oAKeSz5J6Wa1utucUpQwCrZzaZLSjVXqLDJHElgsZocZefPgpKTAgMBAAECgYAFaCDjTqoQWzgu4cQ2xXK7Ur7N7bNixVyOgn+u0MxDsnxZrN5qxP2wElI7Y5xgXF2diseoxqY3zn9tVEPsmwUcY73naoosx9V8oExgT/BUkZYIzj1ei08zOr984zl3dbFcxOCRvqywXj9FAAGx1mhmCzFCIauJg3aX0S9mt5/CwQJBAMYZsmMQ9owoXZuSclKVRfMHFpAPhQlcBM4xadhX0IRYATgNTxpESmcCoGWvyw3bvieNJyC9Njx6X4FJ2EZUzhECQQCm/2IM5MlsCwyKtME5RPFna2hSqYU80UzkNfdiyMokcU2JUI4Fhigog4ol0GFMiMBsHIjS+cJiAwNbIsq5rsJjAkA94yVBobkETFACHBwvBIdXxy0bUF3lcKPnrrQ8bCKuVbf7xNyjfhYoXD+zxNmQuMeNH6HLrpDVD/3qLCGuxyuhAkAiLPl/8gJWnhw+9qbkdXuB0rVS1WZy/9JgkblpHc5Gjt9zTo0CDGaDhAftnSuMYiAe/+fwZTSmoj85k3ExdtZJAkEArJuG/NWY9HP4p7jtZX9rMokyB3517v7HQdJKBDIlOzseRC/roCvU8LQ/URDFBUqXCRgedxgW+0ZmKFf4xeawqw==
加密結(jié)果為:
PWD[bMw8oqC/ma31JqF0DCuf5QWqSFRMigYw3fMBIIIfJ85vnmNnFbH9IcJfUHgbSmNHeITffToODwAygy4vKdzu6o1i1UQOd8w4nPKhnVJCLKqW5jmc3Yw+FkTIRBp63NJWzECVnRHqEK+bTxPMa1gfKql/2U45XxqeDSZOEXGeA+E=]
得到這個數(shù)據(jù)后將其配置在application.properties文件中,具體內(nèi)容如下
server.port=8080
spring.application.name=test-app
spring.datasource.password=PWD[bMw8oqC/ma31JqF0DCuf5QWqSFRMigYw3fMBIIIfJ85vnmNnFbH9IcJfUHgbSmNHeITffToODwAygy4vKdzu6o1i1UQOd8w4nPKhnVJCLKqW5jmc3Yw+FkTIRBp63NJWzECVnRHqEK+bTxPMa1gfKql/2U45XxqeDSZOEXGeA+E=]
接下來需要進(jìn)一步解決的是如何將這個加密字符串進(jìn)行解密,這里需要使用BeanFactoryPostProcessor接口對環(huán)境變量進(jìn)行修改,具體實現(xiàn)代碼如下:
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
MutablePropertySources propertySources = environment.getPropertySources();
for (PropertySource<?> propertySource : propertySources) {
if (propertySource instanceof OriginTrackedMapPropertySource
) {
OriginTrackedMapPropertySource om = (OriginTrackedMapPropertySource) propertySource;
Map<String, Object> source = om.getSource();
source.forEach((k, v) -> {
String property = environment.getProperty(k);
if (hasPreAndSuf(property)) {
LOG.info("開始處理 k = [{}]", k);
try {
String relay = splitPreAndSuf(property, this.prefix, this.suffix);
String decrypt = RSAEncrypt.decrypt(relay, getPrivateKey(environment));
source.put(k, decrypt);
}
catch (Exception e) {
LOG.error("e = ", e);
}
}
});
}
}
}
處理邏輯如下:
- 提取環(huán)境配置中的所有配置屬性
- 判斷配置屬性是否是OriginTrackedMapPropertySource類型,該類型的數(shù)據(jù)是在application.yaml中的內(nèi)容
- 處理OriginTrackedMapPropertySource對象的value值,如果value包含自定義前綴后綴則進(jìn)行解密
在本例中對于公鑰私鑰以及前綴后綴是允許自定義的,開發(fā)者只需要在配置文件中根據(jù)下面表格進(jìn)行填寫即可
屬性名稱 |
屬性含義 |
默認(rèn)值 |
encryption.prefix |
前綴 |
PWD[ |
encryption.suffix |
后綴 |
] |
encryption.rsa.publicKey |
公鑰 |
|
encryption.rsa.privateKey |
私鑰 |
|
注意:**為了便捷操作開放了配置文件形式的公鑰秘鑰的配置,這部分配置可以在測試環(huán)境中開發(fā)環(huán)境中進(jìn)行使用。**如果需要在生產(chǎn)環(huán)境中使用請使用下面兩種方式:
方式一:在項目資源目錄resources文件夾下創(chuàng)建hf_private_key文件,向文件中填寫如下內(nèi)容
encryption.rsa.privateKey=MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAIE6SSS+Ns5DpRMKjxThtEL8RwS3HPyFoeDqW54zc0Fsfd4hPuR/BAU6qPWHWo8ZxA17Kfi/HMxYEArNJB6GOpUEvv/wY3sRntMRpS5Y77yqy5mc52vO1fts0oAKeSz5J6Wa1utucUpQwCrZzaZLSjVXqLDJHElgsZocZefPgpKTAgMBAAECgYAFaCDjTqoQWzgu4cQ2xXK7Ur7N7bNixVyOgn+u0MxDsnxZrN5qxP2wElI7Y5xgXF2diseoxqY3zn9tVEPsmwUcY73naoosx9V8oExgT/BUkZYIzj1ei08zOr984zl3dbFcxOCRvqywXj9FAAGx1mhmCzFCIauJg3aX0S9mt5/CwQJBAMYZsmMQ9owoXZuSclKVRfMHFpAPhQlcBM4xadhX0IRYATgNTxpESmcCoGWvyw3bvieNJyC9Njx6X4FJ2EZUzhECQQCm/2IM5MlsCwyKtME5RPFna2hSqYU80UzkNfDIyMokcU2JUI4Fhigog4ol0GFMiMBsHIjS+cJiAwNbIsq5rsJjAkA94yVBobkETFACHBwvBIdXxy0bUF3lcKPnrrQ8bCKuVbf7xNyjfhYoXD+zxNmQuMeNH6HLrpDVD/3qLCGuxyuhAkAiLPl/8gJWnhw+9qbkdXuB0rVS1WZy/9JgkblpHc5gjt9zTo0CDGaDhAftnSuMYiAe/+fwZTSmoj85k3ExdtZJAkEArJuG/NWY9HP4p7jtZX9rMokyB3517v7HQdJKBDIlOzseRC/roCvU8LQ/URDFBUqXCRgedxgW+0ZmKFf4xeawqw==
需要將等于號后面的內(nèi)容進(jìn)行修改,替換為項目中的秘鑰
方式二:通過命令行進(jìn)行傳遞
在啟動命令中添加
-Dencryption.rsa.privateKey=MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAIE6SSS+Ns5DpRMKjxThtEL8RwS3HPyFoeDqW54zc0Fsfd4hPuR/BAU6qPWHWo8ZxA17Kfi/HMxYEArNJB6GOpUEvv/wY3sRntMRpS5Y77yqy5mc52vO1fts0oAKeSz5J6Wa1utucUpQwCrZzaZLSjVXqLDJHElgsZocZefPgpKTAgMBAAECgYAFaCDjTqoQWzgu4cQ2xXK7Ur7N7bNixVyOgn+u0MxDsnxZrN5qxP2wElI7Y5xgXF2diseoxqY3zn9tVEPsmwUcY73naoosx9V8oExgT/BUkZYIzj1ei08zOr984zl3dbFcxOCRvqywXj9FAAGx1mhmCzFCIauJg3aX0S9mt5/CwQJBAMYZsmMQ9owoXZuSclKVRfMHFpAPhQlcBM4xadhX0IRYATgNTxpESmcCoGWvyw3bvieNJyC9Njx6X4FJ2EZUzhECQQCm/2IM5MlsCwyKtME5RPFna2hSqYU80UzkNfDIyMokcU2JUI4Fhigog4ol0GFMiMBsHIjS+cJiAwNbIsq5rsJjAkA94yVBobkETFACHBwvBIdXxy0bUF3lcKPnrrQ8bCKuVbf7xNyjfhYoXD+zxNmQuMeNH6HLrpDVD/3qLCGuxyuhAkAiLPl/8gJWnhw+9qbkdXuB0rVS1WZy/9JgkblpHc5gjt9zTo0CDGaDhAftnSuMYiAe/+fwZTSmoj85k3ExdtZJAkEArJuG/NWY9HP4p7jtZX9rMokyB3517v7HQdJKBDIlOzseRC/roCvU8LQ/URDFBUqXCRgedxgW+0ZmKFf4xeawqw==
需要將等號后面的內(nèi)容進(jìn)行修改,替換為項目中的秘鑰
從筆者所經(jīng)歷的角度來看命令行傳遞參數(shù)可能更加安全一些,因為文件和命令行相比命令行需要在生產(chǎn)環(huán)境才可以看到,而文件在倉庫中會存在。
接下來做一個測試,在啟動時輸出spring.datasource.password屬性查看是否是加密前的數(shù)據(jù),測試代碼如下:
@SpringBootApplication
public class App {
@Value("${spring.datasource.password}")
private String dataSourceProperties;
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
@Bean
public ApplicationRunner runner() {
return args -> {
System.out.println(dataSourceProperties);
};
}
}
啟動項目后可以看到控制臺輸出
1234qwer
原文鏈接:
https://my.oschina.net/huifer/blog/4990480
如果覺得本文對你有幫助,可以轉(zhuǎn)發(fā)關(guān)注支持一下