前言:
问题现象: 调用spring cloud netflix微服务时,如果请求的Content-type为"x-www-form-urlencoded"格式时,部分请求体的参数如果key与query参数重合时,该请求体参数会丢失.
问题分析: 当x-www-form-urlencoded格式的表单提交的请求体参数的key存在和query参数重合时,请求体参数会被zuul自动合并到一个request的一个隐藏的query参数中,并删除请求体相应参数,导致请求体参数丢失.
解决方式:创建自定义ZuulFilter子类,自动解析被隐藏的请求体参数并重新写入请求体中,问题解决。
自定义ZuulFilter子类如下所示:
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import com.netflix.zuul.http.HttpServletRequestWrapper;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequestWrapper;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
@Component
public class CustomRequestFilter extends ZuulFilter {
@Autowired
private ApplicationContext applicationContext;
@Override
public String filterType() {
return FilterConstants.PRE_TYPE; // 在请求之前执行
}
@Override
public int filterOrder() {
return -1; // 过滤器的执行顺序
}
@Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
return request.getMethod().equalsIgnoreCase(HttpMethod.POST.name()) || request.getMethod().equalsIgnoreCase(HttpMethod.PUT.name());
}
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
try {
//解决表单方式(application/x-www-form-urlencoded)提交参数,参数名称和query参数同名时,
// 参数混入query参数中,而请求体中该同名参数丢失的问题
if (request.getContentType() != null && request.getContentType().contains("application/x-www-form-urlencoded")) {
// 读取请求体
MultiValueMap<String, String> requestBody = readFormUrlEncoded(request.getInputStream());
//该数据中混入了请求体的表单参数
Map<String, String[]> parameterMap = ((ServletRequestWrapper) request).getRequest().getParameterMap();
//query参数中存在这些已知参数,需要校验请求体是否存在这些参数
List<String> tongtechParam = Arrays.asList("signature", "timestamp", "appkey");
for (String paramKey : parameterMap.keySet()) {
if (!tongtechParam.contains(paramKey)) {
continue;
}
String[] values = parameterMap.get(paramKey);
if (values != null && values.length > 1) {
//实际的query参数,需要和requestBody中同名参数的值进行对比
String queryValue = request.getParameterMap().get(paramKey)[0];
//循环将业务混入param的参数回填入requestBody中
for (String value : values) {
if (StringUtils.isNotBlank(queryValue) && !StringUtils.equals(queryValue, value)) {
requestBody.add(paramKey, value);
}
}
}
}
// 重新设置请求体
setRequestBody(ctx, request, requestBody);
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private MultiValueMap<String, String> readFormUrlEncoded(java.io.InputStream inputStream) throws IOException {
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
String line;
StringBuilder content = new StringBuilder();
while ((line = reader.readLine()) != null) {
content.append(line);
}
String[] parts = content.toString().split("&");
for (String part : parts) {
String[] kv = part.split("=");
if (kv.length == 2) {
map.add(kv[0], kv[1]);
}
}
return map;
}
private void setRequestBody(RequestContext ctx, HttpServletRequest request, MultiValueMap<String, String> requestBody) {
// 创建一个新的请求体
StringBuilder newRequestBody = new StringBuilder();
for (String key : requestBody.keySet()) {
for (String value : requestBody.get(key)) {
newRequestBody.append(key).append("=").append(value).append("&");
}
}
if (newRequestBody.length() > 0) {
newRequestBody.deleteCharAt(newRequestBody.length() - 1); // 移除最后一个 &
}
// 设置新的请求体
ctx.setRequest(new HttpServletRequestWrapper(request) {
@Override
public String getCharacterEncoding() {
return StandardCharsets.UTF_8.name();
}
@Override
public String getContentType() {
return MediaType.APPLICATION_FORM_URLENCODED_VALUE;
}
@Override
public int getContentLength() {
return newRequestBody.toString().getBytes(StandardCharsets.UTF_8).length;
}
@Override
public long getContentLengthLong() {
return getContentLength();
}
@Override
public ServletInputStream getInputStream() {
byte[] bytes = newRequestBody.toString().getBytes(StandardCharsets.UTF_8);
return new ByteArrayServletInputStream(bytes);
}
});
}
}
import javax.servlet.ServletInputStream;
import java.io.ByteArrayInputStream;
public class ByteArrayServletInputStream extends ServletInputStream {
private final ByteArrayInputStream bais;
public ByteArrayServletInputStream(byte[] byteArray) {
this.bais = new ByteArrayInputStream(byteArray);
}
@Override
public int read() {
return bais.read();
}
@Override
public boolean isFinished() {
return bais.available() <= 0;
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(javax.servlet.ReadListener listener) {
// 不需要实现
}
}


943

被折叠的 条评论
为什么被折叠?



