CSRF 全稱:Cross Site Request Forgery
,譯:跨站請求僞造html
點擊一個連接以後發現:帳號被盜,錢被轉走,或者莫名發表某些評論等一切本身不知情的操做。前端
csrf 是一個能夠發送http請求的腳本。能夠假裝受害者向網站發送請求,達到修改網站數據的目的。chrome
當你在瀏覽器上登陸某網站後,cookie會保存登陸的信息,這樣在繼續訪問的時候不用每次都登陸了,這個你們都知道。而CSRF就利用這個登錄態去發送惡意請求給後端。json
爲何腳本能夠得到目標網站的cookie呢?
只要是請求目標網站,瀏覽器會自動帶上該網站域名下面的cookie,看下面的腳本,能夠證實惡意腳本能夠得到CSDN網站的登陸信息。
前提是你已經在瀏覽器上登陸了CSND網站。後端
<!doctype html> <html> <head> <meta charset="utf-8"/> <title>csrf demo</title> </head> <body> 您在CSDN上的 粉絲數:<span id="fans_num"></span> 關注數:<span id="follow_num"></span> <script> fetch('https://me.csdn.net/api/relation/get', { credentials: 'include' }).then(res => res.json()) .then( res => { document.getElementById('fans_num').innerText = res.data.fans_num; document.getElementById('follow_num').innerText = res.data.follow_num; }) </script> </body> </html>
保證CSDN的登陸狀態,用瀏覽器打開這個html文件,能夠看到這個腳本已經得到了我在csdn 上的用戶信息。以及寒酸的粉絲數量!
F12打開選擇應用程序一欄左邊Cookie 還有來自csdn網站關於當前用戶的一些信息。
api
這個腳本讓每一個不一樣的登陸用戶打開,都會根據當前用戶來展現關注數和粉絲數,
這就足以說明能夠得到目標網站的當前用戶的信息,並可以表明用戶發送請求。
這只是個無害的get請求,若是是post請求呢?瀏覽器
知道了原理,攻擊就變得好理解了,接着上面的例子,
我把請求地址改爲評論本篇文章的url,參數爲 「這篇文章寫得6
」,
在沒有CSRF防護的狀況下,我發表一個評論如:脫單祕笈:
,後面附上這個腳本的連接,只要有用戶點了連接,就會以他的名義給本篇文章發評論「這篇文章寫得6
」。cookie
CSDN 確定是作了防護了哈,我就不白費力氣了。session
三種防護方式:併發
禁止第三方網站使用本站Cookie。
這是後端在設置Cookie時候給SameSite
的值設置爲Strict
或者Lax
。
當設置Strict
的時候表明第三方網站全部請求都不能使用本站的Cookie。
當設置Lax
的時候表明只容許第三方網站的GET
表單、<a>
標籤和<link>
標籤攜帶Cookie。
當設置None
的時候表明和沒設同樣。
@Bean public CookieSerializer httpSessionIdResolver(){ DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer(); cookieSerializer.setCookieName("JESSIONID"); cookieSerializer.setUseHttpOnlyCookie(true); cookieSerializer.setSameSite("Lax"); cookieSerializer.setUseSecureCookie(true); return cookieSerializer; }
缺點:
目前只有chrome瀏覽器支持........
referer
表明着請求的來源,不能夠僞造。
後端寫個過濾器檢查請求的headers
中的referer
,檢驗是否是本網站的請求。
題外話:referer
和origin
的區別,只有post請求會攜帶origin請求頭,而referer不論何種狀況下都帶。referer
正確的拼寫 應該是 referrer
,HTTP的標準制定者們將錯就錯,不打算改了
缺點:
瀏覽器能夠關閉referer..........
最廣泛的一種防護方法,後端生成一個token放在session中併發給前端,前端發送請求時攜帶這個token,後端經過校驗這個token和session中的token是否一致判斷是不是本網站的請求。
具體實現:
用戶登陸輸入帳號密碼,請求登陸接口,後端在用戶登陸信息正確的狀況下將token放到session中,並返回token給前端,前端把token 存放在localstory中,以後再發送請求都會將token放到header中。
後端寫個過濾器,攔截POST請求,注意忽略掉不須要token的請求,好比登陸接口,獲取token的接口,省得尚未獲取token就檢驗token。
校驗原則: session中的token和前端header中的token一致的post ,放行。
/** * @author mashu * Date 2020/6/22 9:37 */ @Slf4j @Component @WebFilter(urlPatterns = "/*", filterName = "verificationTokenFilter", description = "用於校驗token") public class VerificationTokenFilter implements Filter { List<String> ignorePathList = ImmutableList.of("/demo/login","/demo/getToken"); @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse; //忽略不須要token的請求 String serviceUrl = httpServletRequest.getServletPath(); for (final String ignorePath : ignorePathList) { if (serviceUrl.contains(ignorePath)) { filterChain.doFilter(servletRequest, servletResponse); return; } } String method = httpServletRequest.getMethod(); if ("POST".equals(method)) { String tokenSession = (String)httpServletRequest.getSession().getAttribute("token"); String token = httpServletRequest.getHeader("token"); if (null != token && null != tokenSession && tokenSession.equals(token)) { filterChain.doFilter(servletRequest, servletResponse); return; } else { log.error("驗證token失敗!" + tokenSession + "!=" + token); httpServletResponse.sendError(403); return; } } filterChain.doFilter(servletRequest, servletResponse); } @Override public void destroy() { } }