通過 PropertyPlaceholderConfigurer 來加解密數據

在 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

有兩種解決方法:

  1. 先將密碼加密後寫入 properties 文件中,在初始化時讀取 properties 文件的時候再解密。
  2. 將密碼明文寫入 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 的值 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章