在 properties 文件中一般有數據庫的密碼等重要信息,這些密碼都是明文的話是很危險的。如下面 properties 文件:
- application.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://123.153.230.165:3306/lxh?useUnicode=true&characterEncoding=utf-8
jdbc.user=zzz
jdbc.password=1234242
有兩種解決方法:
- 先將密碼加密後寫入 properties 文件中,在初始化時讀取 properties 文件的時候再解密。
- 將密碼明文寫入 properties 文件中,在初始化時讀取完 properties 文件的時候將此 properties 文件需要加密的字段加密,其實也就是讀取文件全部內容,再寫回此文件中。
下面是第二種方法的思路:
1.自定義類繼承 PropertyPlaceholderConfigurer
需要在此類構造函數裏:
- load 此 application.properties 文件內容作爲 Properties 對象。
- 再調用 this.setProperties(props); 後可以對此 Properties 對象的鍵值作修改,具體是體現在重寫 PropertyPlaceholderConfigurer 的 convertProperties() 方法。
- convertProperties(Properties props) 方法:判斷每個鍵值是否有要加密的數據,若有的話則把標識記爲需要重寫文件,最後會調用 doEncrypt() 方法加密文件。當然如果讀進來本身就是加密的,那此值還需要解密存在內存中才能正確連接到數據庫,只是文件不需要加密而已。
public class EncryptPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer {
private Set<String> encKeys; // 要加密的字段
private Map<String, String> encMap = new HashMap<>(); // 存儲加密的字段與加密後的值
private Resource defaultResource; // 默認配置文件路徑
private Resource locationResource; // 外置配置文件路徑
private String encoding;
private Resource resource;
public EncryptPropertyPlaceholderConfigurer(Resource defaultPath, Resource locationPath, String encoding)
throws IOException {
// resource = new ClassPathResource("db.properties");
this.logger.info("——————Initializing Properties using "
+ EncryptPropertyPlaceholderConfigurer.class.getSimpleName() + "——————");
this.defaultResource = defaultPath;
this.locationResource = locationPath;
this.setFileEncoding(encoding);
// this.ignoreUnresolvablePlaceholders = true; // 不可有多個類繼承PropertyPlaceholderConfigurer
Properties props = null;
if (locationPath == null || !locationPath.isReadable()) {
resource = defaultResource;
} else {
resource = locationResource;
}
props = PropertiesLoaderUtils.loadProperties(new EncodedResource(resource, encoding)); // 加載文件
this.setProperties(props); // 父類方法
}
@Override
protected void convertProperties(Properties props) {
boolean isSave = false; // 判斷是否需要加密
Enumeration<?> propertyNames = props.propertyNames();
while (propertyNames.hasMoreElements()) {
String propertyName = (String) propertyNames.nextElement();
String propertyValue = props.getProperty(propertyName);
System.out.println(propertyName + "=" + propertyValue);
if (encKeys.contains(propertyName)) {
encMap.put(propertyName, propertyValue);
if (propertyValue.startsWith("{enc}")) {
String convertedValue = convertProperty(propertyName, propertyValue); // 解密
props.setProperty(propertyName, convertedValue);
} else {
// 加密
// encValues.put(propertyName, encryptValue(propertyValue));
isSave = true;
}
} else {
props.setProperty(propertyName, propertyValue);
}
}
// 是否加密文件
if (isSave) {
this.doEncrypt();
}
}
/*
* 其實就是讀出文件內容,再寫回去
*/
private void doEncrypt() {
boolean isSave = false;
List<String> newLines = new ArrayList<>();
try {
BufferedReader br = new BufferedReader(new InputStreamReader(this.resource.getInputStream()));
String line = null;
while ((line = br.readLine()) != null) {
String[] str = line.split("=");
if (str.length > 1 && encMap.containsKey(str[0].trim())) {
String value = encMap.get(str[0].trim());
if (!value.startsWith("{enc}")) {
// 此行沒加密則加密
line = str[0].trim() + "=" + encryptValue(value);
isSave = true;
}
}
newLines.add(line);
}
br.close();
if (isSave) {
BufferedWriter bw = new BufferedWriter(new FileWriter(this.resource.getFile()));
for (String string : newLines) {
bw.write(string);
bw.newLine();
}
bw.flush();
bw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 加密數據
*/
private String encryptValue(String propertyValue) {
return "{enc}" + propertyValue;
}
/**
* 解密數據
*/
@Override
protected String convertProperty(String propertyName, String propertyValue) {
if (propertyValue.startsWith("{enc}")) {
int idx = propertyValue.indexOf("}");
return propertyValue.substring(idx + 1);
}
return propertyValue;
}
public Set<String> getEncKeys() {
return encKeys;
}
public void setEncKeys(Set<String> encKeys) {
this.encKeys = encKeys;
}
public Map<String, String> getEncMap() {
return encMap;
}
public void setEncMap(Map<String, String> encValues) {
this.encMap = encValues;
}
public String getEncoding() {
return encoding;
}
public void setEncoding(String encoding) {
this.encoding = encoding;
}
public Resource getResource() {
return resource;
}
public void setResource(Resource resource) {
this.resource = resource;
}
public static void main(String[] args) {
Resource resource = new ClassPathResource("a2pplication.properties");
System.out.println(resource.isReadable());
}
}
2. 將此類交由 Spring 容器來管理
- encKeys:加密的屬性,這裏只加密 jdbc.password 屬性的值
<!-- 系統底層會創建一個Properties對象 並將此對象交給spring管理 <util:properties id="cfg" location="classpath:db.properties"
/> -->
<bean id="EncryptPropertyPlaceholderConfigurer"
class="com.qhf.config.EncryptPropertyPlaceholderConfigurer">
<constructor-arg index="0" value="classpath:/application.properties" />
<constructor-arg index="1" value="" />
<constructor-arg index="2" value="UTF-8" />
<property name="encKeys">
<set>
<value>jdbc.password</value>
</set>
</property>
</bean>
3.結果
在發佈服務運行後 application.properties 會加密 jdbc.password 的值