需求:
通过gateway来转义请求参数中的html标签的“<”、“>”等,来防止xss攻击
版本: springbootVersion:2.1.6RELASE
springCloudGatewayVersion:2.1.2RELASE
因为gateway版本不同所以获取请求参数的方式也不同,这个版本的gateway是通过webflux来处理参数的,什么是webflux请点击查看,这里就不过多赘述了
对于gateway的过滤器有很多种实现方式,根据具体的业务需求来选定最便捷、最简单的处理方式
这里是我自定义gatewayfilter过滤器代码实现:
此代码只处理了两种contentType是:application/json和application/json;charset=UTF-8
对于contentType是multipart/form-data没有处理这是缺陷,如有好的处理方式请留言,大家一起学习一起进步
import lombok.extern.slf4j.Slf4j;
import org.owasp.esapi.ESAPI;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.function.Consumer;
import java.util.regex.Pattern;
/**
* @version 0.1.0
* @author: Lzz
* @Date: 2020-07-08 11:11
* @Description: $ 防御XssApi对请求参数进行过滤
* @return
* @since 0.1.0
**/
@Component
@Slf4j
public class RequestParametersFilter implements GlobalFilter, Ordered {
/**
* 正则表达式预编译处理
*/
private static final Pattern SCRIPT_PATTERN = Pattern.compile("<script>(.*?)</script>", Pattern.CASE_INSENSITIVE);
private static final Pattern SRC_PATTERN = Pattern.compile("src[\r\n]*=[\r\n]*'(.*?)'", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
private static final Pattern NR_PATTERN = Pattern.compile("src[\r\n]*=[\r\n]*\"(.*?)\"", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
private static final Pattern SCRIPT_ALL_PATTERN = Pattern.compile("<script(.*?)>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
private static final Pattern EVL_PATTERN = Pattern.compile("eval\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
private static final Pattern EXPR_PATTERN = Pattern.compile("expression\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
private static final Pattern JAVASCRIPT_PATTERN = Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE);
private static final Pattern VBSCRIPT_PATTERN = Pattern.compile("vbscript:", Pattern.CASE_INSENSITIVE);
private static final Pattern ONLOAD_PATTERN = Pattern.compile("onload(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
private static final Pattern ONXX_PATTERN = Pattern.compile("on.*(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
private static final String POST = "POST";
private static final String GET = "GET";
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest serverHttpRequest = exchange.getRequest();
String method = serverHttpRequest.getMethodValue();
log.info("请求路径 {},请求方式{}", serverHttpRequest.getURI().getPath(), method);
if (POST.equals(method)) {
if (MediaType.APPLICATION_JSON.isCompatibleWith(serverHttpRequest.getHeaders().getContentType()) || MediaType.APPLICATION_JSON_UTF8.isCompatibleWith(serverHttpRequest.getHeaders().getContentType())) {
log.info("contentType {}", serverHttpRequest.getHeaders().getContentType());
return DataBufferUtils.join(exchange.getRequest().getBody())
.flatMap(dataBuffer -> {
byte[] bytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(bytes);
//读取到具体的请求参数,但是读取到的json是string字符串,如果想做特殊处理可以从这里入手
String bodyString = new String(bytes, StandardCharsets.UTF_8);
log.info("post 请求参数 {}", bodyString);
//对string字符串进行正则过滤以及转义
String convertBody = cleanXss(bodyString);
//将处理完的数据重新放回ServerWebExchange
exchange.getAttributes().put("POST_BODY", convertBody);
DataBufferUtils.release(dataBuffer);
//需要注意的是:这里的参数都是IO流来读取,只能读一次,所有需要再次重新构建request
//提示:如果对于请参数没有处理需求,可以开启gateway的请求参数缓存方式,cachedRequestBodyObject
//适用于springbootVersion2.1+ 具体详见:https://github.com/spring-cloud/spring-cloud-gateway/issues/747
Flux<DataBuffer> cachedFlux = Flux.defer(() -> {
DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
return Mono.just(buffer);
});
ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {
@Override
public Flux<DataBuffer> getBody() {
return cachedFlux;
}
};
return chain.filter(exchange.mutate().request(mutatedRequest).build());
});
}
return chain.filter(exchange);
} else if (GET.equals(method)) {
//获取到get请求的map参数集合
MultiValueMap<String, String> queryParams = serverHttpRequest.getQueryParams();
//将处理完成的参数重新放入request中
Consumer<HttpHeaders> httpHeaders = httpHeader -> queryParams.forEach((k, v) -> {
//因为业务需求,在传参时参数名称会传入[ ] 等特殊符号,不轮需求如何这里一定要进行URLEncoder编码
httpHeader.set(URLEncoder.encode(k, StandardCharsets.UTF_8), cleanXss(String.valueOf(v)));
log.info("get 请求参数name={},value={}", URLEncoder.encode(k, StandardCharsets.UTF_8), v);
});
ServerHttpRequest build = exchange.getRequest().mutate().headers(httpHeaders).build();
return chain.filter(exchange.mutate().request(build).build());
}
return chain.filter(exchange);
}
/**
* 数字越小越先执行
* @return
*/
@Override
public int getOrder() {
return 0;
}
private String cleanXss(String value) {
if (value != null) {
// 推荐使用ESAPI库来避免脚本攻击
value = ESAPI.encoder().canonicalize(value);
// 避免空字符串
value = value.replaceAll("", "");
// 避免script 标签
value = SCRIPT_PATTERN.matcher(value).replaceAll("");
// 避免src形式的表达式
value = SRC_PATTERN.matcher(value).replaceAll("");
value = NR_PATTERN.matcher(value).replaceAll("");
// 删除单个的<script ...> 标签
value = SCRIPT_ALL_PATTERN.matcher(value).replaceAll("");
// 避免 eval(...) 形式表达式
value = EVL_PATTERN.matcher(value).replaceAll("");
// 避免 expression(...) 表达式
value = EXPR_PATTERN.matcher(value).replaceAll("");
// 避免 javascript: 表达式
value = JAVASCRIPT_PATTERN.matcher(value).replaceAll("");
// 避免 vbscript: 表达式
value = VBSCRIPT_PATTERN.matcher(value).replaceAll("");
// 避免 onload= 表达式
value = ONLOAD_PATTERN.matcher(value).replaceAll("");
// 避免 onXX= 表达式
value = ONXX_PATTERN.matcher(value).replaceAll("");
//将特殊字符进行转义
return replaceTag(value);
}
return "";
}
/**
* 替换尖括号等特殊字符
*
* @param input
* @return
*/
public String replaceTag(String input) {
if (!hasSpecialChars(input)) {
return input;
}
StringBuffer strBuffer = new StringBuffer(input.length());
char c;
for (int i = 0; i <= input.length() - 1; i++) {
c = input.charAt(i);
switch (c) {
case '<':
strBuffer.append("<");
break;
case '>':
strBuffer.append(">");
break;
case '"':
strBuffer.append(""");
break;
case '&':
strBuffer.append("&");
break;
default:
strBuffer.append(c);
}
}
return (strBuffer.toString());
}
/**
* 基本功能:判断标记是否存在
* <p>
*
* @param input
* @return boolean
*/
public boolean hasSpecialChars(String input) {
boolean flag = false;
if ((input != null) && (input.length() > 0)) {
char c;
for (int i = 0; i <= input.length() - 1; i++) {
c = input.charAt(i);
switch (c) {
case '>':
flag = true;
break;
case '<':
flag = true;
break;
case '"':
flag = true;
break;
case '&':
flag = true;
break;
default:
}
}
}
return flag;
}
}
这个地方就是我在代码中说的要加URLEncoder进行编码的原因,这个问题是已经通过gateway请求到了具体的服务上报的错,
通过度娘知道了大致意思是,由于Tomcat高版本太高不支持特殊字符,然后我通过一下方式完美解决了这个问题,
这个是对RFC 7230的讲解:https://datatracker.ietf.org/doc/rfc7230/
java.lang.IllegalArgumentException: The HTTP header line [test[1]: [RE]] does not conform to RFC 7230 and has been ignored.
原因:tomcatVersion:9.0.21中get请求header中的请求参数不支持特殊字符串需要通过
解决办法如下
URLEncoder.encode(parameter, StandardCharsets.UTF_8)
来源:oschina
链接:https://my.oschina.net/Pirvate/blog/4384977