<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
CSRF的全稱是(Cross Site Request Forgery),可譯為跨域請求偽造,是一種利用使用者帶登入 態的cookie進行安全操作的攻擊方式。CSRF實際上並不難防,但常常被系統開發者忽略,從而埋下巨 大的安全隱患。
舉個例子,假設你登入了郵箱,正常情況下可以通過某個連結http:xx.mail.com/send可以傳送郵件。此時你又存取了別的網站,網站中有黃色廣告,點選後廣告會請求http:xx.mail.com/send。此時相當於在盜版網站中呼叫了傳送郵件的連結,存取時會使用你郵箱網站的cookie資訊。雖然盜版網站會提示跨域,但伺服器端任然進行了相應處理。
在任何情況下,都應當儘可能地避免以GET方式提供涉及資料修改的API。並不是說其他請求方式可以避免CSRF,只是GET請求更容易被攻擊。
在此基礎上,防禦 CSRF攻擊的方式主要有以下兩種。
Http referer是由瀏覽器新增的一個請求頭欄位,用於標識請求來源,瀏覽器端無法輕易篡改該值。
比如攻擊者在第三方頁面構造了POST請求,htttp referer不是我們網站的地址(有的老版IE瀏覽器可以修改該值,如果使用者的瀏覽器比較新,就能避免這個問題),當伺服器端收到請求,發現請求來自其他站點,就能拒絕該請求。
這種方式簡單便捷,但不是完全可靠,比如老的瀏覽器就能修改該值。使用者在瀏覽器設定了不被跟蹤,就不會有該欄位,伺服器端加了校驗後就會攔截掉使用者的正常請求。
CSRF是利用使用者的登入態進行攻擊的,而使用者的登入態記錄在cookie中。其實攻擊者並不知道用 戶的cookie存放了哪些資料,於是想方設法讓使用者自身發起請求,這樣瀏覽器便會自行將cookie傳送到 伺服器完成身份校驗。
CsrfToken 的防範思路是,新增一些並不存放於 cookie 的驗證值,並在每個請求中都進行校驗, 便可以阻止CSRF攻擊。
具體做法是在使用者登入時,由系統發放一個CsrfToken值,使用者攜帶該CsrfToken值與使用者名稱、密碼 等引數完成登入。伺服器端記錄該對談的CsrfToken值,之後在使用者的任何請求中,都必須帶上該 CsrfToken值,並由系統進行校驗。
該方案需要前端配合,包括儲存CsrfToken的值,在每次的請求中,不管是form表單還是ajax,都需要攜帶該token。雖然比HTTP Referer安全很多,但也有弊端,如果在已有系統進行改造,就需要修改每一個請求,所以建議在系統開發之初就考慮防禦CSRF攻擊。
csrf攻擊完全是基於瀏覽器的,如果前端沒有瀏覽器,也就不會有CSRF攻擊了,所以我們需要關閉SpringSecurity自動設定的csrf。
CsrfFilter:
SpringSecurity通過註冊一個CsrfFilter來專門處理CSRF攻擊。
CsrfToken:
用該介面來定義csrftoekn所需的一些必要方法。
public interface CsrfToken extends Serializable { //從哪個頭欄位獲取token值 String getHeaderName(); //從哪個引數獲取token值 String getParameterName(); String getToken(); }
CsrfTokenRepository
定義瞭如何生成,儲存、以及載入token.
public interface CsrfTokenRepository { CsrfToken generateToken(HttpServletRequest request); void saveToken(CsrfToken token, HttpServletRequest request, HttpServletResponse response); CsrfToken loadToken(HttpServletRequest request); }
HttpSessionCsrfTokenRepository
預設情況下,SpringSecurity使用的CsrfTokenRepository的實現類是HttpSessionCsrfTokenRepository
public final class HttpSessionCsrfTokenRepository implements CsrfTokenRepository { private static final String DEFAULT_CSRF_PARAMETER_NAME = "_csrf"; private static final String DEFAULT_CSRF_HEADER_NAME = "X-CSRF-TOKEN"; private static final String DEFAULT_CSRF_TOKEN_ATTR_NAME = HttpSessionCsrfTokenRepository.class.getName() .concat(".CSRF_TOKEN"); private String parameterName = DEFAULT_CSRF_PARAMETER_NAME; private String headerName = DEFAULT_CSRF_HEADER_NAME; private String sessionAttributeName = DEFAULT_CSRF_TOKEN_ATTR_NAME; @Override public void saveToken(CsrfToken token, HttpServletRequest request, HttpServletResponse response) { if (token == null) { HttpSession session = request.getSession(false); if (session != null) { session.removeAttribute(this.sessionAttributeName); } } else { HttpSession session = request.getSession(); session.setAttribute(this.sessionAttributeName, token); } } @Override public CsrfToken loadToken(HttpServletRequest request) { HttpSession session = request.getSession(false); if (session == null) { return null; } return (CsrfToken) session.getAttribute(this.sessionAttributeName); } @Override public CsrfToken generateToken(HttpServletRequest request) { return new DefaultCsrfToken(this.headerName, this.parameterName, createNewToken()); } public void setParameterName(String parameterName) { Assert.hasLength(parameterName, "parameterName cannot be null or empty"); this.parameterName = parameterName; } public void setHeaderName(String headerName) { Assert.hasLength(headerName, "headerName cannot be null or empty"); this.headerName = headerName; } public void setSessionAttributeName(String sessionAttributeName) { Assert.hasLength(sessionAttributeName, "sessionAttributename cannot be null or empty"); this.sessionAttributeName = sessionAttributeName; } private String createNewToken() { return UUID.randomUUID().toString(); } }
HttpSessionCsrfTokenRepository將CsrfToken值儲存在HttpSession中,並指定前端把CsrfToken 值放在名為“_csrf”的請求引數或名為“X-CSRF-TOKEN”的請求頭欄位裡(可以呼叫相應的設定方法來重新設定)。校驗時,通過對比HttpSession記憶體儲的CsrfToken值與前端攜帶的CsrfToken值是否一致,便能斷定本次請求是否為CSRF攻擊。
前端使用Token的時候,必須使用從伺服器端渲染的方式,比如jsp頁面:
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
CookieCsrfTokenRepository
Spring Security還提供了另一種方式,即CookieCsrfTokenRepository。之前是伺服器端將token儲存在了session中。這個是將token儲存在瀏覽器的cookie中,這樣可以減少伺服器端的記憶體消耗,而且前端可以使用js讀取(需要設定該cookie的httpOnly屬性為false),更加靈活。
有人可能會有疑問,放在cookie中,不是又可以被攻擊了嗎?其實不是的。
cookie只有在同域的情況下才能被js獲取。正常情況下,伺服器端從cookie中獲取token,前端使用js從cookie中獲取token,2者進行校驗。攻擊者只能在第三方頁面偽造請求的時候,利用請求攜帶cookie,這個時候伺服器端能拿從攜帶的cookie中拿到token,但是前端並沒有使用js將用於校驗的token傳給伺服器端(攻擊者沒法獲取cookie),所以校驗沒法通過。
CsrfFilter
現在我們重新來看這個類的主要邏輯:
@Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { request.setAttribute(HttpServletResponse.class.getName(), response); CsrfToken csrfToken = this.tokenRepository.loadToken(request); boolean missingToken = (csrfToken == null); if (missingToken) { csrfToken = this.tokenRepository.generateToken(request); this.tokenRepository.saveToken(csrfToken, request, response); } request.setAttribute(CsrfToken.class.getName(), csrfToken); request.setAttribute(csrfToken.getParameterName(), csrfToken); if (!this.requireCsrfProtectionMatcher.matches(request)) { if (this.logger.isTraceEnabled()) { this.logger.trace("Did not protect against CSRF since request did not match " + this.requireCsrfProtectionMatcher); } filterChain.doFilter(request, response); return; } String actualToken = request.getHeader(csrfToken.getHeaderName()); if (actualToken == null) { actualToken = request.getParameter(csrfToken.getParameterName()); } if (!equalsConstantTime(csrfToken.getToken(), actualToken)) { this.logger.debug( LogMessage.of(() -> "Invalid CSRF token found for " + UrlUtils.buildFullRequestUrl(request))); AccessDeniedException exception = (!missingToken) ? new InvalidCsrfTokenException(csrfToken, actualToken) : new MissingCsrfTokenException(actualToken); this.accessDeniedHandler.handle(request, response, exception); return; } filterChain.doFilter(request, response); }
這段程式碼的意思就是, 從你指定或者預設的的CsrfTokenRepository中獲取token,其實就是獲取的伺服器端儲存的token(session中或者cookie中),如果沒有,那麼就生成並且儲存token,然後獲取前端傳過來的token,然後進行對比。
1.我們使用cookie的方式儲存token.
2.新增AccessDeniedHandler
用來在token請求不通過的時候,返回資料。
@Component @Slf4j public class MyAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(JSON.toJSONString(ResultVO.error(10000, "禁止存取"))); } }
3. 前端修改
生成的token:
function getCookie(name){ var strcookie = document.cookie;//獲取cookie字串 var arrcookie = strcookie.split("; ");//分割 //遍歷匹配 for ( var i = 0; i < arrcookie.length; i++) { var arr = arrcookie[i].split("="); if (arr[0] === name){ return arr[1]; } } return ""; }
啟動專案,登入成功,跳轉頁面。
文章配套程式碼:https://gitee.com/lookoutthebush/spring-security-demo
到此這篇關於SpringSecurity跨域請求偽造(CSRF)的防護實現的文章就介紹到這了,更多相關SpringSecurity CSRF防護內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45