[英]Modify the filter chain - Or select servlet to respond to request using filter
[英]Modify request parameter with servlet filter
現有的 Web 應用程序正在 Tomcat 4.1 上運行。 頁面存在 XSS 問題,但我無法修改源。 我決定編寫一個 servlet 過濾器來在頁面看到參數之前對其進行清理。
我想寫一個像這樣的過濾器類:
import java.io.*;
import javax.servlet.*;
public final class XssFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException
{
String badValue = request.getParameter("dangerousParamName");
String goodValue = sanitize(badValue);
request.setParameter("dangerousParamName", goodValue);
chain.doFilter(request, response);
}
public void destroy() {
}
public void init(FilterConfig filterConfig) {
}
}
但是ServletRequest.setParameter
不存在。
如何在將請求向下傳遞之前更改請求參數的值?
正如您所指出的HttpServletRequest
沒有 setParameter 方法。 這是故意的,因為該類表示來自客戶端的請求,而修改參數並不代表該請求。
一種解決方案是使用HttpServletRequestWrapper
類,它允許您將一個請求與另一個請求包裝在一起。 您可以將其子類化,並覆蓋getParameter
方法以返回您的清理值。 然后,您可以將包裝chain.doFilter
請求傳遞給chain.doFilter
而不是原始請求。
這有點難看,但這就是 servlet API 所說的你應該做的。 如果您嘗試向doFilter
傳遞任何其他內容,一些 servlet 容器會抱怨您違反了規范,並拒絕處理它。
更優雅的解決方案是更多的工作 - 修改處理參數的原始 servlet/JSP,使其期望請求屬性而不是參數。 過濾器檢查參數,對其進行清理,並使用清理后的值設置屬性(使用request.setAttribute
)。 沒有子類化,沒有欺騙,但確實需要您修改應用程序的其他部分。
作為記錄,這是我最終編寫的課程:
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
public final class XssFilter implements Filter {
static class FilteredRequest extends HttpServletRequestWrapper {
/* These are the characters allowed by the Javascript validation */
static String allowedChars = "+-0123456789#*";
public FilteredRequest(ServletRequest request) {
super((HttpServletRequest)request);
}
public String sanitize(String input) {
String result = "";
for (int i = 0; i < input.length(); i++) {
if (allowedChars.indexOf(input.charAt(i)) >= 0) {
result += input.charAt(i);
}
}
return result;
}
public String getParameter(String paramName) {
String value = super.getParameter(paramName);
if ("dangerousParamName".equals(paramName)) {
value = sanitize(value);
}
return value;
}
public String[] getParameterValues(String paramName) {
String values[] = super.getParameterValues(paramName);
if ("dangerousParamName".equals(paramName)) {
for (int index = 0; index < values.length; index++) {
values[index] = sanitize(values[index]);
}
}
return values;
}
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
chain.doFilter(new FilteredRequest(request), response);
}
public void destroy() {
}
public void init(FilterConfig filterConfig) {
}
}
編寫一個簡單的類,該類使用 getParameter() 方法對HttpServletRequestWrapper
進行子類,該方法返回輸入的凈化版本。 然后將HttpServletRequestWrapper
的實例傳遞給Filter.doChain()
而不是直接傳遞請求對象。
根據您的所有評論,我的建議對我有用:
private final class CustomHttpServletRequest extends HttpServletRequestWrapper {
private final Map<String, String[]> queryParameterMap;
private final Charset requestEncoding;
public CustomHttpServletRequest(HttpServletRequest request) {
super(request);
queryParameterMap = getCommonQueryParamFromLegacy(request.getParameterMap());
String encoding = request.getCharacterEncoding();
requestEncoding = (encoding != null ? Charset.forName(encoding) : StandardCharsets.UTF_8);
}
private final Map<String, String[]> getCommonQueryParamFromLegacy(Map<String, String[]> paramMap) {
Objects.requireNonNull(paramMap);
Map<String, String[]> commonQueryParamMap = new LinkedHashMap<>(paramMap);
commonQueryParamMap.put(CommonQueryParams.PATIENT_ID, new String[] { paramMap.get(LEGACY_PARAM_PATIENT_ID)[0] });
commonQueryParamMap.put(CommonQueryParams.PATIENT_BIRTHDATE, new String[] { paramMap.get(LEGACY_PARAM_PATIENT_BIRTHDATE)[0] });
commonQueryParamMap.put(CommonQueryParams.KEYWORDS, new String[] { paramMap.get(LEGACY_PARAM_STUDYTYPE)[0] });
String lowerDateTime = null;
String upperDateTime = null;
try {
String studyDateTime = new SimpleDateFormat("yyyy-MM-dd").format(new SimpleDateFormat("dd-MM-yyyy").parse(paramMap.get(LEGACY_PARAM_STUDY_DATE_TIME)[0]));
lowerDateTime = studyDateTime + "T23:59:59";
upperDateTime = studyDateTime + "T00:00:00";
} catch (ParseException e) {
LOGGER.error("Can't parse StudyDate from query parameters : {}", e.getLocalizedMessage());
}
commonQueryParamMap.put(CommonQueryParams.LOWER_DATETIME, new String[] { lowerDateTime });
commonQueryParamMap.put(CommonQueryParams.UPPER_DATETIME, new String[] { upperDateTime });
legacyQueryParams.forEach(commonQueryParamMap::remove);
return Collections.unmodifiableMap(commonQueryParamMap);
}
@Override
public String getParameter(String name) {
String[] params = queryParameterMap.get(name);
return params != null ? params[0] : null;
}
@Override
public String[] getParameterValues(String name) {
return queryParameterMap.get(name);
}
@Override
public Map<String, String[]> getParameterMap() {
return queryParameterMap; // unmodifiable to uphold the interface contract.
}
@Override
public Enumeration<String> getParameterNames() {
return Collections.enumeration(queryParameterMap.keySet());
}
@Override
public String getQueryString() {
// @see : https://stackoverflow.com/a/35831692/9869013
// return queryParameterMap.entrySet().stream().flatMap(entry -> Stream.of(entry.getValue()).map(value -> entry.getKey() + "=" + value)).collect(Collectors.joining("&")); // without encoding !!
return queryParameterMap.entrySet().stream().flatMap(entry -> encodeMultiParameter(entry.getKey(), entry.getValue(), requestEncoding)).collect(Collectors.joining("&"));
}
private Stream<String> encodeMultiParameter(String key, String[] values, Charset encoding) {
return Stream.of(values).map(value -> encodeSingleParameter(key, value, encoding));
}
private String encodeSingleParameter(String key, String value, Charset encoding) {
return urlEncode(key, encoding) + "=" + urlEncode(value, encoding);
}
private String urlEncode(String value, Charset encoding) {
try {
return URLEncoder.encode(value, encoding.name());
} catch (UnsupportedEncodingException e) {
throw new IllegalArgumentException("Cannot url encode " + value, e);
}
}
@Override
public ServletInputStream getInputStream() throws IOException {
throw new UnsupportedOperationException("getInputStream() is not implemented in this " + CustomHttpServletRequest.class.getSimpleName() + " wrapper");
}
}
注意:queryString() 需要處理每個 KEY 的所有值,如果需要,在添加自己的參數值時不要忘記 encodeUrl()
作為限制,如果您調用 request.getParameterMap() 或任何會調用 request.getReader() 並開始讀取的方法,您將阻止對 request.setCharacterEncoding(...) 的任何進一步調用
我遇到了同樣的問題(從過濾器中的 HTTP 請求更改參數)。 我最終使用了ThreadLocal<String>
。 在Filter
我有:
class MyFilter extends Filter {
public static final ThreadLocal<String> THREAD_VARIABLE = new ThreadLocal<>();
public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) {
THREAD_VARIABLE.set("myVariableValue");
chain.doFilter(request, response);
}
}
在我的請求處理器( HttpServlet
、JSF 控制器或任何其他 HTTP 請求處理器)中,我取回當前線程值:
...
String myVariable = MyFilter.THREAD_VARIABLE.get();
...
優點:
HttpServletRequestWrapper
樣板更優雅request.setAttribute(String,Object)
時的范圍,即您可以訪問其他過濾器中的變量。缺點:
java.util.stream.Stream.parallel
、 java.util.concurrent.Future
、 java.lang.Thread
。一些旁注:
服務器有一個線程池來處理 HTTP 請求。 由於這是池:
if (value!=null) { THREAD_VARIABLE.set(value);}
因為當value
為 null 時,您將重用來自前一個 HTTP 請求的value
:保證副作用)。HttpSession.setAttribute()
@RequestScoped
內部使用ThreadLocal
,但使用ThreadLocal
更通用:您可以在非 JEE/CDI 容器中使用它(例如在多線程 JRE 應用程序中)這就是我最終做的
//import ../../Constants;
public class RequestFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(RequestFilter.class);
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
try {
CustomHttpServletRequest customHttpServletRequest = new CustomHttpServletRequest((HttpServletRequest) servletRequest);
filterChain.doFilter(customHttpServletRequest, servletResponse);
} finally {
//do something here
}
}
@Override
public void destroy() {
}
public static Map<String, String[]> ADMIN_QUERY_PARAMS = new HashMap<String, String[]>() {
{
put("diagnostics", new String[]{"false"});
put("skipCache", new String[]{"false"});
}
};
/*
This is a custom wrapper over the `HttpServletRequestWrapper` which
overrides the various header getter methods and query param getter methods.
Changes to the request pojo are
=> A custom header is added whose value is a unique id
=> Admin query params are set to default values in the url
*/
private class CustomHttpServletRequest extends HttpServletRequestWrapper {
public CustomHttpServletRequest(HttpServletRequest request) {
super(request);
//create custom id (to be returned) when the value for a
//particular header is asked for
internalRequestId = RandomStringUtils.random(10, true, true) + "-local";
}
public String getHeader(String name) {
String value = super.getHeader(name);
if(Strings.isNullOrEmpty(value) && isRequestIdHeaderName(name)) {
value = internalRequestId;
}
return value;
}
private boolean isRequestIdHeaderName(String name) {
return Constants.RID_HEADER.equalsIgnoreCase(name) || Constants.X_REQUEST_ID_HEADER.equalsIgnoreCase(name);
}
public Enumeration<String> getHeaders(String name) {
List<String> values = Collections.list(super.getHeaders(name));
if(values.size()==0 && isRequestIdHeaderName(name)) {
values.add(internalRequestId);
}
return Collections.enumeration(values);
}
public Enumeration<String> getHeaderNames() {
List<String> names = Collections.list(super.getHeaderNames());
names.add(Constants.RID_HEADER);
names.add(Constants.X_REQUEST_ID_HEADER);
return Collections.enumeration(names);
}
public String getParameter(String name) {
if (ADMIN_QUERY_PARAMS.get(name) != null) {
return ADMIN_QUERY_PARAMS.get(name)[0];
}
return super.getParameter(name);
}
public Map<String, String[]> getParameterMap() {
Map<String, String[]> paramsMap = new HashMap<>(super.getParameterMap());
for (String paramName : ADMIN_QUERY_PARAMS.keySet()) {
if (paramsMap.get(paramName) != null) {
paramsMap.put(paramName, ADMIN_QUERY_PARAMS.get(paramName));
}
}
return paramsMap;
}
public String[] getParameterValues(String name) {
if (ADMIN_QUERY_PARAMS.get(name) != null) {
return ADMIN_QUERY_PARAMS.get(name);
}
return super.getParameterValues(name);
}
public String getQueryString() {
Map<String, String[]> map = getParameterMap();
StringBuilder builder = new StringBuilder();
for (String param: map.keySet()) {
for (String value: map.get(param)) {
builder.append(param).append("=").append(value).append("&");
}
}
builder.deleteCharAt(builder.length() - 1);
return builder.toString();
}
}
}
您可以使用正則表達式進行清理。 在調用chain.doFilter(request, response)方法之前的過濾器內部,調用此代碼。 這是示例代碼:
for (Enumeration en = request.getParameterNames(); en.hasMoreElements(); ) {
String name = (String)en.nextElement();
String values[] = request.getParameterValues(name);
int n = values.length;
for(int i=0; i < n; i++) {
values[i] = values[i].replaceAll("[^\\dA-Za-z ]","").replaceAll("\\s+","+").trim();
}
}
我只是想知道,當我添加一個包裝器類之后,我創建了設置和獲取Header的方法,只有當請求到達控制器@RequestHeader HttpHeader標頭時,標頭才可以請求,但我沒有得到它,但是HttpServletRequest中仍然可用,我該如何解決它或如何將其他自定義標頭添加到HttpServletRequest中,我也嘗試使用攔截器。 找到有關發行的詳細信息。 在此處輸入鏈接描述
試試request.setAttribute("param",value);
. 它對我來說很好。
請找到此代碼示例:
private void sanitizePrice(ServletRequest request){
if(request.getParameterValues ("price") != null){
String price[] = request.getParameterValues ("price");
for(int i=0;i<price.length;i++){
price[i] = price[i].replaceAll("[^\\dA-Za-z0-9- ]", "").trim();
System.out.println(price[i]);
}
request.setAttribute("price", price);
//request.getParameter("numOfBooks").re
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.