公司需要将系统配置信息中的敏感信息独立存放。
现有系统采用Spring Cloud Config提供配置信息,其中敏感信息主要是Db配置,分解本次需求:
(1)数据库配置信息分离(主要是Db信息)。
(2)原有Config Server功能继续可用。
(3)对特定环境(这里是仿真环境-Demo、生产环境)可以定制配置信息。
思路有如下几种:
(1)Spring Aop 拦截Config Server中配置返回方法,改写方法返回值。
(2)Spring Aop 拦截Config Server中读取配置方法,读取更多文件源。
(3)Filter拦截Config Server中数据返回方法,改写方法返回值。
其中:
方法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方便性的简单处理方案就这样可以投入使用了。