springboot xss防御配置
注意:
这个xss过滤器有一些问题,比如某些时候,使用jquery ajax post的时候,如果是传的默认的 "application/x-www-form-urlencoded",会出现Controller中的bean接收的值为null的情况,
当我调整ajax设置 contentType:"application/json",dataType:"json" 发送一个带post json数据时,xss拦截器报错,于是删除整个xss包,不再使用这四个类,jquery ajax post一切正常,可以以默认方式提交。(之前只能用axios去拼接参数到url里来实现post(将拼接的参数存入一个变量))
总共4个类,放在config.safe.xss包下了:
package org.kosoku.commonfast.config.safe.xss; import javax.servlet.ServletRequest; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.Charset; public class HttpGetBody { /** * 获取请求Body * * @param request * @return */ public static String getBodyString(ServletRequest request) { //StringBuilder sb = new StringBuilder(); StringBuffer sb = new StringBuffer(); InputStream inputStream = null; BufferedReader reader = null; try { inputStream = request.getInputStream(); reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8"))); String line = ""; while ((line = reader.readLine()) != null) { sb.append(line); } } catch (IOException e) { e.printStackTrace(); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } if (reader != null) { try { reader.close(); } catch (IOException e) { e.printStackTrace(); } } } return sb.toString(); } }
package org.kosoku.commonfast.config.safe.xss; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import java.io.IOException; public class XssFilter implements Filter { @Override public void init(FilterConfig config) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { XssHttpServletRequestWrapper xssHttpServletRequestWrapper = new XssHttpServletRequestWrapper((HttpServletRequest) request); chain.doFilter(xssHttpServletRequestWrapper, response); } @Override public void destroy() { } }
package org.kosoku.commonfast.config.safe.xss; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.servlet.Filter; @Configuration public class XSSFilterConfig { @Bean public FilterRegistrationBean filterRegistrationBean() { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(xssFilter()); registration.addUrlPatterns("/*"); registration.addInitParameter("paramName", "paramValue"); registration.setName("xssFilter"); return registration; } /** * 创建一个bean * * @return */ @Bean(name = "xssFilter") public Filter xssFilter() { return new XssFilter(); } }
package org.kosoku.commonfast.config.safe.xss; import org.apache.commons.lang.StringUtils; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.Iterator; import java.util.Map; public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper { HttpServletRequest orgRequest = null; private String body; public XssHttpServletRequestWrapper(HttpServletRequest request) { super(request); orgRequest = request; body = HttpGetBody.getBodyString(request); } /** * 覆盖getParameter方法,将参数名和参数值都做xss过滤。<br/> * 如果需要获得原始的值,则通过super.getParameterValues(name)来获取<br/> * getParameterNames,getParameterValues和getParameterMap也可能需要覆盖 */ @Override public String getParameter(String name) { String value = super.getParameter(xssEncode(name, 0)); if (null != value) { value = xssEncode(value, 0); } return value; } @Override public String[] getParameterValues(String name) { String[] values = super.getParameterValues(xssEncode(name, 0)); if (values == null) { return null; } int count = values.length; String[] encodedValues = new String[count]; for (int i = 0; i < count; i++) { encodedValues[i] = xssEncode(values[i], 0); } return encodedValues; } @Override public Map getParameterMap() { HashMap paramMap = (HashMap) super.getParameterMap(); paramMap = (HashMap) paramMap.clone(); for (Iterator iterator = paramMap.entrySet().iterator(); iterator.hasNext(); ) { Map.Entry entry = (Map.Entry) iterator.next(); String[] values = (String[]) entry.getValue(); for (int i = 0; i < values.length; i++) { if (values[i] instanceof String) { values[i] = xssEncode(values[i], 0); } } entry.setValue(values); } return paramMap; } @Override public ServletInputStream getInputStream() throws IOException { ServletInputStream inputStream = null; if (StringUtils.isNotEmpty(body)) { body = xssEncode(body, 1); InputStream is = new ByteArrayInputStream(body.getBytes("UTF-8")); inputStream = (ServletInputStream) is; } return inputStream; } /** * 覆盖getHeader方法,将参数名和参数值都做xss过滤。<br/> * 如果需要获得原始的值,则通过super.getHeaders(name)来获取<br/> * getHeaderNames 也可能需要覆盖 */ @Override public String getHeader(String name) { String value = super.getHeader(xssEncode(name, 0)); if (value != null) { value = xssEncode(value, 0); } return value; } /** * 将容易引起xss漏洞的半角字符直接替换成全角字符 * * @param s * @return */ private static String xssEncode(String s, int type) { if (s == null || s.isEmpty()) { return s; } StringBuilder sb = new StringBuilder(s.length() + 16); for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); if (type == 0) { switch (c) { case '\'': // 全角单引号 sb.append('‘'); break; case '\"': // 全角双引号 sb.append('“'); break; case '>': // 全角大于号 sb.append('>'); break; case '<': // 全角小于号 sb.append('<'); break; case '&': // 全角&符号 sb.append('&'); break; case '\\': // 全角斜线 sb.append('\'); break; case '#': // 全角井号 sb.append('#'); break; // < 字符的 URL 编码形式表示的 ASCII 字符(十六进制格式) 是: %3c case '%': processUrlEncoder(sb, s, i); break; default: sb.append(c); break; } } else { switch (c) { case '>': // 全角大于号 sb.append('>'); break; case '<': // 全角小于号 sb.append('<'); break; case '&': // 全角&符号 sb.append('&'); break; case '\\': // 全角斜线 sb.append('\'); break; case '#': // 全角井号 sb.append('#'); break; // < 字符的 URL 编码形式表示的 ASCII 字符(十六进制格式) 是: %3c case '%': processUrlEncoder(sb, s, i); break; default: sb.append(c); break; } } } return sb.toString(); } public static void processUrlEncoder(StringBuilder sb, String s, int index) { if (s.length() >= index + 2) { // %3c, %3C if (s.charAt(index + 1) == '3' && (s.charAt(index + 2) == 'c' || s.charAt(index + 2) == 'C')) { sb.append('<'); return; } // %3c (0x3c=60) if (s.charAt(index + 1) == '6' && s.charAt(index + 2) == '0') { sb.append('<'); return; } // %3e, %3E if (s.charAt(index + 1) == '3' && (s.charAt(index + 2) == 'e' || s.charAt(index + 2) == 'E')) { sb.append('>'); return; } // %3e (0x3e=62) if (s.charAt(index + 1) == '6' && s.charAt(index + 2) == '2') { sb.append('>'); return; } } sb.append(s.charAt(index)); } /** * 获取最原始的request * * @return */ public HttpServletRequest getOrgRequest() { return orgRequest; } /** * 获取最原始的request的静态方法 * * @return */ public static HttpServletRequest getOrgRequest(HttpServletRequest req) { if (req instanceof XssHttpServletRequestWrapper) { return ((XssHttpServletRequestWrapper) req).getOrgRequest(); } return req; } }