过滤器+正则处理加解密请求响应

727 阅读4分钟

背景

最近在处理用户敏感信息的加密和解密,其中遇到以下问题:

  1. 单个接口处理不好维护
  2. 如果直接在业务中加相关代码的话,耦合比较高
  3. 每个接口请求和响应的数据格式不一样,很难用同一套数据结构处理

调研

整个包加密

就是在请求和响应的时候对请求的数据包进行加解密,这是最快也最暴力的方式,但是会存在传输的数据膨胀,数据的加密因为算法的不同,明文的字符串加密后都会膨胀很多,甚至几倍,这是不可取的,无形之中变相的给服务增加压力,pass.

目标字段加解密

这个是从算法的加解密还是从传输的数据量考虑来说,性价比最高的

实现

思路

  1. 设置目标URL
  2. 先将请求的参数或者响应的内容变成字符串
  3. 用正则表达式去匹配
  4. 截取对应的字符串进行加解密,将结果替换旧的字符串

代码

正则表达式

private final String s = ""name":"(.*?)"|"mobile":"(.*?)"|"account":"(.*?)"|"idcard":"(.*?)"|"newPwd":"(.*?)"|"vcode":"(.*?)"|"validateCode":"(.*?)"";

目标接口

private String[] urlPatterns = {"/base/group/user" ,......};

请求包装类

public class PostHttpServletRequestWrapper extends HttpServletRequestWrapper {

    private byte[] body; //用于保存读取body中数据   

    public PostHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        //读取请求的数据保存到本类当中
        String sessionStream = getRequestBodyStr(request);//读取流中的参数
        body = sessionStream.getBytes(Charset.forName("UTF-8"));
    }

    public String getRequestBodyStr(final ServletRequest request) throws IOException {
        StringBuilder sb = new StringBuilder();
        InputStream inputStream = null;
        BufferedReader reader = null;
        try {
            inputStream = cloneInputStream(request.getInputStream());
            reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
            String line = "";
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }
            if (reader != null) {
                reader.close();
            }
        }
        return sb.toString();
    }

    public InputStream cloneInputStream(ServletInputStream inputStream) throws IOException {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len;
        while ((len = inputStream.read(buffer)) > -1) {
            byteArrayOutputStream.write(buffer, 0, len);
        }
        byteArrayOutputStream.flush();
        InputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        return byteArrayInputStream;
    }

    //覆盖(重写)父类的方法
    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }

    //覆盖(重写)父类的方法
    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream bais = new ByteArrayInputStream(body);
        return new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener readListener) {

            }

            @Override
            public int read() throws IOException {
                return bais.read();
            }
        };
    }

    /**
     * 获取body中的数据
     *
     * @return
     */
    public byte[] getBody() {
        return body;
    }

    /**
     * 把处理后的参数放到body里面
     *
     * @param body
     */
    public void setBody(byte[] body) {
        this.body = body;
    }
} 
public class GetHttpServletRequestWrapper extends HttpServletRequestWrapper{  
    
    // 用于存储请求参数
    private Map<String , String[]> params = new HashMap<String, String[]>();  
    // 构造方法
     public GetHttpServletRequestWrapper(HttpServletRequest request) throws IOException {    
         super(request);
         // 把请求参数添加到我们自己的map当中
         this.params.putAll(request.getParameterMap()); 
     }    
     
     
     /**
      * 添加参数到map中
      * @param extraParams
      */
     public void setParameterMap(Map<String, Object> extraParams) {
         for (Map.Entry<String, Object> entry : extraParams.entrySet()) {
             setParameter(entry.getKey(), entry.getValue());
         }
     }
     
     /**
      * 添加参数到map中
      * @param name 
      * @param value
      */
     public void setParameter(String name, Object value) {
         if (value != null) {
             System.out.println(value);
             if (value instanceof String[]) {
                 params.put(name, (String[]) value);
             } else if (value instanceof String) {
                 params.put(name, new String[]{(String) value});
             } else {
                 params.put(name, new String[]{String.valueOf(value)});
             }
         }
     }
     
     /**
      * 重写getParameter,代表参数从当前类中的map获取
      * @param name
      * @return
      */
     @Override
     public String getParameter(String name) {
         String[]values = params.get(name);
         if(values == null || values.length == 0) {
             return null;
         }
         return values[0];
     }

     /**
      * 重写getParameterValues方法,从当前类的 map中取值
      * @param name
      * @return
      */
     @Override
     public String[] getParameterValues(String name) {
         return params.get(name);
     }
}  

响应包装类

public class ResponseWrapper extends HttpServletResponseWrapper {

    private ByteArrayOutputStream buffer = null;
    private ServletOutputStream out = null;
    private PrintWriter writer = null;

    public ResponseWrapper(HttpServletResponse response) throws IOException {
        super(response);
        buffer = new ByteArrayOutputStream();// 真正存储数据的流
        out = new WapperedOutputStream(buffer);
        writer = new PrintWriter(new OutputStreamWriter(buffer,this.getCharacterEncoding()));
    }

    /** 重载父类获取outputstream的方法 */
    @Override
    public ServletOutputStream getOutputStream() throws IOException {
        return out;
    }

    /** 重载父类获取writer的方法 */
    @Override
    public PrintWriter getWriter() throws UnsupportedEncodingException {
        return writer;
    }

    /** 重载父类获取flushBuffer的方法 */
    @Override
    public void flushBuffer() throws IOException {
        if (out != null) {
            out.flush();
        }
        if (writer != null) {
            writer.flush();
        }
    }

    @Override
    public void reset() {
        buffer.reset();
    }

    /** 将out、writer中的数据强制输出到WapperedResponse的buffer里面,否则取不到数据 */
    public byte[] getResponseData() throws IOException {
        flushBuffer();
        return buffer.toByteArray();
    }

    /** 内部类,对ServletOutputStream进行包装 */
    private class WapperedOutputStream extends ServletOutputStream {
        private ByteArrayOutputStream bos = null;

        public WapperedOutputStream(ByteArrayOutputStream stream)
                throws IOException {
            bos = stream;
        }

        @Override
        public void write(int b) throws IOException {
            bos.write(b);
        }

        @Override
        public void write(byte[] b) throws IOException {
            bos.write(b, 0, b.length);
        }

        @Override
        public boolean isReady() {
            return false;
        }

        @Override
        public void setWriteListener(WriteListener writeListener) {

        }
    }


}

目标路径判断

public boolean dealWith(HttpServletRequest request, String[] strArr) {
    if (!isEncryptVersion(request)) {
        return false;
    }
    String path = request.getRequestURI();
    for (String url : strArr) {
        if (path.contains(url)) {
            return true;
        }
    }
    return false;
}

由于涉及加解密的过程已经以及业务,只贴出相关核心代码

private void buildPost(ServletRequest request, FilterChain chain, HttpServletResponse responseWrapper,boolean isIos) throws Exception {
    PostHttpServletRequestWrapper requestWrapper = new PostHttpServletRequestWrapper(
            (HttpServletRequest) request);
    // 读取请求内容
    BufferedReader br;
    br = requestWrapper.getReader();
    String line = null;
    StringBuilder sb = new StringBuilder();
    while ((line = br.readLine()) != null) {
        sb.append(line);
    }
    String s = sb.toString();
    if (!isPostEpi(s)) {
        s = replace(filterIos(s,isIos), false);
        requestWrapper.setBody(s.getBytes("UTF-8"));
    }
    chain.doFilter(requestWrapper, responseWrapper);
}
private void buildGet(ServletRequest request, FilterChain chain, HttpServletResponse responseWrapper,boolean isIos) throws Exception {
    GetHttpServletRequestWrapper requestWrapper = new GetHttpServletRequestWrapper((HttpServletRequest) request);
    if (!isGetEpi(requestWrapper)) {
        //doRestUrl(httpRequest.getRequestURI(), requestWrapper);
        doGetDecrypt(requestWrapper,isIos);
    }
    chain.doFilter(requestWrapper, responseWrapper);
}

核心:正则替换

private String replace(String context, boolean isEncrypt) {
    Matcher matcher = pattern.matcher(context);
    Map<String, String> map = new HashMap<>();
    if (matcher.find()) {
        matcher.reset();
        while (matcher.find()) {
            String j = matcher.group(0);
            if (j.contains(""name"")) {
                String name = matcher.group(1);
                dealReplace(j, name, map, isEncrypt);
            } else if (j.contains(""mobile"")) {
                String mobile = matcher.group(2);
                dealReplace(j, mobile, map, isEncrypt);
            } else if (j.contains(""account"")) {
                String account = matcher.group(3);
                dealReplace(j, account, map, isEncrypt);
            } else if (j.contains(""idcard"")) {
                String idcard = matcher.group(4);
                dealReplace(j, idcard, map, isEncrypt);
            }
        }
    }
    for (Map.Entry<String, String> entry : map.entrySet()) {
        String key = entry.getKey();
        String value = entry.getValue();
        context = context.replace(key, value);
    }
    return context;
}

这里其实也可以抽取的,用定义好的正则表达式字符串抽取的,只不过累了不想动了,交给后人吧,哈哈哈.

启用过滤器

image.png

后话

代码已经上线,已接受考验.由于涉及到公司的业务,敏感信息就不能展示了.