1.问题描述
改造一个十年老项目,目的是防sql注入和XSS攻击。
难点在于前后端传参类型十分复杂,有application/json的,有x-www-form-urlencoded的。
2.解决思路
最开始想使用filter,统一拦截下请求体,然后过滤实现。
但是对于application/json可以这么做,因为就是个json,但是对于x-www-form-urlencoded,它每个请求的key都不一样,不好拿。
解决问题的思路,找到这两种请求类型的共同点。
- 都是放到request里的。
- 使用时,application/json的本质是把请求中的流转化为预先设置好的对象,x-www-form-urlencoded的本质是一个放到request里的map。
根据以上两点,可以使用装饰模式,把request包起来,然后在包装类上对流和map进行处理,然后再把这个包装类当做request放到请求链中。
3.方案实践
public class ParameterRequestWrapper extends HttpServletRequestWrapper {
private final byte[] body;
private Map<String, Object> parameterMap;
public ParameterRequestWrapper(HttpServletRequest request) {
super(request);
//在这里,把request中的流读出来,转成String
String sessionStream = getBodyString(request);
//针对x-www-form-urlencoded,由于后续调用使用的是getParameter这种形式从request的map中取值.
//针对application/json,我们把这个String转成字符数组。
if("x-www-form-urlencoded".reqeust.getContentType){
parameterMap =reqest.getParametes()
}else if("application/json".reqeust.getContentType){
body = sessionStream.getBytes(Charset.forName("UTF-8"));
}else{
//根据项目中还出现什么妖魔鬼怪,来决定怎么组织数据结构接request里的值
}
}
@Override
public String getParameter(String name) {
String parameter = null;
String value = null;
if(parameterMap != null && parameterMap.get(name) !=null) {
value =(String) parameterMap.get(name);
}
parameter = xssEncode(value);
// 过滤sql注入
parameter = filterContent(parameter);
return parameter;
}
/**
* 获取请求Body
*
* @param request
* @return
*/
public String getBodyString(final ServletRequest request) {
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) {
try {
inputStream.close();
}
catch (IOException e) {
e.printStackTrace();
}
}
if (reader != null) {
try {
reader.close();
}
catch (IOException e) {
e.printStackTrace();
}
}
}
String ssb = xssEncode(sb.toString());
// 过滤sql注入
ssb = filterContent(ssb);
System.out.println("application/json的StringBuilder"+sb.toString());
return ssb;
}
/**
* 复制输入流
*/
public InputStream cloneInputStream(ServletInputStream inputStream) {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
try {
while ((len = inputStream.read(buffer)) > -1) {
byteArrayOutputStream.write(buffer, 0, len);
}
byteArrayOutputStream.flush();
}
catch (IOException e) {
e.printStackTrace();
}
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 int read() throws IOException {
//@RequestBody会使用这个方法获取流,然后转成Bean给Controller使用,所以要把body写到这里来。
return bais.read();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
};
}
}