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

背景

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

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

调研

整个包加密

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

目标字段加解密

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

实现

思路

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

代码

正则表达式

1
ruby复制代码private final String s = ""name":"(.*?)"|"mobile":"(.*?)"|"account":"(.*?)"|"idcard":"(.*?)"|"newPwd":"(.*?)"|"vcode":"(.*?)"|"validateCode":"(.*?)"";

目标接口

1
ini复制代码private String[] urlPatterns = {"/base/group/user" ,......};

请求包装类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
java复制代码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;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
typescript复制代码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);
}
}

响应包装类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
java复制代码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) {

}
}


}

目标路径判断

1
2
3
4
5
6
7
8
9
10
11
12
typescript复制代码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;
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ini复制代码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);
}
1
2
3
4
5
6
7
8
scss复制代码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);
}

核心:正则替换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
ini复制代码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

后话

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

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%