第八篇SpringSecurity核心過濾器-CsrfFilter

      網友投稿 964 2022-05-30

      SpringSecurity核心過濾器-CsrfFilter

      Spring Security除了認證授權外功能外,還提供了安全防護功能。本文我們來介紹下SpringSecurity中是如何阻止CSRF攻擊的。

      一、什么是CSRF攻擊

      跨站請求偽造(英語:Cross-site request forgery),也被稱為 one-click attack 或者 session riding,通常縮寫為 CSRF 或者 XSRF, 是一種挾制用戶在當前已登錄的 Web 應用程序上執行非本意的操作的攻擊方法。跟跨網站腳本(XSS)相比,XSS利用的是用戶對指定網站的信任,CSRF 利用的是網站對用戶網頁瀏覽器的信任。

      跨站請求攻擊,簡單地說,是攻擊者通過一些技術手段欺騙用戶的瀏覽器去訪問一個自己曾經認證過的網站并運行一些操作(如發郵件,發消息,甚至財產操作如轉賬和購買商品)。由于瀏覽器曾經認證過,所以被訪問的網站會認為是真正的用戶操作而去運行。這利用了 web 中用戶身份驗證的一個漏洞:簡單的身份驗證只能保證請求發自某個用戶的瀏覽器,卻不能保證請求本身是用戶自愿發出的。舉個例子如下:

      二、解決方案

      1.檢查Referer字段

      HTTP頭中有一個Referer字段,這個字段用以標明請求來源于哪個地址。在處理敏感數據請求時,通常來說,Referer字段應和請求的地址位于同一域名下。以上文銀行操作為例,Referer字段地址通常應該是轉賬按鈕所在的網頁地址,應該也位于www.bankchina.com之下。而如果是CSRF攻擊傳來的請求,Referer字段會是包含惡意網址的地址,不會位于www.bankhacker.com之下,這時候服務器就能識別出惡意的訪問。

      這種辦法簡單易行,工作量低,僅需要在關鍵訪問處增加一步校驗。但這種辦法也有其局限性,因其完全依賴瀏覽器發送正確的Referer字段。雖然http協議對此字段的內容有明確的規定,但并無法保證來訪的瀏覽器的具體實現,亦無法保證瀏覽器沒有安全漏洞影響到此字段。并且也存在攻擊者攻擊某些瀏覽器,篡改其Referer字段的可能。

      2.CsrfToken

      其實CSRF攻擊是在用戶登錄且沒有退出瀏覽器的情況下訪問了第三方的站點而被攻擊的,完全是攜帶了認證的cookie來實現的,我們只需要在服務端響應給客戶端的頁面中綁定隨機的信息,然后提交請求后在服務端校驗,如果攜帶的數據和之前的不一致就認為是CSRF攻擊,拒絕這些請求即可。

      三、SpringSecurity是如何防止CSRF攻擊的

      首先從 Spring Security 4.0 開始,默認情況下會啟用 CSRF 保護,以防止 CSRF 攻擊應用程序,Spring Security CSRF 會針對 PATCH,POST,PUT 和 DELETE 方法進行防護。

      1.開啟關閉CSRF防御

      在SpringSecurity中默認是開啟csrf防御的,我們可以通過一下配置來關閉csrf防御

      http.csrf().disable();

      1

      或者在基于配置文件的使用中使用如下操作關閉

      1

      2.SpringSecurity的實現

      2.1 CSRF的原理

      生成csrfToken保存到HttpSession或者Cookie中

      請求到來時,程序會從請求中獲取提交的csrfToken,同時會從HttpSession中獲取之前存儲的csrfToken進行比較,如果相同則認為是合法的請求,繼續后面的操作,如果不相等則認為是CSRF工具,拒絕該請求

      2.2 源碼分析

      然后我們來看下SpringSecurity中的代碼是如何實現的。我們主要看的是 spring-security-web.jar中的

      org.springframework.security.web.csrf包下的源碼。

      CsrfToken是一個非常簡單的接口,定義了Token令牌,消息頭和請求參數。

      public interface CsrfToken extends Serializable { /** * 獲取我們放置在請求頭中CSRF隨機值的名稱 */ String getHeaderName(); /** * 獲取請求體中的csrf隨機值的參數名稱 */ String getParameterName(); /** * 返回具體的Token值 */ String getToken(); }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      CsrfToken的默認實現是DefaultCsrfToken。

      CsrfTokenRepository接口也非常簡單,定義了Token的生成,存儲和獲取的相關API

      public interface CsrfTokenRepository { /** * 生成Token */ CsrfToken generateToken(HttpServletRequest request); /** * 存儲生成的Token */ void saveToken(CsrfToken token, HttpServletRequest request, HttpServletResponse response); /** * 返回Token */ CsrfToken loadToken(HttpServletRequest request); }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      CsrfTokenRepository的實現在SpringSecurity中有兩個實現。

      默認的實現是HttpSessionCsrfTokenRepository。是一個基于HttpSession保存csrfToken的實現。

      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; // 保存Token到session中 @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); } } // 從session中加載token @Override public CsrfToken loadToken(HttpServletRequest request) { HttpSession session = request.getSession(false); if (session == null) { return null; } return (CsrfToken) session.getAttribute(this.sessionAttributeName); } // 生成Token @Override public CsrfToken generateToken(HttpServletRequest request) { return new DefaultCsrfToken(this.headerName, this.parameterName, createNewToken()); } /** * Sets the {@link HttpServletRequest} parameter name that the {@link CsrfToken} is * expected to appear on * @param parameterName the new parameter name to use */ public void setParameterName(String parameterName) { Assert.hasLength(parameterName, "parameterName cannot be null or empty"); this.parameterName = parameterName; } /** * Sets the header name that the {@link CsrfToken} is expected to appear on and the * header that the response will contain the {@link CsrfToken}. * @param headerName the new header name to use */ public void setHeaderName(String headerName) { Assert.hasLength(headerName, "headerName cannot be null or empty"); this.headerName = headerName; } /** * Sets the {@link HttpSession} attribute name that the {@link CsrfToken} is stored in * @param sessionAttributeName the new attribute name to use */ public void setSessionAttributeName(String sessionAttributeName) { Assert.hasLength(sessionAttributeName, "sessionAttributename cannot be null or empty"); this.sessionAttributeName = sessionAttributeName; } // 通過UUID來生成Token信息 private String createNewToken() { return UUID.randomUUID().toString(); } }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      24

      25

      26

      27

      28

      29

      30

      31

      32

      33

      34

      35

      36

      37

      38

      39

      40

      41

      42

      43

      44

      45

      46

      47

      48

      49

      50

      51

      52

      53

      54

      55

      56

      57

      58

      59

      60

      61

      62

      63

      64

      65

      66

      67

      68

      69

      70

      【第八篇】SpringSecurity核心過濾器-CsrfFilter

      71

      72

      73

      74

      75

      76

      77

      78

      79

      CsrfFilter用于處理跨站請求偽造。檢查表單提交的_csrf隱藏域的value與內存中保存的的是否一致,如果一致框架則認為當然登錄頁面是安全的,如果不一致,會報403forbidden錯誤。

      具體處理請求的方法

      @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { request.setAttribute(HttpServletResponse.class.getName(), response); // 從session中加載 Token CsrfToken csrfToken = this.tokenRepository.loadToken(request); boolean missingToken = (csrfToken == null); // 如果是第一次訪問就生成Token信息 if (missingToken) { csrfToken = this.tokenRepository.generateToken(request); // 把生成的Token信息存儲在Session中 this.tokenRepository.saveToken(csrfToken, request, response); } request.setAttribute(CsrfToken.class.getName(), csrfToken); request.setAttribute(csrfToken.getParameterName(), csrfToken); // 匹配是否是需要做CSRF防御的相關請求 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; } // 獲取請求攜帶在header中的Token信息 String actualToken = request.getHeader(csrfToken.getHeaderName()); if (actualToken == null) { // 從請求參數中獲取Token信息 actualToken = request.getParameter(csrfToken.getParameterName()); } // 判斷請求中的Token是否和Session中存儲的Token相等 if (!equalsConstantTime(csrfToken.getToken(), actualToken)) { this.logger.debug( LogMessage.of(() -> "Invalid CSRF token found for " + UrlUtils.buildFullRequestUrl(request))); // Token不相等,說明是CSRF攻擊,拋出訪問拒絕的異常 AccessDeniedException exception = (!missingToken) ? new InvalidCsrfTokenException(csrfToken, actualToken) : new MissingCsrfTokenException(actualToken); this.accessDeniedHandler.handle(request, response, exception); return; } // 說明是正常的訪問,放過 filterChain.doFilter(request, response); }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      24

      25

      26

      27

      28

      29

      30

      31

      32

      33

      34

      35

      36

      37

      38

      39

      40

      41

      42

      43

      3.分布式Session

      上面介紹的CsrfToken校驗,生成的Token信息是存儲在HttpSession中的,那么我們在分布式環境下,跨進程的場景下我們要如何實現Session共享呢?這時我們可以通過SpringSession來實現,但是這里有個前提就是分布式的項目必須都得是在一個一級域名下的多個二級域名是可以實現的。

      3.1 配置SpringSession

      配置SpringSession可以參考Spring的官網:https://docs.spring.io/spring-session/docs/2.5.6/reference/html5/ 因為在分布式Session我們需要把Session數據獨立的存儲在Redis服務中,所以還需要啟動Redis服務。

      添加相關依賴:

      org.springframework.session spring-session-data-redis org.springframework.boot spring-boot-starter-data-redis

      1

      2

      3

      4

      5

      6

      7

      8

      然后添加對應的配置

      spring.redis.host=192.168.56.100 spring.redis.port=6379 spring.session.store-type=redis spring.session.redis.namespace=spring:session

      1

      2

      3

      4

      修改host文件,設置域名關系

      添加配置文件,設置Cookie中的domain為一級域名

      @Configuration public class MySessionConfig { @Bean public CookieSerializer cookieSerializer(){ DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer(); cookieSerializer.setDomainName("msb.com"); cookieSerializer.setCookieName("csrfSession"); return cookieSerializer; } }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      然后測試看效果,然后aa.msb.com:8080

      然后訪問bb.msb.com:8081

      可以看到兩個頁面中生成的csrfToken是一樣的,說明共享了數據,而且Cookie中的Session信息也一致。

      搞定~

      域名注冊服務 網站

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:Python進階(三十四)-Python3多線程解讀
      下一篇:#私藏項目實操分享# Go 語言入門很簡單 -- 2. Go 的數據類型
      相關文章
      亚洲av日韩av欧v在线天堂| 亚洲自偷自拍另类图片二区| 亚洲国产成人精品电影| 亚洲第一永久在线观看| 亚洲一区二区中文| 久久精品国产亚洲AV麻豆网站| 亚洲AV无码一区二区二三区软件| 亚洲午夜久久久影院伊人| 丁香五月亚洲综合深深爱| 夜夜春亚洲嫩草影院| 亚洲男人的天堂www| 亚洲日韩乱码中文无码蜜桃臀网站 | 亚洲国产精品无码成人片久久| 亚洲国产精彩中文乱码AV| 人人狠狠综合久久亚洲88| 亚洲va久久久噜噜噜久久| 亚洲AV无码不卡无码| 亚洲电影免费在线观看| 日本久久久久亚洲中字幕| 久久亚洲AV成人无码软件| 亚洲欧洲综合在线| 激情综合亚洲色婷婷五月 | 亚洲视频一区二区三区| 亚洲熟妇色自偷自拍另类| 亚洲同性男gay网站在线观看| ass亚洲**毛茸茸pics| 久久久久久亚洲精品影院| 亚洲精品无码久久久久秋霞| 亚洲成a人无码亚洲成www牛牛| 国产AV无码专区亚洲AV琪琪| 亚洲国产婷婷综合在线精品| 国产亚洲精品久久久久秋霞| 亚洲AV日韩AV天堂久久| 亚洲国产美女精品久久| 亚洲中文字幕无码中文| 在线观看亚洲专区| 亚洲中文字幕无码日韩| 久久亚洲中文字幕精品有坂深雪| 亚洲国产精品久久人人爱| 亚洲日韩国产欧美一区二区三区 | 亚洲av无码久久忘忧草|