Spring Cloud 自定義ConfigServer 解決敏感信息存儲問題

公司須要將系統配置信息中的敏感信息獨立存放。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方便性的簡單處理方案就這樣能夠投入使用了。

相關文章
相關標籤/搜索