對(duì)于CSRF,可能一些朋友比較陌生。我們下面先簡(jiǎn)單介紹下。
什么是CSRF呢,我們看下Wikipedia的說(shuō)明:
Cross-site request forgery,即跨站請(qǐng)求偽造,也稱為 "One Click Attach" 或者"Session Riding",??s寫成CSRF。是通過(guò)偽裝來(lái)自受信任用戶的請(qǐng)求來(lái)利用受信任的網(wǎng)站。
其中,說(shuō)起CSRF,經(jīng)常會(huì)舉的一個(gè)例子,是這樣的:
用戶A在訪問(wèn)網(wǎng)上銀行,在銀行網(wǎng)站里進(jìn)行了一些操作后。
之后點(diǎn)擊了一個(gè)陌生的鏈接。而這個(gè)鏈接,是用戶B提供的一個(gè)惡意網(wǎng)頁(yè)W,網(wǎng)頁(yè)W內(nèi)也包含訪問(wèn)和用戶A相同銀行信息的鏈接,可能是轉(zhuǎn)帳,也可能是修改密碼。
此時(shí)如果A的認(rèn)證信息還未過(guò)期,就會(huì)被直接利用,成功幫助W進(jìn)行了銀行網(wǎng)站的對(duì)應(yīng)操作,而這一切,都是在A不知情的情況下進(jìn)行的。
為了防范CSRF,常見(jiàn)的方式有:
請(qǐng)求中包含隨機(jī)token信息
Cookie中包含csrf token信息
其他的驗(yàn)證請(qǐng)求頭Refer等...
在Tomcat中,默認(rèn)提供了一個(gè)防范CSRF的好工具: CSRF Prevention Filter。
Tomcat默認(rèn)提供了各類的Filter,處理不同的場(chǎng)景和需求。今天介紹的CSRF Prevention Filter也是其中的一個(gè)。
整個(gè)Filter的工作流程可以概括成以下內(nèi)容:
該Filter為Web應(yīng)用提供了基本的CSRF 保護(hù)。它的filter mapping對(duì)應(yīng)到/*
并且所有返回到頁(yè)面上的鏈接,都通過(guò)調(diào)用HttpServletResponse#encodeRedirectURL(String) 或者 HttpServletResponse#encodeURL(String)進(jìn)行編碼。實(shí)現(xiàn)機(jī)制是生成一個(gè)token并且將其保存到session中,URL的encode也使用同樣的token,當(dāng)請(qǐng)求到達(dá)時(shí),會(huì)比較請(qǐng)求中的token和session中的token是否一致,只有相同的才允許繼續(xù)執(zhí)行。
我們通過(guò)一個(gè)例子,深入源碼,來(lái)了解下內(nèi)部的實(shí)現(xiàn)細(xì)節(jié)。
還是使用Tomcat自帶的Manager應(yīng)用來(lái)看下。
在其web.xml中,有這樣的配置:
下面的內(nèi)容是CsrfPreventionFilter的doFilter方法,
我們注意到前面的配置里包含一個(gè)entryPoints,對(duì)照代碼,馬上就能明白,這項(xiàng)配置用來(lái)做類似于exclude的功能,在配置中的映射,可以跳過(guò)檢查。
而如果沒(méi)有在entryPoints中,同時(shí)在session存在,但不包含對(duì)應(yīng)的Nonce,就會(huì)直接返回403(SC_FORBIDDEN)。
如果session不存在,就會(huì)在doFilter中走到下面的內(nèi)容:
做為初次請(qǐng)求,會(huì)在session中保存對(duì)應(yīng)的Attribute,同時(shí)添加到一個(gè)LruCache中。這里的重點(diǎn)在于,使用了HttpServletResponseWrapper的子類CsrfResponseWrapper替換了它做為response傳入后續(xù)的流程。
所以,后面所有調(diào)用encodeUrl的地方,其實(shí)實(shí)際調(diào)用到的是這個(gè):
public String encodeURL(String url) {
return addNonce(super.encodeURL(url));
}
addNonce對(duì)應(yīng)的,是在傳入U(xiǎn)RL后面增加csrf token或者是nonce的標(biāo)識(shí),用于后續(xù)請(qǐng)求時(shí)的識(shí)別。
頁(yè)面具體的編碼操作,則是對(duì)response的encodeURL的使用,也就是我們上面addNonce的使用:
對(duì)應(yīng)到Manager應(yīng)用,它的頁(yè)面是通過(guò)Servlet輸出的,所以具體的邏輯在Java文件中,我們?cè)陧?yè)面上的連接觀察到,此時(shí)獲取應(yīng)用列表的請(qǐng)求URL變成了這樣:
http://localhost:8080/manager/html/list?org.apache.catalina.filters.CSRF_NONCE=6BC061DD606D7BA1BDEF7F40657F0C47
每個(gè)不在entryPoints中的請(qǐng)求,都會(huì)加上org.apache.catalina.filters.CSRF_NONCE=6BC061DD606D7BA1BDEF7F40657F0C47
這種形式的URL輸出,就是在頁(yè)面上調(diào)用encodeURL的結(jié)果,對(duì)應(yīng)的Manager中的代碼是這個(gè)樣子:
以上,就是CSRF Prevetion Filter實(shí)現(xiàn)的原理和細(xì)節(jié)。當(dāng)然,上面返回403的地方,以及生成nonce的地方,都可以通過(guò)Filter提供的參數(shù)來(lái)進(jìn)行配置,分別對(duì)應(yīng)到denyStatus和randomClass。后者需要提供一個(gè)Random的實(shí)現(xiàn)。