为什么过滤器需要一个 Wrapper 来 extends HttpServletRequestWrapper
为什么过滤器需要一个 Wrapper 来 extends HttpServletRequestWrapper
- 1. HttpServletRequest` 不可直接修改
- 2. 能够修改请求参数和请求体
- 3. 方便扩展与重用
- 4. 处理请求参数和请求体
- 5. 避免对原始请求做修改
- 典型使用场景
- 举个例子
- 总结
在 Java Web 开发中,过滤器(Filter)和包装类(Wrapper)经常一起配合使用,尤其是在处理像 XSS 攻击这种安全问题时。通过 HttpServletRequestWrapper
或者其他类型的 Wrapper,可以在请求到达 Servlet 或 Controller 之前对其进行处理。这是因为 HttpServletRequest
类的设计原则是不可变的,因此需要通过包装类来对其进行增强或修改。
1. HttpServletRequest` 不可直接修改
HttpServletRequest
是一个接口,它提供了对 HTTP 请求的访问,例如获取请求参数、请求头、请求体等。这个接口的实现类(比如 HttpServletRequestImpl
)是由服务器提供的,我们无法直接修改这些实现类的行为,因为它们是由容器(如 Tomcat)提供的。
然而,在 Web 开发中,特别是涉及到 XSS 防护、输入验证、日志记录等需求时,我们需要在不修改原始请求对象的基础上对其进行增强或修改。此时,HttpServletRequestWrapper
类就提供了一个方便的解决方案。
HttpServletRequestWrapper
是 HttpServletRequest
接口的一个包装类,它允许开发人员重写 HttpServletRequest
的某些方法,从而修改请求数据(如参数、头信息等)。通过继承 HttpServletRequestWrapper
,我们可以对请求进行自定义处理。
2. 能够修改请求参数和请求体
过滤器的作用是拦截请求并执行一些操作,通常是在请求被实际处理之前。例如,过滤器可以用来检查请求参数、验证请求头、检查认证信息等。
但 HTTP 请求本身是不可变的(不可直接修改),因此我们需要用一个包装类来拦截和修改请求数据。通过 HttpServletRequestWrapper
,我们可以:
- 修改请求参数(如通过
getParameterValues
方法清理 XSS 攻击)。 - 修改请求体(例如,在
getInputStream
方法中过滤 JSON 数据)。
这样,我们可以在请求被实际处理之前对其进行修改,并确保这些修改对后续处理透明,不会破坏原本的请求流程。
3. 方便扩展与重用
通过继承 HttpServletRequestWrapper
,我们可以非常方便地创建自定义的包装器。例如,XssHttpServletRequestWrapper
就是一个典型的实现,它可以对请求的参数和请求体进行 XSS 清理。这种做法的好处是:
- 封装性:我们将 XSS 过滤的逻辑封装在一个类中,后续只需要在过滤器中应用这个包装类,就能实现统一的 XSS 防护。
- 可扩展性:如果以后需要增加其他类型的过滤器(例如 SQL 注入过滤、URL 编码检查等),我们只需要创建相应的
HttpServletRequestWrapper
的子类即可。
4. 处理请求参数和请求体
当我们处理请求参数时,HttpServletRequestWrapper
可以重写 getParameter
、getParameterValues
、getParameterMap
等方法。这使得我们可以拦截并修改这些参数,而不需要修改应用程序中处理这些参数的代码。
对于请求体,HttpServletRequestWrapper
也提供了重写 getInputStream()
和 getReader()
的能力。这使得我们能够修改请求体内容(比如过滤掉潜在的恶意脚本)。
5. 避免对原始请求做修改
通过使用 HttpServletRequestWrapper
,我们可以保留原始请求对象的所有功能,同时增强其安全性和可扩展性。HttpServletRequestWrapper
并不会修改原始的请求对象,而是返回一个新的对象来包装它。这样,过滤器在处理请求时可以安全地修改或增强请求的行为,而不影响其他组件或功能。
典型使用场景
- XSS 过滤:当请求包含用户输入(如 URL 参数、表单字段或请求体)时,可以通过包装器清理这些输入,防止恶意脚本执行。
- 日志记录:可以通过包装器记录请求的详细信息,比如请求参数、请求头等。
- 参数加密/解密:可以通过包装器在请求进入系统之前对敏感数据进行加密或解密。
举个例子
假设我们有一个需要进行 XSS 过滤的过滤器,可以通过 HttpServletRequestWrapper
来修改请求中的所有参数。
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
public XssHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public String[] getParameterValues(String name) {
String[] values = super.getParameterValues(name);
if (values != null) {
for (int i = 0; i < values.length; i++) {
// 进行 XSS 清理
values[i] = cleanXss(values[i]);
}
}
return values;
}
private String cleanXss(String value) {
// 对 value 进行 XSS 清理处理
return value.replaceAll("<", "<").replaceAll(">", ">");
}
}
然后在过滤器中,我们就可以使用这个包装类:
public class XssFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
XssHttpServletRequestWrapper wrappedRequest = new XssHttpServletRequestWrapper(httpRequest);
chain.doFilter(wrappedRequest, response);
}
}
在这种方式下,我们将 XSS 过滤的逻辑封装在 XssHttpServletRequestWrapper
中,而过滤器只是简单地应用它。这使得代码更加模块化和可维护。
总结
HttpServletRequestWrapper
允许我们在不修改原始请求对象的情况下对请求进行增强或处理。这使得它非常适合在过滤器中使用,特别是在需要拦截请求并处理安全问题(如 XSS、SQL 注入等)时。通过继承 HttpServletRequestWrapper
,我们可以灵活地修改请求数据、实现跨请求的安全措施,同时保持代码的清晰和可维护性。