业务背景:一般情况下,我们的项目是需要做SQL注入、XSS拦截等安全性问题,本来是打算在网关层面做全局过滤器或者自定义过滤器,但是苦于遇到依赖问题等实在短期内无法解决,于是便在其下一层针对某个项目做SQL注入拦截。
一、直接上源码
1.启动类路径下,直接建一个filter包,
过滤器SqlInjectFilter
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.bc.base.log.BaseLog;
import com.bc.digitalmanage.entity.common.ResultMsg;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.*;
/**
* @author w7h1te-ywq
* @version 9月...
* @date 2022/9/13 16:00
*/
@WebFilter(urlPatterns = "/",filterName = "SqlInjectionFilter")
@Configuration
public class SqlInjectionFilter implements Filter {
private static BaseLog log = BaseLog.getInstance(SqlInjectionFilter.class.getName());
private static final Set<String> ALLOWED_PATHS = Collections.unmodifiableSet(new HashSet<>(
Arrays.asList("")));
private static final String SQL_REG_EXP = ".*(\\b(select|insert|into|update|delete|from|where|trancate" +
"|drop|execute|grant|use|union)\\b).*";
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
CustomRequestWrapper requestWrapper = new CustomRequestWrapper(request);
Map<String, Object> parameterMap = new HashMap<>();
String path = request.getRequestURI().substring(request.getContextPath().length()).replaceAll("[/]+$", "");
boolean allowedPath = ALLOWED_PATHS.contains(path);
if (!allowedPath) {
parameterMap = getParameterMap(parameterMap, request, requestWrapper);
// 正则校验是否有SQL关键字
for (Object obj : parameterMap.entrySet()) {
Map.Entry entry = (Map.Entry) obj;
Object value = entry.getValue();
if (value != null) {
boolean isValid = isSqlInject(value.toString(), servletResponse);
if (!isValid) {
return;
}
}
}
}
filterChain.doFilter(requestWrapper, servletResponse);
}
private Map<String, Object> getParameterMap(Map<String, Object> paramMap, HttpServletRequest request, CustomRequestWrapper requestWrapper) {
// 1.POST请求获取参数
if ("POST".equals(request.getMethod().toUpperCase())) {
String body = requestWrapper.getBody();
if(StringUtils.isNotEmpty(body)){
boolean jsonType = getJSONType(body);
if(jsonType==true){
paramMap = JSONObject.parseObject(body, HashMap.class);
}else {
String[] split = body.split("&");
for (int i = 0; i < split.length; i++) {
String[] split1;
split1 = split[i].split("=");
paramMap.put(split1[0],split1[1]);
split1 = null;
}
}
}else {
Map<String, String[]> parameterMap = requestWrapper.getParameterMap();
if (parameterMap != null && parameterMap.size() > 0) {
Set<Map.Entry<String, String[]>> entries = parameterMap.entrySet();
for (Map.Entry<String, String[]> next : entries) {
paramMap.put(next.getKey(), next.getValue()[0]);
}
}
}
} else {
Map<String, String[]> parameterMap = requestWrapper.getParameterMap();
//普通的GET请求
if (parameterMap != null && parameterMap.size() > 0) {
Set<Map.Entry<String, String[]>> entries = parameterMap.entrySet();
for (Map.Entry<String, String[]> next : entries) {
paramMap.put(next.getKey(), next.getValue()[0]);
}
} else {
//GET请求,参数在URL路径型式,比如server/{var1}/{var2}
String afterDecodeUrl = null;
try {
//编码过URL需解码解码还原字符
afterDecodeUrl = URLDecoder.decode(request.getRequestURI(), "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
paramMap.put("pathVar", afterDecodeUrl);
}
}
return paramMap;
}
private boolean isSqlInject(String value, ServletResponse servletResponse) throws IOException {
ResultMsg<Object> resultMsg = new ResultMsg<>();
if (null != value && value.toLowerCase().matches(SQL_REG_EXP)) {
log.info(" SqlInjectionFilter isSqlInject :入参中有非法字符: " + value);
HttpServletResponse response = (HttpServletResponse) servletResponse;
// Map<String, String> responseMap = new HashMap<>();
// 匹配到非法字符,立即返回
// responseMap.put("code", "999");
// responseMap.put("message","入参中有非法字符");
resultMsg.setResultMsg("输入数据中存在非法字符!");
resultMsg.setSuccess(false);
resultMsg.setResultCode("999");
resultMsg.setData("入参中有非法字符————SQL注入拦截!!!");
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpStatus.OK.value());
response.getWriter().write(JSON.toJSONString(resultMsg));
response.getWriter().flush();
response.getWriter().close();
return false;
}
return true;
}
private boolean getJSONType(String str){
boolean result = false;
if (StringUtils.isNotBlank(str)) {
str = str.trim();
if (str.startsWith("{") && str.endsWith("}")) {
result = true;
} else if (str.startsWith("[") && str.endsWith("]")) {
result = true;
}
}
return result;
}
@Override
public void destroy() {
}
}
设置请求装饰类CustomRequestWrapper
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.Map;
/**
* @author w7h1te-ywq
* @version 9月...
* @date 2022/9/13 16:01
*/
public class CustomRequestWrapper extends HttpServletRequestWrapper {
private final String body;
public CustomRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
StringBuilder sb = new StringBuilder();
BufferedReader bufferedReader = null;
try {
InputStream inputStream = request.getInputStream();
if (inputStream != null) {
bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
char[] charBuffer = new char[512];
int bytesRead = -1;
while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
sb.append(charBuffer, 0, bytesRead);
}
} else {
sb.append("");
}
} catch (IOException e) {
e.printStackTrace();
throw e;
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
throw e;
}
}
}
body = sb.toString();
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bais = new ByteArrayInputStream(body.getBytes("UTF-8"));
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() {
return bais.read();
}
};
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream(), StandardCharsets.UTF_8));
}
public String getBody() {
return this.body;
}
@Override
public String getParameter(String name) {
return super.getParameter(name);
}
@Override
public Map<String, String[]> getParameterMap() {
return super.getParameterMap();
}
@Override
public Enumeration<String> getParameterNames() {
return super.getParameterNames();
}
@Override
public String[] getParameterValues(String name) {
return super.getParameterValues(name);
}
}
以bean注解来诠释次配置文件相关代码FilterConfiguration
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.Map;
/**
* @author w7h1te-ywq
* @version 9月...
* @date 2022/9/13 16:01
*/
public class CustomRequestWrapper extends HttpServletRequestWrapper {
private final String body;
public CustomRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
StringBuilder sb = new StringBuilder();
BufferedReader bufferedReader = null;
try {
InputStream inputStream = request.getInputStream();
if (inputStream != null) {
bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
char[] charBuffer = new char[512];
int bytesRead = -1;
while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
sb.append(charBuffer, 0, bytesRead);
}
} else {
sb.append("");
}
} catch (IOException e) {
e.printStackTrace();
throw e;
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
throw e;
}
}
}
body = sb.toString();
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bais = new ByteArrayInputStream(body.getBytes("UTF-8"));
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() {
return bais.read();
}
};
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream(), StandardCharsets.UTF_8));
}
public String getBody() {
return this.body;
}
@Override
public String getParameter(String name) {
return super.getParameter(name);
}
@Override
public Map<String, String[]> getParameterMap() {
return super.getParameterMap();
}
@Override
public Enumeration<String> getParameterNames() {
return super.getParameterNames();
}
@Override
public String[] getParameterValues(String name) {
return super.getParameterValues(name);
}
}
二、在springboot启动类上添加注解声明过滤器
@ServletComponentScan(basePackages = “com.bc.***.filter”)
三、参考链接
springboot自带filter实现sql防注入过滤器,可以全路径也可以自己设置过滤路径,还可以在全路径的时候选择放行某些路径不过滤。