springboot項(xiàng)目如何防止XSS攻擊
1. 什么是XSS攻擊?
XSS攻擊全稱(chēng)跨站腳本攻擊,是一種在web應(yīng)用中的計(jì)算機(jī)安全漏洞,它允許惡意web用戶(hù)將代碼植入到提供給其它用戶(hù)使用的頁(yè)面中。也就是作惡的用戶(hù)通過(guò)表單提交一些前端代碼,如果不做處理的話,這些前端代碼將會(huì)在展示的時(shí)候被瀏覽器執(zhí)行。
2. 如何防范?
有兩種方式,一種是一些特殊字符轉(zhuǎn)義,另一種是去除一些危險(xiǎn)html元素。本文通過(guò)JSOUP庫(kù)實(shí)現(xiàn)第二種方式。
現(xiàn)在問(wèn)題是,在什么時(shí)候?qū)SS攻擊的字符串進(jìn)行處理呢?這就涉及到spring mvc的Controller中的method params什么時(shí)候注入的問(wèn)題。下面對(duì)方法參數(shù)注入的原理進(jìn)行簡(jiǎn)單說(shuō)明。
2.1 什么時(shí)候注入請(qǐng)求參數(shù)
常見(jiàn)的控制器方法有下面幾種,分別是通過(guò)GET獲取請(qǐng)求參數(shù),POST獲取json或者form表單的參數(shù)
/**
* 通過(guò)url的path,獲取請(qǐng)求參數(shù)
*/
@ResponseBody
@GetMapping("/xssByGetUsingPath/{content}")
public String testGetUsingPath(@PathVariable(name = "content") String content) {
return content;
}
/**
* 通過(guò)url的query params獲取請(qǐng)求數(shù)據(jù). 方法使用簡(jiǎn)單類(lèi)型進(jìn)行接收
*/
@ResponseBody
@GetMapping("/xssByGetUsingSimple")
public String testUsingSimple(@RequestParam(name = "content") String content) {
return content;
}
/**
* 通過(guò)url的query params獲取請(qǐng)求數(shù)據(jù). 方法使用model進(jìn)行接收
*/
@ResponseBody
@GetMapping("/xssByGetUsingModel")
public String testGetUsingModel(BaseTest.Paper paper) {
return paper.getContent();
}
/**
* 通過(guò) form 表單的方式獲取數(shù)據(jù). 方法使用簡(jiǎn)單類(lèi)型進(jìn)行接收
*/
@ResponseBody
@PostMapping("/xssByFormPostUsingSimple")
public String testFormPostUsingSimple(@RequestParam(name = "content") String content) {
return content;
}
/**
* 通過(guò) form 表單的方式獲取數(shù)據(jù). 方法使用model進(jìn)行參數(shù)接收
*/
@ResponseBody
@PostMapping("/xssByFormPostUsingModel")
public String testFormPostUsingModel(BaseTest.Paper paper) {
return paper.getContent();
}
/**
* 通過(guò) request body 發(fā)送 json數(shù)據(jù)
*/
@ResponseBody
@PostMapping("/xssByPostJsonBody")
public String testPostJsonBody(@RequestBody BaseTest.Paper paper) {
return paper.getContent();
}
大家都知道,在spring mvc中處理請(qǐng)求的入口在 DispatcherServlet 類(lèi)中,其中 doDispatch() 方法完成所有核心功能。在該主流程中,HandlerAdapter 將會(huì)對(duì)HTTP請(qǐng)求進(jìn)行 Controller 的方法調(diào)用,以及對(duì)請(qǐng)求結(jié)果進(jìn)行轉(zhuǎn)換,并封裝為DispatcherServlet 類(lèi)需要的 ModelAndView 。在這里,由于使用注解的方式進(jìn)行 Controller 定義,所以 HandlerAdapter 的實(shí)現(xiàn)類(lèi)為 RequestMappingHandlerAdapter 。RequestMappingHandlerAdapter 類(lèi)的 handle() 方法中,委托給 HandlerMethodArgumentResolver 對(duì)每個(gè) Controller 的 方法的每個(gè)參數(shù)進(jìn)行解析,反射調(diào)用 Controller 的 方法后,再 委托 HandlerMethodReturnValueHandler 對(duì)反射調(diào)用的返回值進(jìn)行處理。
至此,本文的主角出現(xiàn)了,也就是 HandlerMethodArgumentResolver 。我們看下有關(guān)于本文的HandlerMethodArgumentResolver 類(lèi)繼承關(guān)系。

上面的常見(jiàn)controller定義方法的 參數(shù)解析(注意,這里是說(shuō)方法那里的參數(shù)解析) 對(duì)應(yīng) HandlerMethodArgumentResolver 的關(guān)系如下圖:

現(xiàn)在,我們已經(jīng)知道了他們都由什么樣 HandlerMethodArgumentResolver 解析方法參數(shù),我們繼續(xù)分析他們之前的共同點(diǎn),并得到在哪里對(duì)http傳過(guò)來(lái)的數(shù)據(jù)進(jìn)行XSS處理。
RequestResponseBodyMethodProcessor 是一個(gè)對(duì)request body的JSON數(shù)據(jù)反序列化的處理器,在 resolveArgument() 方法中,將會(huì)獲取合適的 org.springframework.http.converter.HttpMessageConverter 類(lèi)對(duì)string數(shù)據(jù)進(jìn)行反序列化處理。springboot 在初始化的時(shí)候,已經(jīng)默認(rèn)注冊(cè)了 jackson 的 MappingJackson2HttpMessageConverter ,并使用它對(duì) JSON 數(shù)據(jù)進(jìn)行反序列化操作。在 jackson 里面,JsonDeserializer 類(lèi) 用于json數(shù)據(jù)的property的反序列化,因此,我們可以通過(guò)擴(kuò)展 JsonDeserializer ,并在里面處理XSS即可。
對(duì)于 PathVariableMethodArgumentResolver、 RequestParamMethodArgumentResolver、 ServletModelAttributeMethodProcessor ,在 resolveArgument() 方法中,他們對(duì)需要對(duì)請(qǐng)求參數(shù)調(diào)用 DataBinder 類(lèi) 對(duì)獲取到的參數(shù)類(lèi)型轉(zhuǎn)換和數(shù)據(jù)綁定。對(duì)于類(lèi)型轉(zhuǎn)換的過(guò)程,他們會(huì)使用 org.springframework.core.convert.converter.Converter 進(jìn)行轉(zhuǎn)換。同樣,在SPRINGBOOT初始化的過(guò)程,也注冊(cè)了很多個(gè)默認(rèn)的轉(zhuǎn)換器,我們可以注冊(cè)一個(gè)自定義轉(zhuǎn)換器,用于對(duì)數(shù)據(jù)進(jìn)行xss處理。
3. 具體處理細(xì)節(jié)
抽象XSSCleaner,用于對(duì)string進(jìn)行XSS處理
public interface XssCleaner {
/**
* 清理 html, 防止XSS
*
* @param html html
* @return 清理后的數(shù)據(jù)
*/
String clean(String html);
}
使用JSOUP,做HTML節(jié)點(diǎn)的白名單處理
public class DefaultXssCleaner implements XssCleaner {
public static final HtmlWhitelist WHITE_LIST = new HtmlWhitelist();
@Override
public String clean(String html) {
if (StringUtils.hasText(html)) {
return Jsoup.clean(html, WHITE_LIST);
}
return html;
}
private static class HtmlWhitelist extends Whitelist {
public HtmlWhitelist() {
//定義標(biāo)簽和屬性的白名單
addTags("a", "b", "blockquote", "br", "caption", "cite", "code", "col", "colgroup", "dd", "div", "span", "embed", "object", "dl", "dt",
"em", "h1", "h2", "h3", "h4", "h5", "h6", "i", "img", "li", "ol", "p", "pre", "q", "small",
"strike", "strong", "sub", "sup", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "u", "ul");
addAttributes("a", "href", "title", "target");
addAttributes("blockquote", "cite");
addAttributes("col", "span");
addAttributes("colgroup", "span");
addAttributes("img", "align", "alt", "src", "title");
addAttributes("ol", "start");
addAttributes("q", "cite");
addAttributes("table", "summary");
addAttributes("td", "abbr", "axis", "colspan", "rowspan", "width");
addAttributes("th", "abbr", "axis", "colspan", "rowspan", "scope", "width");
addAttributes("video", "src", "autoplay", "controls", "loop", "muted", "poster", "preload");
addAttributes("object", "width", "height", "classid", "codebase");
addAttributes("param", "name", "value");
addAttributes("embed", "src", "quality", "width", "height", "allowFullScreen", "allowScriptAccess", "flashvars", "name", "type", "pluginspage");
addAttributes(":all", "class", "style", "height", "width", "type", "id", "name");
addProtocols("blockquote", "cite", "http", "https");
addProtocols("cite", "cite", "http", "https");
addProtocols("q", "cite", "http", "https");
}
@Override
protected boolean isSafeAttribute(String tagName, Element el, Attribute attr) {
//不允許 javascript 開(kāi)頭的 src 和 href
if ("src".equalsIgnoreCase(attr.getKey()) || "href".equalsIgnoreCase(attr.getKey())) {
String value = attr.getValue();
if (StringUtils.hasText(value) && value.toLowerCase().startsWith("javascript")) {
return false;
}
}
//允許 base64 的圖片內(nèi)容
if ("img".equals(tagName) && "src".equals(attr.getKey()) && attr.getValue().startsWith("data:;base64")) {
return true;
}
return super.isSafeAttribute(tagName, el, attr);
}
}
}
新增一個(gè)jackson的JsonDeserializer
/**
* jackson的反序列化時(shí)的html xss過(guò)濾器
*/
public class JacksonXssCleanJsonDeserializer extends JsonDeserializer<String> {
private final static Logger LOGGER = LoggerFactory.getLogger(JacksonXssCleanJsonDeserializer.class);
private final XssCleaner xssCleaner;
public JacksonXssCleanJsonDeserializer(XssCleaner xssCleaner) {
this.xssCleaner = xssCleaner;
}
@Override
public Class<?> handledType() {
return String.class;
}
@Override
public String deserialize(JsonParser p, DeserializationContext context) throws IOException, JsonProcessingException {
// XSS clean
String text = p.getValueAsString();
if (StringUtils.hasText(text) && XssCleanMarker.shouldClean()) {
String cleanText = xssCleaner.clean(text);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Json property value: [{}] cleaned up by JacksonXssCleanJsonDeserializer, current value is:{}.", text, cleanText);
}
return cleanText;
}
return text;
}
}
新增 Converter
/**
* 對(duì)請(qǐng)求數(shù)據(jù)過(guò)濾xss
*/
public class XssCleanConverter implements Converter<String, String> {
private final Logger LOGGER = LoggerFactory.getLogger(XssCleanConverter.class);
private XssCleaner xssCleaner;
public XssCleanConverter(XssCleaner xssCleaner) {
this.xssCleaner = xssCleaner;
}
@Override
public String convert(String text) {
if (StringUtils.hasText(text) && XssCleanMarker.shouldClean()) {
String cleanText = xssCleaner.clean(text);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("request param [{}] cleaned up by XssCleanConverter, current value is:{}.", text, cleanText);
}
return cleanText;
}
return text;
}
}
對(duì) JsonDeserializer 和 Converter 進(jìn)行注冊(cè)
@Configuration
@ConditionalOnProperty(value = XSS_PROPERTIES_ENABLED, havingValue = "true", matchIfMissing = true)
@ConditionalOnWebApplication
@EnableConfigurationProperties({XssProperties.class})
public class WebXssConfiguration implements WebMvcConfigurer {
private XssProperties properties;
public WebXssConfiguration(XssProperties properties) {
this.properties = properties;
}
@Bean
@ConditionalOnMissingBean
public XssCleaner xssCleaner() {
return new DefaultXssCleaner();
}
@Bean
@ConditionalOnClass(value = {ObjectMapper.class, Jackson2ObjectMapperBuilder.class})
public Jackson2ObjectMapperBuilderCustomizer jacksonXssCleanJsonDeserializerCustomer(XssCleaner xssCleaner) {
return builder -> builder.deserializers(new JacksonXssCleanJsonDeserializer(xssCleaner));
}
@Override
public void addFormatters(FormatterRegistry registry) {
XssCleaner xssCleaner = xssCleaner();
registry.addConverter(new XssCleanConverter(xssCleaner));
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
XssCleanMarkerHandlerInterceptor handlerInterceptor = new XssCleanMarkerHandlerInterceptor(properties);
registry.addInterceptor(handlerInterceptor);
}
}
上面是XSS處理的核心代碼。處理XSS處理,還進(jìn)行了一些擴(kuò)展,比如 http path 路徑的過(guò)濾 和 一些使能控制。
完整的代碼可以參考倉(cāng)庫(kù):倉(cāng)庫(kù)地址
以上就是springboot項(xiàng)目如何防止XSS攻擊的詳細(xì)內(nèi)容,更多關(guān)于springboot防止XSS攻擊的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
win10安裝JDK14.0.2的詳細(xì)安裝過(guò)程
這篇文章主要介紹了win10安裝JDK14.0.2的詳細(xì)安裝過(guò)程的相關(guān)資料,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09
SpringBoot靜態(tài)方法調(diào)用Spring容器bean的三種解決方案
在SpringBoot中靜態(tài)方法調(diào)用Spring容器bean時(shí)出現(xiàn)的null值問(wèn)題,本文就來(lái)介紹一下SpringBoot靜態(tài)方法調(diào)用Spring容器bean的三種解決方案,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2025-01-01
教你用Springboot實(shí)現(xiàn)攔截器獲取header內(nèi)容
項(xiàng)目中遇到一個(gè)需求,對(duì)接上游系統(tǒng)是涉及到需要增加請(qǐng)求頭,請(qǐng)求頭的信息是動(dòng)態(tài)獲取的,需要?jiǎng)討B(tài)從下游拿到之后轉(zhuǎn)給上游,文中非常詳細(xì)的介紹了該需求的實(shí)現(xiàn),需要的朋友可以參考下2021-05-05
SpringBoot獲取maven打包時(shí)間的兩種方式
這篇文章主要介紹了SpringBoot獲取maven打包時(shí)間的兩種方式,文章通過(guò)代碼示例給大家講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2024-05-05
Spring-IOC容器-Bean管理-基于XML方式超詳解
這篇文章主要介紹了Spring為IOC容器Bean的管理,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2021-08-08

