公司須要將系統配置信息中的敏感信息獨立存放。javascript
現有系統採用Spring Cloud Config提供配置信息,其中敏感信息主要是Db配置,分解本次需求:java
(1)數據庫配置信息分離(主要是Db信息)。mysql
(2)原有Config Server功能繼續可用。web
(3)對特定環境(這裏是仿真環境-Demo、生產環境)能夠定製配置信息。spring
思路有以下幾種:sql
(1)Spring Aop 攔截Config Server中配置返回方法,改寫方法返回值。數據庫
(2)Spring Aop 攔截Config Server中讀取配置方法,讀取更多文件源。json
(3)Filter攔截Config Server中數據返回方法,改寫方法返回值。app
其中:ide
方法1與方法3都是對返回結果進行處理,能夠兼容Spring Cloud Config的版本升級,因方法1須要綁定到特定方法,而方法3無需考慮具體方法,耦合性更低。
方法2須要改寫Config Server的代碼,或覆蓋Config Server的Bean,較1和2複雜,且沒法隨這主版本升級,直接pass。
綜合考慮採用方法3,如下是處理與代碼:
公司統一標準,將Db配置信息存儲到Json文件中,所以須要一個解析Json文件的Service:
import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.env.MapPropertySource; import org.springframework.stereotype.Component; import java.io.File; import java.io.IOException; import java.util.Date; import java.util.LinkedHashMap; import java.util.Map; @Component public class JsonSourcePropertiesLoader { @Value("${Config.Json.FileLocation}") String FileLocation; public MapPropertySource load() throws IOException { File jsonFile = new File(FileLocation); final Map<String, Object> source = process(jsonFile); System.out.println(new Date().getTime()); return new MapPropertySource("dbConfigMap",source); } private Map<String, Object> process(final File resourceFile){ Map<String, Object> map = null; try{ map = new ObjectMapper().readValue(resourceFile, LinkedHashMap.class); }catch (IOException e){ e.printStackTrace(); } return map; } }
須要一個類攔截並獲取Config Server在正常處理請求後返回的內容,以便接下來的改寫:
import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintWriter; public class MyResponseWrapper extends HttpServletResponseWrapper { private ByteArrayOutputStream byteArrayOutputStream; private ServletOutputStream servletOutputStream; private PrintWriter printWriter; public MyResponseWrapper(HttpServletResponse response) { super(response); byteArrayOutputStream = new ByteArrayOutputStream(4096); printWriter = new PrintWriter(byteArrayOutputStream); servletOutputStream = new MyServletOutputStream(byteArrayOutputStream); } @Override public PrintWriter getWriter() throws IOException{ return printWriter; } @Override public ServletOutputStream getOutputStream() throws IOException{ return servletOutputStream; } @Override public void flushBuffer() throws IOException{ if(printWriter!=null){ printWriter.flush(); } if(servletOutputStream !=null){ servletOutputStream.flush(); } } @Override public void reset(){ byteArrayOutputStream.reset(); } public byte[] getResult(){ try{ flushBuffer(); }catch (IOException e){ } return byteArrayOutputStream.toByteArray(); } }
對ServletOutputStream的封裝類:
import javax.servlet.ServletOutputStream; import javax.servlet.WriteListener; import java.io.ByteArrayOutputStream; import java.io.IOException; public class MyServletOutputStream extends ServletOutputStream { ByteArrayOutputStream output; public MyServletOutputStream(ByteArrayOutputStream output) { this.output = output; } @Override public void write(int b) throws IOException { output.write(b); } public void write(byte[] data, int offset, int length) { output.write(data, offset, length); } @Override public boolean isReady() { return false; } @Override public void setWriteListener(WriteListener listener) { } }
有了攔截與處理的方法,最後就是把功能組合到攔截器中:
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.fang.microservice.cloud.Loader.JsonSourcePropertiesLoader; import com.fang.microservice.cloud.Wrapper.MyResponseWrapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.web.servlet.ServletComponentScan; import org.springframework.core.env.MapPropertySource; import org.springframework.stereotype.Component; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletResponse; import java.io.*; @Component @ServletComponentScan @WebFilter(urlPatterns = "/{name}/{profiles:.*[^-].*}", filterName = "ConfigServletFilter") public class ConfigAspectFilter implements Filter { @Autowired JsonSourcePropertiesLoader jsonConfig; @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { MyResponseWrapper wrapper = new MyResponseWrapper((HttpServletResponse) response); chain.doFilter(request, wrapper); byte[] respBytes = wrapper.getResult(); String result = Byte2String(respBytes); if (response.getContentType().toLowerCase().contains("application/json")) { if (!result.toLowerCase().contains("datasource.mysql.")) { JSONObject configObj = (JSONObject) JSON.parse(result); JSONArray propertySourcesObjs = configObj.getJSONArray("propertySources"); if (propertySourcesObjs.size() > 0) { JSONObject propertySourcesObj = (JSONObject) propertySourcesObjs.get(0); JSONObject sourceObj = propertySourcesObj.getJSONObject("source"); MapPropertySource mps = jsonConfig.load(); for (String key : mps.getPropertyNames()) { sourceObj.put(key, mps.getProperty(key)); } result = JSON.toJSONString(configObj); } } } response.setContentLength(-1); PrintWriter out = response.getWriter(); out.write(result); out.flush(); out.close(); } public static String Byte2String(byte[] buff) { ByteArrayOutputStream result = new ByteArrayOutputStream(); int length; try { result.write(buff, 0, buff.length); } catch (Exception e) { e.printStackTrace(); } try { return result.toString("UTF-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return ""; } @Override public void destroy() { } }
做爲敏感數據存儲的文件內容:
{ "testDbName":"testDb","userName":"userName","password":"pass"}
測試效果以下:
最後不能忘記特定敏感文件的路徑,須要配置在配置文件中:
Config.Json.FileLocation=/xx/test/test.json
這樣,一個既能夠規避敏感文件,又能不失Config Server方便性的簡單處理方案就這樣能夠投入使用了。