前言
項(xiàng)目安全需要, 需要全局對(duì)參數(shù)進(jìn)行xss過(guò)濾處理.
Xss簡(jiǎn)介
關(guān)于Xss很多人可能都有了解, 出于“禮貌”,
咸魚(yú)君還是簡(jiǎn)單舉個(gè)例子
用戶注冊(cè)時(shí)可以填寫(xiě)姓名
此時(shí)我填寫(xiě)了“<script>alert(1);</script>” 并提交,
后端呢沒(méi)有做任何檢測(cè)就保存了.
那么就可能有問(wèn)題, 下次再訪問(wèn)這個(gè)頁(yè)面你會(huì)發(fā)現(xiàn)不停的彈窗“1”!
Xss攻擊呢也分很多種, 感興趣的自行查閱資料, 這里不多說(shuō)了.
那么如何面對(duì)Xss攻擊呢?
其實(shí)也很好解決, 一句話“不要相信用戶的任何輸入”!
編程上就是對(duì)用戶的提交內(nèi)容進(jìn)行過(guò)濾以及非法字符的“轉(zhuǎn)義”!
Springboot Xss攔截器實(shí)現(xiàn)
對(duì)整個(gè)系統(tǒng)的提交進(jìn)行過(guò)濾和轉(zhuǎn)義, 如過(guò)針對(duì)每個(gè)點(diǎn)分別調(diào)用某個(gè)方法去做這件事, 會(huì)顯得很麻煩, 而且, 重復(fù)的代碼看著也不美觀, 所以, 這里就采用Springboot+Filter的方式實(shí)現(xiàn)一個(gè)Xss的全局過(guò)濾器.
Springboot實(shí)現(xiàn)一個(gè)Xss過(guò)濾器, 常用的有兩種方式:
- 重寫(xiě)HttpServletRequestWrapper
重寫(xiě)getHeader()、getParameter()、getParameterValues()、getInputStream()實(shí)現(xiàn)對(duì)傳統(tǒng)“鍵值對(duì)”傳參方式的過(guò)濾
重寫(xiě)getInputStream()實(shí)現(xiàn)對(duì)Json方式傳參的過(guò)濾,也就是@RequestBody參數(shù).
- 自定義序列化器, 對(duì)MappingJackson2HttpMessageConverter 的objectMapper做設(shè)置.
重寫(xiě)JsonSerializer.serialize()實(shí)現(xiàn)對(duì)出參的過(guò)濾 (PS: 數(shù)據(jù)原樣保存, 取出來(lái)的時(shí)候轉(zhuǎn)義)
重寫(xiě)JsonDeserializer.deserialize()實(shí)現(xiàn)對(duì)入?yún)⒌倪^(guò)濾 (PS: 數(shù)據(jù)轉(zhuǎn)義后保存)
針對(duì)以上兩種方式有幾個(gè)注意點(diǎn):
問(wèn)題一: json參數(shù)(@RequestBody)的處理
針對(duì)Json方式傳參 ,
springmvc默認(rèn)使用jackjson做序列化.
重寫(xiě)getInputStream()來(lái)實(shí)現(xiàn)xss過(guò)濾時(shí),
如果你對(duì)參數(shù)替換了雙引號(hào), jackjson序列/反序列化參數(shù)時(shí)會(huì)報(bào)錯(cuò),
因?yàn)樗徽J(rèn)識(shí)這個(gè)格式!
所以我們對(duì)參數(shù)處理時(shí)要剔除雙引號(hào)!
或者, 你選擇自定義序列化器來(lái)實(shí)現(xiàn)json參數(shù)的處理.
問(wèn)題二: 自定義序列化器
雖然我們?nèi)雲(yún)⒑统鰠煞N方式都寫(xiě)了案例,
實(shí)際上只需要選擇其一來(lái)做即可!沒(méi)必要兩種都做過(guò)濾.
這里推薦對(duì)入?yún)⑦M(jìn)行處理, 理由如下:
重寫(xiě)getHeader()、getParameter()、getParameterValues()是直接對(duì)入?yún)⑦M(jìn)行轉(zhuǎn)義,
也就是你后續(xù)程序獲取的參數(shù)都是轉(zhuǎn)義后的,
所以, 為了全局統(tǒng)一, 我們對(duì)@RequestBody類型的參數(shù)也進(jìn)行入?yún)⑻幚?對(duì)入?yún)⑥D(zhuǎn)義意味著保存進(jìn)DB的就是“安全”的數(shù)據(jù)
本次案例實(shí)現(xiàn)原理如下:
- 針對(duì)“鍵值對(duì)”參數(shù)采用重寫(xiě)HttpServletRequestWrapper過(guò)濾
- 針對(duì)json參數(shù)采用自定義序列化器來(lái)過(guò)濾
PS: 針對(duì)json參數(shù)也實(shí)現(xiàn)了getInputStream()方式的過(guò)濾(代碼為注釋狀態(tài))
實(shí)現(xiàn)
首先, 我們先引入一個(gè)工具包, 就是大名鼎鼎的“糊涂”工具包, 該包集成了大量的好用的工具!強(qiáng)烈推薦使用!
<!--HuTool工具包 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.2.3</version>
</dependency>
我們用到HuTool內(nèi)的EscapeUtil這個(gè)工具來(lái)轉(zhuǎn)義特殊字符.
使用HttpServletRequestWrapper重寫(xiě)Request請(qǐng)求參數(shù)
package com.mrcoder.sbxssfilter.config.xss;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.EscapeUtil;
import cn.hutool.core.util.StrUtil;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* XSS過(guò)濾處理
*/
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
/**
* 描述 : 構(gòu)造函數(shù)
*
* @param request 請(qǐng)求對(duì)象
*/
public XssHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public String getHeader(String name) {
String value = super.getHeader(name);
return EscapeUtil.escape(value);
}
//重寫(xiě)getParameter
@Override
public String getParameter(String name) {
String value = super.getParameter(name);
return EscapeUtil.escape(value);
}
//重寫(xiě)getParameterValues
@Override
public String[] getParameterValues(String name) {
String[] values = super.getParameterValues(name);
if (values != null) {
int length = values.length;
String[] escapseValues = new String[length];
for (int i = 0; i < length; i++) {
escapseValues[i] = EscapeUtil.escape(values[i]);
}
return escapseValues;
}
return super.getParameterValues(name);
}
// //重寫(xiě)getInputStream,對(duì)json格式參數(shù)進(jìn)行過(guò)濾(也就是@RequestBody類型的參數(shù))
// @Override
// public ServletInputStream getInputStream() throws IOException {
// // 非json類型,直接返回
// if (!super.getHeader(HttpHeaders.CONTENT_TYPE).equalsIgnoreCase(MediaType.APPLICATION_JSON_VALUE)) {
// return super.getInputStream();
// }
//
// // 為空,直接返回
// String json = IoUtil.read(super.getInputStream(), "utf-8");
// if (StrUtil.isEmpty(json)) {
// return super.getInputStream();
// }
// // 這里要注意,json格式的參數(shù)不能直接使用hutool的EscapeUtil.escape, 因?yàn)樗鼤?huì)把"也給轉(zhuǎn)義,
// // 使得@RequestBody沒(méi)辦法解析成為一個(gè)正常的對(duì)象,所以我們自己實(shí)現(xiàn)一個(gè)過(guò)濾方法
// // 或者采用定制自己的objectMapper處理json出入?yún)⒌霓D(zhuǎn)義(推薦使用)
// json = cleanXSS(json).trim();
// final ByteArrayInputStream bis = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8));
// return new ServletInputStream() {
// @Override
// public boolean isFinished() {
// return true;
// }
//
// @Override
// public boolean isReady() {
// return true;
// }
//
// @Override
// public void setReadListener(ReadListener readListener) {
// }
//
// @Override
// public int read() {
// return bis.read();
// }
// };
// }
//
// public static String cleanXSS(String value) {
// value = value.replaceAll("&", "%26");
// value = value.replaceAll("<", "%3c");
// value = value.replaceAll(">", "%3e");
// value = value.replaceAll("'", "%27");
// //value = value.replaceAll(":", "%3a");
// //value = value.replaceAll("\"", "%22");
// //value = value.replaceAll("/", "%2f");
// return value;
// }
}
實(shí)現(xiàn)一個(gè)過(guò)濾器
package com.mrcoder.sbxssfilter.config.xss;
import cn.hutool.core.util.StrUtil;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 防止XSS攻擊的過(guò)濾器
*/
@Component
public class XssFilter implements Filter {
/**
* 排除鏈接
*/
private List<String> excludes = new ArrayList<>();
/**
* xss過(guò)濾開(kāi)關(guān)
*/
private boolean enabled = false;
@Override
public void init(FilterConfig filterConfig) {
String tempExcludes = filterConfig.getInitParameter("excludes");
String tempEnabled = filterConfig.getInitParameter("enabled");
if (StrUtil.isNotEmpty(tempExcludes)) {
String[] url = tempExcludes.split(",");
Collections.addAll(excludes, url);
}
if (StrUtil.isNotEmpty(tempEnabled)) {
enabled = Boolean.valueOf(tempEnabled);
}
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
if (handleExcludeUrl(req)) {
chain.doFilter(request, response);
return;
}
XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request);
chain.doFilter(xssRequest, response);
}
/**
* 判斷當(dāng)前路徑是否需要過(guò)濾
*/
private boolean handleExcludeUrl(HttpServletRequest request) {
if (!enabled) {
return true;
}
if (excludes == null || excludes.isEmpty()) {
return false;
}
String url = request.getServletPath();
for (String pattern : excludes) {
Pattern p = Pattern.compile("^" + pattern);
Matcher m = p.matcher(url);
if (m.find()) {
return true;
}
}
return false;
}
}
配置并注冊(cè)過(guò)濾器
package com.mrcoder.sbxssfilter.config.xss;
import javax.servlet.DispatcherType;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import java.util.HashMap;
import java.util.Map;
/**
* @Author xssfilter配置
*/
@Configuration
public class XssFilterConfig {
@Value("${xss.enabled}")
private String enabled;
@Value("${xss.excludes}")
private String excludes;
@Value("${xss.urlPatterns}")
private String urlPatterns;
@SuppressWarnings({"rawtypes", "unchecked"})
@Bean
public FilterRegistrationBean xssFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setDispatcherTypes(DispatcherType.REQUEST);
registration.setFilter(new XssFilter());
//添加過(guò)濾路徑
registration.addUrlPatterns(StrUtil.split(urlPatterns, ","));
registration.setName("xssFilter");
registration.setOrder(Integer.MAX_VALUE);
//設(shè)置初始化參數(shù)
Map<String, String> initParameters = new HashMap<String, String>();
initParameters.put("excludes", excludes);
initParameters.put("enabled", enabled);
registration.setInitParameters(initParameters);
return registration;
}
/**
* 過(guò)濾json類型的
*
* @param builder
* @return
*/
@Bean
@Primary
public ObjectMapper xssObjectMapper(Jackson2ObjectMapperBuilder builder) {
//解析器
ObjectMapper objectMapper = builder.createXmlMapper(false).build();
//注冊(cè)xss解析器
SimpleModule xssModule = new SimpleModule("XssStringJsonDeserializer");
//入?yún)⒑统鰠⑦^(guò)濾選一個(gè)就好了,沒(méi)必要兩個(gè)都加
//這里為了和XssHttpServletRequestWrapper統(tǒng)一,建議對(duì)入?yún)⑦M(jìn)行處理
//注冊(cè)入?yún)⑥D(zhuǎn)義
xssModule.addDeserializer(String.class, new XssStringJsonDeserializer());
//注冊(cè)出參轉(zhuǎn)義
//xssModule.addSerializer(new XssStringJsonSerializer());
objectMapper.registerModule(xssModule);
//返回
return objectMapper;
}
}
最后,我們?cè)谂渲梦募由?/p>
#是否打開(kāi)
xss.enabled=true
#不過(guò)濾路徑, 以逗號(hào)分割
xss.excludes=/open/*,/open2/*
//過(guò)濾路徑, 逗號(hào)分割
xss.urlPatterns=/*
另外, 我們需要實(shí)現(xiàn)一個(gè)ObjectMapper來(lái)處理json格式的參數(shù)
處理json入?yún)⒌霓D(zhuǎn)義
package com.mrcoder.sbxssfilter.config.xss;
import cn.hutool.core.util.EscapeUtil;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import java.io.IOException;
/**
* 處理json入?yún)⒌霓D(zhuǎn)義
*/
public class XssStringJsonDeserializer extends JsonDeserializer<String> {
@Override
public Class<String> handledType() {
return String.class;
}
//對(duì)入?yún)⑥D(zhuǎn)義
@Override
public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
String value = jsonParser.getText();
if (value != null) {
return EscapeUtil.escape(value);
}
return value;
}
}
處理json出參的轉(zhuǎn)義
package com.mrcoder.sbxssfilter.config.xss;
import cn.hutool.core.util.EscapeUtil;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
/**
* 處理json出參的轉(zhuǎn)義
*/
public class XssStringJsonSerializer extends JsonSerializer<String> {
@Override
public Class<String> handledType() {
return String.class;
}
//對(duì)出參轉(zhuǎn)義
@Override
public void serialize(String value, JsonGenerator jsonGenerator,
SerializerProvider serializerProvider) throws IOException {
if (value != null) {
String encodedValue = EscapeUtil.escape(value);
jsonGenerator.writeString(encodedValue);
}
}
}
寫(xiě)個(gè)測(cè)試
package com.mrcoder.sbxssfilter.model;
import lombok.Getter;
import lombok.Setter;
@Setter
@Getter
public class People {
private String name;
private String info;
}
package com.mrcoder.sbxssfilter.controller;
import com.mrcoder.sbxssfilter.model.People;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
public class XssController {
//鍵值對(duì)
@PostMapping("xssFilter")
public String xssFilter(String name, String info) {
log.error(name + "---" + info);
return name + "---" + info;
}
//實(shí)體
@PostMapping("modelXssFilter")
public People modelXssFilter(@RequestBody People people) {
log.error(people.getName() + "---" + people.getInfo());
return people;
}
//不轉(zhuǎn)義
@PostMapping("open/xssFilter")
public String openXssFilter(String name) {
return name;
}
//不轉(zhuǎn)義2
@PostMapping("open2/xssFilter")
public String open2XssFilter(String name) {
return name;
}
}
json方式

鍵值對(duì)方式

項(xiàng)目地址
https://github.com/MrCoderStack/SpringBootDemo/tree/master/sb-xssfilter
請(qǐng)關(guān)注我的訂閱號(hào)
