文章来源 http://www.houxiurong.com/
1.添加过滤器
/**
* 拦截防止xss注入
*
* @author houxiurong
* @date 2022-01-21
*/
@Slf4j
@Component
public class XssFilter extends ZuulFilter {
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
@Override
public int filterOrder() {
return 3;
}
@Autowired
private NoCheckTokenUriCfg noCheckTokenUriCfg;
@Override
public boolean shouldFilter() {
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
// 注册和登录接口不拦截,其他接口都要拦截校验 token
log.info("XssFilter.shouldFilter() 请求URL:" + request.getRequestURI());
String requestUrl = request.getRequestURI();
// 放行不校验token的请求
for (String urlStr : noCheckTokenUriCfg.getUriArray()) {
if (requestUrl.contains(urlStr)) {
return false;
}
}
return true;
}
@Override
public Object run() {
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
// 放行不校验文件上传请求
if (request.getRequestURI().contains("/component/comAttachment/uploadFile")) {
return null;
}
try (InputStream requestInputStream = request.getInputStream()) {
//String toString = IOUtils.toString(requestInputStream, StandardCharsets.UTF_8);
String requestBody = StreamUtils.copyToString(requestInputStream, StandardCharsets.UTF_8);
// 空值不处理
if (StringUtils.isEmpty(requestBody)) {
return null;
}
Map beforeRequestBody = JSON.parseObject(requestBody);
log.info("Xss filter beforeRequestBody={}", beforeRequestBody);
for (Map.Entry entry : beforeRequestBody.entrySet()) {
if (this.containsXssValue(entry.getValue().toString())) {
log.error("Xss filter containsXssValue包含有特殊xss字符,{}", entry.getValue().toString());
this.setXssResponse(requestContext, HttpStatus.BAD_REQUEST.value(), "XSS跨站安全检查不通过");
}
}
} catch (Exception e) {
log.error("zuul 过滤器读取参数XSS跨站危险检查异常", e);
}
return null;
}
/**
* 特殊字符判断
*
* @param requestValue 参数
* @return handled requestValue
*/
private Boolean containsXssValue(String requestValue) {
if (Pattern.matches("^.*(script).*$", requestValue)
|| Pattern.matches("^.*(eval).*$", requestValue)
|| Pattern.matches("^.*javascript.*$", requestValue)) {
return true;
}
return false;
}
/**
* 拒绝提交
*/
private void setXssResponse(RequestContext requestContext, int code, String msg) {
// 过滤该请求对其进行路由
requestContext.setSendZuulResponse(false);
//组装统一格式返回报文
ObjectResult result = new ObjectResult();
result.setStatusCode(code + "");
result.setMsg(msg);
// 返回错误代码
requestContext.setResponseBody(JSONUtils.toJSONString(result));
requestContext.getResponse().setContentType("application/json;charset=UTF-8");
}
}
2.解决request.getInputStream()使用一次后请求为空问题。
request.getInputStream()在使用过一次后,再次使用会为null,这个可以重新RequestWrapper来解决该问题。
下面添加 MyServletRequestWrapper
/**
* 包装HttpServletRequest
*
* @author houxiurong
* @date 2022-01-21
*/
public class MyServletRequestWrapper extends HttpServletRequestWrapper {
private final byte[] body;
public MyServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
body = IOUtils.toByteArray(super.getInputStream());
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public ServletInputStream getInputStream() throws IOException {
return new RequestBodyCachingInputStream(body);
}
/**
* 缓存RequestBodyCachingInputStream
*/
private class RequestBodyCachingInputStream extends ServletInputStream {
private byte[] body;
private int lastIndexRetrieved = -1;
private ReadListener listener;
public RequestBodyCachingInputStream(byte[] body) {
this.body = body;
}
@Override
public int read() throws IOException {
if (isFinished()) {
return -1;
}
int i = body[lastIndexRetrieved + 1];
lastIndexRetrieved++;
if (isFinished() && listener != null) {
try {
listener.onAllDataRead();
} catch (IOException e) {
listener.onError(e);
throw e;
}
}
return i;
}
@Override
public boolean isFinished() {
return lastIndexRetrieved == body.length - 1;
}
@Override
public boolean isReady() {
return isFinished();
}
@Override
public void setReadListener(ReadListener listener) {
if (listener == null) {
throw new IllegalArgumentException("listener cann not be null");
}
if (this.listener != null) {
throw new IllegalArgumentException("listener has been set");
}
this.listener = listener;
if (!isFinished()) {
try {
listener.onAllDataRead();
} catch (IOException e) {
listener.onError(e);
}
} else {
try {
listener.onAllDataRead();
} catch (IOException e) {
listener.onError(e);
}
}
}
@Override
public int available() throws IOException {
return body.length - lastIndexRetrieved - 1;
}
@Override
public void close() throws IOException {
lastIndexRetrieved = body.length - 1;
body = null;
}
}
}
网关服务注入Filter过滤器,此处需要过滤掉文件上传接口。
/**
* 替换Request对象被getInputStream读取一次的问题
*
* @author houxiurong
* @date 2022-01-21
*/
@Component
public class RequestReplaceFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String requestUrl = request.getRequestURI();
// 放行不校验文件上传请求
if (!requestUrl.contains("/component/comAttachment/uploadFile")) {
if (!(request instanceof MyServletRequestWrapper)) {
request = new MyServletRequestWrapper(request);
}
}
filterChain.doFilter(request, response);
}
}
最后启动网关服务,大功告成! ^_^为了解决 requestInputSteam这个读取一次失效问题,此处研究了半天。感谢踩过坑的同事提醒