太強(qiáng)了!Spring Boot 配置參數(shù)名可控、值加密可查,全鏈路守護(hù)配置安全
不少企業(yè)上線前對(duì)配置文件有嚴(yán)格審查規(guī)則:禁止出現(xiàn) username
、password
等敏感字段名,更不能出現(xiàn)明文密碼。這種規(guī)則雖然出發(fā)點(diǎn)是安全,但卻給開發(fā)帶來了不少困擾:
- 字段名被屏蔽,程序無法讀取
- 值不能加密,容易泄漏
- 改配置影響啟動(dòng),難以排查
如何兼顧審查要求、代碼規(guī)范與安全性?本文提供一種優(yōu)雅高效的解決方案:
自定義字段名 + 值使用國(guó)密 SM4 加密 + 啟動(dòng)監(jiān)聽器解密還原
項(xiàng)目結(jié)構(gòu)
/src
└── main
├── java
│ └── com
│ └── icoderoad
│ ├── listener # 啟動(dòng)解密監(jiān)聽器
│ ├── utils # 加解密工具(SM4)
│ └── Application.java # 主啟動(dòng)類
└── resources
└── application.properties # 自定義字段 + 加密值
示例配置(字段脫敏 + SM4 加密值)
原始配置(上線不允許)
spring.datasource.url=jdbc:mysql://localhost:3306/study2
spring.datasource.username=root
spring.datasource.password=root
安全配置(字段名脫敏 + 值加密)
spring.datasource.dbu=lUMr5GMPQUghyqRZpH8U3rfWcWoOFl2F1lmuX9u2tNc=
spring.datasource.usr=gAvu3DuUOecT43AjtA1Rmw==
spring.datasource.pwd=gAvu3DuUOecT43AjtA1Rmw==
解密后示例值
- URL:
jdbc:mysql://localhost:3306/study2
- 用戶名/密碼:
root
上述密文通過 SM4 加密生成(工具類示例見下方)
SM4 加解密工具類(國(guó)密算法)
國(guó)密算法采用 BouncyCastle 實(shí)現(xiàn),確保兼容性與安全性。
引入依賴(Maven)
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.70</version>
</dependency>
工具類實(shí)現(xiàn)
// com/icoderoad/utils/Sm4Util.java
package com.icoderoad.utils;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.engines.SM4Engine;
import org.bouncycastle.crypto.modes.ECBBlockCipher;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.paddings.PKCS7Padding;
import org.bouncycastle.crypto.params.KeyParameter;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class Sm4Util {
private static final String DEFAULT_KEY = "1234567890abcdef"; // 16字節(jié)國(guó)密Key
public static String encrypt(String plainText) throws Exception {
return encrypt(plainText, DEFAULT_KEY);
}
public static String decrypt(String cipherText) throws Exception {
return decrypt(cipherText, DEFAULT_KEY);
}
public static String encrypt(String plainText, String key) throws Exception {
PaddedBufferedBlockCipher cipher = createCipher(true, key.getBytes(StandardCharsets.UTF_8));
byte[] input = plainText.getBytes(StandardCharsets.UTF_8);
byte[] output = new byte[cipher.getOutputSize(input.length)];
int len = cipher.processBytes(input, 0, input.length, output, 0);
len += cipher.doFinal(output, len);
byte[] encrypted = new byte[len];
System.arraycopy(output, 0, encrypted, 0, len);
return Base64.getEncoder().encodeToString(encrypted);
}
public static String decrypt(String base64Cipher, String key) throws Exception {
byte[] input = Base64.getDecoder().decode(base64Cipher);
PaddedBufferedBlockCipher cipher = createCipher(false, key.getBytes(StandardCharsets.UTF_8));
byte[] output = new byte[cipher.getOutputSize(input.length)];
int len = cipher.processBytes(input, 0, input.length, output, 0);
len += cipher.doFinal(output, len);
byte[] decrypted = new byte[len];
System.arraycopy(output, 0, decrypted, 0, len);
return new String(decrypted, StandardCharsets.UTF_8);
}
private static PaddedBufferedBlockCipher createCipher(boolean forEncryption, byte[] key) {
SM4Engine engine = new SM4Engine();
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new ECBBlockCipher(engine), new PKCS7Padding());
cipher.init(forEncryption, new KeyParameter(key));
return cipher;
}
public static void main(String[] args) throws Exception {
String url = "jdbc:mysql://localhost:3306/study2";
String encryptedUrl = encrypt(url);
System.out.println("Encrypted URL: " + encryptedUrl);
System.out.println("Decrypted: " + decrypt(encryptedUrl));
String user = "root";
System.out.println("Encrypted user: " + encrypt(user));
}
}
啟動(dòng)監(jiān)聽器還原明文配置項(xiàng)
// com/icoderoad/listener/EnvironmentDecryptListener.java
package com.icoderoad.listener;
import com.icoderoad.utils.Sm4Util;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.env.ConfigurableEnvironment;
public class EnvironmentDecryptListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {
@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment env = event.getEnvironment();
try {
String url = env.getProperty("spring.datasource.dbu");
String usr = env.getProperty("spring.datasource.usr");
String pwd = env.getProperty("spring.datasource.pwd");
System.setProperty("spring.datasource.url", Sm4Util.decrypt(url));
System.setProperty("spring.datasource.username", Sm4Util.decrypt(usr));
System.setProperty("spring.datasource.password", Sm4Util.decrypt(pwd));
} catch (Exception e) {
System.err.println("配置解密失敗: " + e.getMessage());
}
}
}
啟動(dòng)類注冊(cè)監(jiān)聽器
// com/icoderoad/Application.java
package com.icoderoad;
import com.icoderoad.listener.EnvironmentDecryptListener;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class)
.listeners(new EnvironmentDecryptListener())
.run(args);
}
}
結(jié)語:一站式配置安全守護(hù)方案
通過以上實(shí)戰(zhàn)方案,我們實(shí)現(xiàn)了配置安全的三重防護(hù):
- 字段名自定義:繞過審查敏感關(guān)鍵字
- 值加密保護(hù):使用國(guó)密 SM4 算法替代常見 AES,更符合監(jiān)管要求
- 啟動(dòng)自動(dòng)還原:無侵入式接入 Spring Boot 項(xiàng)目
此方案不僅適用于數(shù)據(jù)源連接,還可擴(kuò)展用于 Redis、MQ、API Token 等敏感配置項(xiàng)的保護(hù)。
建議將加密工具提取為開發(fā)工具類,加密后再錄入配置文件,運(yùn)行階段解密即可,無需人工干預(yù)。