原理是保存数据时对数据转义,把一些script的标签比如< >给去掉了,这些脚本就不能执行了
因为Hutool
工具包带有XSS转义的工具类,所以我们要导入Hutool
,然后利用Servlet
规范提供的请求包装类,定义数据转义功能。
<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.4.0</version> </dependency>
我们平时写Web项目遇到的HttpServletRequest
,它其实是个接口。如果我们想要重新定义请求类,扩展这个接口是最不应该的。因为HttpServletRequest
接口中抽象方法太多了,我们逐一实现起来太耗费时间。所以我们应该挑选一个简单一点的自定义请求类的方式。那就是继承HttpServletRequestWrapper
父类。
JavaEE只是一个标准,具体的实现由各家应用服务器厂商来完成。比如说Tomcat
在实现Servlet
规范的时候,就自定义了HttpServletRequest
接口的实现类。同时JavaEE规范还定义了HttpServletRequestWrapper
,这个类是请求类的包装类,用上了装饰器模式。不得不说这里用到的设计模式真的非常棒,无论各家应用服务器厂商怎么去实现HttpServletRequest
接口,用户想要自定义请求,只需要继承HttpServletRequestWrapper
,对应覆盖某个方法即可,然后把请求传入请求包装类,装饰器模式就会替代请求对象中对应的某个方法。用户的代码和服务器厂商的代码完全解耦,我们不用关心HttpServletRequest
接口是怎么实现的,借助于包装类我们可以随意修改请求中的方法。同学们,如此优雅的代码设计,有时间你真该认真学习设计模式。
如果把各个厂商的实现类都修改一遍显然是很冗余的,通过修改wrapper类把request传进来然后就可以对请求进行转义
package com.example.emos.wx.config.xss; import cn.hutool.core.util.StrUtil; import cn.hutool.http.HtmlUtil; import cn.hutool.json.JSONUtil; import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.io.*; import java.nio.charset.Charset; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper { public XssHttpServletRequestWrapper(HttpServletRequest request) { super(request); } @Override public String getParameter(String name) { String value = super.getParameter(name); if (!StrUtil.hasEmpty(value)) { value = HtmlUtil.filter(value); } return value; } @Override public String[] getParameterValues(String name) { String[] values = super.getParameterValues(name); if (values != null) { for (int i = 0; i < values.length; i++) { String value = values[i]; if (!StrUtil.hasEmpty(value)) { value = HtmlUtil.filter(value); } values[i] = value; } } return values; } @Override public Map<String, String[]> getParameterMap() { Map<String, String[]> parameters = super.getParameterMap(); Map<String, String[]> map = new LinkedHashMap<>(); if (parameters != null) { for (String key : parameters.keySet()) { String[] values = parameters.get(key); for (int i = 0; i < values.length; i++) { String value = values[i]; if (!StrUtil.hasEmpty(value)) { value = HtmlUtil.filter(value); } values[i] = value; } map.put(key, values); } } return map; } @Override public String getHeader(String name) { String value = super.getHeader(name); if (!StrUtil.hasEmpty(value)) { value = HtmlUtil.filter(value); } return value; } @Override public ServletInputStream getInputStream() throws IOException { InputStream in = super.getInputStream(); StringBuffer body = new StringBuffer(); InputStreamReader reader = new InputStreamReader(in, Charset.forName("UTF-8")); BufferedReader buffer = new BufferedReader(reader); String line = buffer.readLine(); while (line != null) { body.append(line); line = buffer.readLine(); } buffer.close(); reader.close(); in.close(); Map<String, Object> map = JSONUtil.parseObj(body.toString()); Map<String, Object> resultMap = new HashMap(map.size()); for (String key : map.keySet()) { Object val = map.get(key); if (map.get(key) instanceof String) { resultMap.put(key, HtmlUtil.filter(val.toString())); } else { resultMap.put(key, val); } } String str = JSONUtil.toJsonStr(resultMap); final ByteArrayInputStream bain = new ByteArrayInputStream(str.getBytes()); return new ServletInputStream() { @Override public int read() throws IOException { return bain.read(); } @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener listener) { } }; } }
为了让刚刚定义的包装类生效,我们还要在com.example.emos.wx.config.xss
中创建XssFilter
过滤器。过滤器拦截所有请求,然后把请求传入包装类,这样包装类就能覆盖所有请求的参数方法,用户从请求中获得数据,全都经过转义。
package com.example.emos.wx.config.xss; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import java.io.IOException; @WebFilter(urlPatterns = "/*") public class XssFilter implements Filter { public void init(FilterConfig config) throws ServletException { } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper( (HttpServletRequest) request); chain.doFilter(xssRequest, response); } @Override public void destroy() { } }
给SpringBoot主类添加@ServletComponentScan
注解。在SpringBootApplication上使用@ServletComponentScan注解后,Servlet、Filter、Listener可以直接通过@WebServlet、@WebFilter、@WebListener注解自动注册,无需其他代码。