偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

Spring Security 中 CSRF 防御源碼解析

安全 應(yīng)用安全
就是生成一個(gè) CsrfToken,這個(gè) Token,本質(zhì)上就是一個(gè) UUID 字符串,然后將這個(gè) Token 保存到 HttpSession 中,或者保存到 Cookie 中,待請(qǐng)求到來時(shí),從 HttpSession 或者 Cookie 中取出來做校驗(yàn)。

 上篇文章松哥和大家聊了什么是 CSRF 攻擊,以及 CSRF 攻擊要如何防御。主要和大家聊了 Spring Security 中處理該問題的幾種辦法。

今天松哥來和大家簡(jiǎn)單的看一下 Spring Security 中,CSRF 防御源碼。

本文主要從兩個(gè)方面來和大家講解:

  • 返回給前端的 _csrf 參數(shù)是如何生成的。
  • 前端傳來的 _csrf 參數(shù)是如何校驗(yàn)的。

1.隨機(jī)字符串生成

我們先來看一下 Spring Security 中的 csrf 參數(shù)是如何生成的。

首先,Spring Security 中提供了一個(gè)保存 csrf 參數(shù)的規(guī)范,就是 CsrfToken:

  1. public interface CsrfToken extends Serializable { 
  2.  String getHeaderName(); 
  3.  String getParameterName(); 
  4.  String getToken(); 
  5.  

這里三個(gè)方法都好理解,前兩個(gè)是獲取 _csrf 參數(shù)的 key,第三個(gè)是獲取 _csrf 參數(shù)的 value。

CsrfToken 有兩個(gè)實(shí)現(xiàn)類,如下:

默認(rèn)情況下使用的是 DefaultCsrfToken,我們來稍微看下 DefaultCsrfToken:

  1. public final class DefaultCsrfToken implements CsrfToken { 
  2.  private final String token; 
  3.  private final String parameterName; 
  4.  private final String headerName; 
  5.  public DefaultCsrfToken(String headerName, String parameterName, String token) { 
  6.   this.headerName = headerName; 
  7.   this.parameterName = parameterName; 
  8.   this.token = token; 
  9.  } 
  10.  public String getHeaderName() { 
  11.   return this.headerName; 
  12.  } 
  13.  public String getParameterName() { 
  14.   return this.parameterName; 
  15.  } 
  16.  public String getToken() { 
  17.   return this.token; 
  18.  } 

這段實(shí)現(xiàn)很簡(jiǎn)單,幾乎沒有添加額外的方法,就是接口方法的實(shí)現(xiàn)。

CsrfToken 相當(dāng)于就是 _csrf 參數(shù)的載體。那么參數(shù)是如何生成和保存的呢?這涉及到另外一個(gè)類:

  1. public interface CsrfTokenRepository { 
  2.  CsrfToken generateToken(HttpServletRequest request); 
  3.  void saveToken(CsrfToken token, HttpServletRequest request, 
  4.    HttpServletResponse response); 
  5.  CsrfToken loadToken(HttpServletRequest request); 

這里三個(gè)方法:

  1. generateToken 方法就是 CsrfToken 的生成過程。
  2. saveToken 方法就是保存 CsrfToken。
  3. loadToken 則是如何加載 CsrfToken。

CsrfTokenRepository 有四個(gè)實(shí)現(xiàn)類,在上篇文章中,我們用到了其中兩個(gè):HttpSessionCsrfTokenRepository 和 CookieCsrfTokenRepository,其中 HttpSessionCsrfTokenRepository 是默認(rèn)的方案。

我們先來看下 HttpSessionCsrfTokenRepository 的實(shí)現(xiàn):

  1. public final class HttpSessionCsrfTokenRepository implements CsrfTokenRepository { 
  2.  private static final String DEFAULT_CSRF_PARAMETER_NAME = "_csrf"
  3.  private static final String DEFAULT_CSRF_HEADER_NAME = "X-CSRF-TOKEN"
  4.  private static final String DEFAULT_CSRF_TOKEN_ATTR_NAME = HttpSessionCsrfTokenRepository.class 
  5.    .getName().concat(".CSRF_TOKEN"); 
  6.  private String parameterName = DEFAULT_CSRF_PARAMETER_NAME; 
  7.  private String headerName = DEFAULT_CSRF_HEADER_NAME; 
  8.  private String sessionAttributeName = DEFAULT_CSRF_TOKEN_ATTR_NAME; 
  9.  public void saveToken(CsrfToken token, HttpServletRequest request, 
  10.    HttpServletResponse response) { 
  11.   if (token == null) { 
  12.    HttpSession session = request.getSession(false); 
  13.    if (session != null) { 
  14.     session.removeAttribute(this.sessionAttributeName); 
  15.    } 
  16.   } 
  17.   else { 
  18.    HttpSession session = request.getSession(); 
  19.    session.setAttribute(this.sessionAttributeName, token); 
  20.   } 
  21.  } 
  22.  public CsrfToken loadToken(HttpServletRequest request) { 
  23.   HttpSession session = request.getSession(false); 
  24.   if (session == null) { 
  25.    return null
  26.   } 
  27.   return (CsrfToken) session.getAttribute(this.sessionAttributeName); 
  28.  } 
  29.  public CsrfToken generateToken(HttpServletRequest request) { 
  30.   return new DefaultCsrfToken(this.headerName, this.parameterName, 
  31.     createNewToken()); 
  32.  } 
  33.  private String createNewToken() { 
  34.   return UUID.randomUUID().toString(); 
  35.  } 

這段源碼其實(shí)也很好理解:

  1. saveToken 方法將 CsrfToken 保存在 HttpSession 中,將來再?gòu)?HttpSession 中取出和前端傳來的參數(shù)做比較。
  2. loadToken 方法當(dāng)然就是從 HttpSession 中讀取 CsrfToken 出來。
  3. generateToken 是生成 CsrfToken 的過程,可以看到,生成的默認(rèn)載體就是 DefaultCsrfToken,而 CsrfToken 的值則通過 createNewToken 方法生成,是一個(gè) UUID 字符串。
  4. 在構(gòu)造 DefaultCsrfToken 是還有兩個(gè)參數(shù) headerName 和 parameterName,這兩個(gè)參數(shù)是前端保存參數(shù)的 key。

這是默認(rèn)的方案,適用于前后端不分的開發(fā),具體用法可以參考上篇文章

如果想在前后端分離開發(fā)中使用,那就需要 CsrfTokenRepository 的另一個(gè)實(shí)現(xiàn)類 CookieCsrfTokenRepository ,代碼如下:

  1. public final class CookieCsrfTokenRepository implements CsrfTokenRepository { 
  2.  static final String DEFAULT_CSRF_COOKIE_NAME = "XSRF-TOKEN"
  3.  static final String DEFAULT_CSRF_PARAMETER_NAME = "_csrf"
  4.  static final String DEFAULT_CSRF_HEADER_NAME = "X-XSRF-TOKEN"
  5.  private String parameterName = DEFAULT_CSRF_PARAMETER_NAME; 
  6.  private String headerName = DEFAULT_CSRF_HEADER_NAME; 
  7.  private String cookieName = DEFAULT_CSRF_COOKIE_NAME; 
  8.  private boolean cookieHttpOnly = true
  9.  private String cookiePath; 
  10.  private String cookieDomain; 
  11.  public CookieCsrfTokenRepository() { 
  12.  } 
  13.  @Override 
  14.  public CsrfToken generateToken(HttpServletRequest request) { 
  15.   return new DefaultCsrfToken(this.headerName, this.parameterName, 
  16.     createNewToken()); 
  17.  } 
  18.  @Override 
  19.  public void saveToken(CsrfToken token, HttpServletRequest request, 
  20.    HttpServletResponse response) { 
  21.   String tokenValue = token == null ? "" : token.getToken(); 
  22.   Cookie cookie = new Cookie(this.cookieName, tokenValue); 
  23.   cookie.setSecure(request.isSecure()); 
  24.   if (this.cookiePath != null && !this.cookiePath.isEmpty()) { 
  25.     cookie.setPath(this.cookiePath); 
  26.   } else { 
  27.     cookie.setPath(this.getRequestContext(request)); 
  28.   } 
  29.   if (token == null) { 
  30.    cookie.setMaxAge(0); 
  31.   } 
  32.   else { 
  33.    cookie.setMaxAge(-1); 
  34.   } 
  35.   cookie.setHttpOnly(cookieHttpOnly); 
  36.   if (this.cookieDomain != null && !this.cookieDomain.isEmpty()) { 
  37.    cookie.setDomain(this.cookieDomain); 
  38.   } 
  39.  
  40.   response.addCookie(cookie); 
  41.  } 
  42.  @Override 
  43.  public CsrfToken loadToken(HttpServletRequest request) { 
  44.   Cookie cookie = WebUtils.getCookie(request, this.cookieName); 
  45.   if (cookie == null) { 
  46.    return null
  47.   } 
  48.   String token = cookie.getValue(); 
  49.   if (!StringUtils.hasLength(token)) { 
  50.    return null
  51.   } 
  52.   return new DefaultCsrfToken(this.headerName, this.parameterName, token); 
  53.  } 
  54.  public static CookieCsrfTokenRepository withHttpOnlyFalse() { 
  55.   CookieCsrfTokenRepository result = new CookieCsrfTokenRepository(); 
  56.   result.setCookieHttpOnly(false); 
  57.   return result; 
  58.  } 
  59.  private String createNewToken() { 
  60.   return UUID.randomUUID().toString(); 
  61.  } 

和 HttpSessionCsrfTokenRepository 相比,這里 _csrf 數(shù)據(jù)保存的時(shí)候,都保存到 cookie 中去了,當(dāng)然讀取的時(shí)候,也是從 cookie 中讀取,其他地方則和 HttpSessionCsrfTokenRepository 是一樣的。

OK,這就是我們整個(gè) _csrf 參數(shù)生成的過程。

總結(jié)一下,就是生成一個(gè) CsrfToken,這個(gè) Token,本質(zhì)上就是一個(gè) UUID 字符串,然后將這個(gè) Token 保存到 HttpSession 中,或者保存到 Cookie 中,待請(qǐng)求到來時(shí),從 HttpSession 或者 Cookie 中取出來做校驗(yàn)。

2.參數(shù)校驗(yàn)

那接下來就是校驗(yàn)了。

校驗(yàn)主要是通過 CsrfFilter 過濾器來進(jìn)行,我們來看下核心的 doFilterInternal 方法:

  1. protected void doFilterInternal(HttpServletRequest request, 
  2.   HttpServletResponse response, FilterChain filterChain) 
  3.     throws ServletException, IOException { 
  4.  request.setAttribute(HttpServletResponse.class.getName(), response); 
  5.  CsrfToken csrfToken = this.tokenRepository.loadToken(request); 
  6.  final boolean missingToken = csrfToken == null
  7.  if (missingToken) { 
  8.   csrfToken = this.tokenRepository.generateToken(request); 
  9.   this.tokenRepository.saveToken(csrfToken, request, response); 
  10.  } 
  11.  request.setAttribute(CsrfToken.class.getName(), csrfToken); 
  12.  request.setAttribute(csrfToken.getParameterName(), csrfToken); 
  13.  if (!this.requireCsrfProtectionMatcher.matches(request)) { 
  14.   filterChain.doFilter(request, response); 
  15.   return
  16.  } 
  17.  String actualToken = request.getHeader(csrfToken.getHeaderName()); 
  18.  if (actualToken == null) { 
  19.   actualToken = request.getParameter(csrfToken.getParameterName()); 
  20.  } 
  21.  if (!csrfToken.getToken().equals(actualToken)) { 
  22.   if (this.logger.isDebugEnabled()) { 
  23.    this.logger.debug("Invalid CSRF token found for " 
  24.      + UrlUtils.buildFullRequestUrl(request)); 
  25.   } 
  26.   if (missingToken) { 
  27.    this.accessDeniedHandler.handle(request, response, 
  28.      new MissingCsrfTokenException(actualToken)); 
  29.   } 
  30.   else { 
  31.    this.accessDeniedHandler.handle(request, response, 
  32.      new InvalidCsrfTokenException(csrfToken, actualToken)); 
  33.   } 
  34.   return
  35.  } 
  36.  filterChain.doFilter(request, response); 

這個(gè)方法我來稍微解釋下:

  1. 首先調(diào)用 tokenRepository.loadToken 方法讀取 CsrfToken 出來,這個(gè) tokenRepository 就是你配置的 CsrfTokenRepository 實(shí)例,CsrfToken 存在 HttpSession 中,這里就從 HttpSession 中讀取,CsrfToken 存在 Cookie 中,這里就從 Cookie 中讀取。
  2. 如果調(diào)用 tokenRepository.loadToken 方法沒有加載到 CsrfToken,那說明這個(gè)請(qǐng)求可能是第一次發(fā)起,則調(diào)用 tokenRepository.generateToken 方法生成 CsrfToken ,并調(diào)用 tokenRepository.saveToken 方法保存 CsrfToken。
  3. 大家注意,這里還調(diào)用 request.setAttribute 方法存了一些值進(jìn)去,這就是默認(rèn)情況下,我們通過 jsp 或者 thymeleaf 標(biāo)簽渲染 _csrf 的數(shù)據(jù)來源。
  4. requireCsrfProtectionMatcher.matches 方法則使用用來判斷哪些請(qǐng)求方法需要做校驗(yàn),默認(rèn)情況下,"GET", "HEAD", "TRACE", "OPTIONS" 方法是不需要校驗(yàn)的。
  5. 接下來獲取請(qǐng)求中傳遞來的 CSRF 參數(shù),先從請(qǐng)求頭中獲取,獲取不到再?gòu)恼?qǐng)求參數(shù)中獲取。
  6. 獲取到請(qǐng)求傳來的 csrf 參數(shù)之后,再和一開始加載到的 csrfToken 做比較,如果不同的話,就拋出異常。

如此之后,就完成了整個(gè)校驗(yàn)工作了。

3.LazyCsrfTokenRepository

前面我們說了 CsrfTokenRepository 有四個(gè)實(shí)現(xiàn)類,除了我們介紹的兩個(gè)之外,還有一個(gè) LazyCsrfTokenRepository,這里松哥也和大家做一個(gè)簡(jiǎn)單介紹。

在前面的 CsrfFilter 中大家發(fā)現(xiàn),對(duì)于常見的 GET 請(qǐng)求實(shí)際上是不需要 CSRF 攻擊校驗(yàn)的,但是,每當(dāng) GET 請(qǐng)求到來時(shí),下面這段代碼都會(huì)執(zhí)行:

  1. if (missingToken) { 
  2.  csrfToken = this.tokenRepository.generateToken(request); 
  3.  this.tokenRepository.saveToken(csrfToken, request, response); 

生成 CsrfToken 并保存,但實(shí)際上卻沒什么用,因?yàn)?GET 請(qǐng)求不需要 CSRF 攻擊校驗(yàn)。

所以,Spring Security 官方又推出了 LazyCsrfTokenRepository。

LazyCsrfTokenRepository 實(shí)際上不能算是一個(gè)真正的 CsrfTokenRepository,它是一個(gè)代理,可以用來增強(qiáng) HttpSessionCsrfTokenRepository 或者 CookieCsrfTokenRepository 的功能:

  1. public final class LazyCsrfTokenRepository implements CsrfTokenRepository { 
  2.  @Override 
  3.  public CsrfToken generateToken(HttpServletRequest request) { 
  4.   return wrap(request, this.delegate.generateToken(request)); 
  5.  } 
  6.  @Override 
  7.  public void saveToken(CsrfToken token, HttpServletRequest request, 
  8.    HttpServletResponse response) { 
  9.   if (token == null) { 
  10.    this.delegate.saveToken(token, request, response); 
  11.   } 
  12.  } 
  13.  @Override 
  14.  public CsrfToken loadToken(HttpServletRequest request) { 
  15.   return this.delegate.loadToken(request); 
  16.  } 
  17.  private CsrfToken wrap(HttpServletRequest request, CsrfToken token) { 
  18.   HttpServletResponse response = getResponse(request); 
  19.   return new SaveOnAccessCsrfToken(this.delegate, request, response, token); 
  20.  } 
  21.  private static final class SaveOnAccessCsrfToken implements CsrfToken { 
  22.   private transient CsrfTokenRepository tokenRepository; 
  23.   private transient HttpServletRequest request; 
  24.   private transient HttpServletResponse response; 
  25.  
  26.   private final CsrfToken delegate; 
  27.  
  28.   SaveOnAccessCsrfToken(CsrfTokenRepository tokenRepository, 
  29.     HttpServletRequest request, HttpServletResponse response, 
  30.     CsrfToken delegate) { 
  31.    this.tokenRepository = tokenRepository; 
  32.    this.request = request; 
  33.    this.response = response; 
  34.    this.delegate = delegate; 
  35.   } 
  36.   @Override 
  37.   public String getToken() { 
  38.    saveTokenIfNecessary(); 
  39.    return this.delegate.getToken(); 
  40.   } 
  41.   private void saveTokenIfNecessary() { 
  42.    if (this.tokenRepository == null) { 
  43.     return
  44.    } 
  45.  
  46.    synchronized (this) { 
  47.     if (this.tokenRepository != null) { 
  48.      this.tokenRepository.saveToken(this.delegate, this.request, 
  49.        this.response); 
  50.      this.tokenRepository = null
  51.      this.request = null
  52.      this.response = null
  53.     } 
  54.    } 
  55.   } 
  56.  
  57.  } 

這里,我說三點(diǎn):

  1. generateToken 方法,該方法用來生成 CsrfToken,默認(rèn) CsrfToken 的載體是 DefaultCsrfToken,現(xiàn)在換成了 SaveOnAccessCsrfToken。
  2. SaveOnAccessCsrfToken 和 DefaultCsrfToken 并沒有太大區(qū)別,主要是 getToken 方法有區(qū)別,在 SaveOnAccessCsrfToken 中,當(dāng)開發(fā)者調(diào)用 getToken 想要去獲取 csrfToken 時(shí),才會(huì)去對(duì) csrfToken 做保存操作(調(diào)用 HttpSessionCsrfTokenRepository 或者 CookieCsrfTokenRepository 的 saveToken 方法)。
  3. LazyCsrfTokenRepository 自己的 saveToken 則做了修改,相當(dāng)于放棄了 saveToken 的功能,調(diào)用該方法并不會(huì)做保存操作。

使用了 LazyCsrfTokenRepository 之后,只有在使用 csrfToken 時(shí)才會(huì)去存儲(chǔ)它,這樣就可以節(jié)省存儲(chǔ)空間了。

LazyCsrfTokenRepository 的配置方式也很簡(jiǎn)單,在我們使用 Spring Security 時(shí),如果對(duì) csrf 不做任何配置,默認(rèn)其實(shí)就是 LazyCsrfTokenRepository+HttpSessionCsrfTokenRepository 組合。

當(dāng)然我們也可以自己配置,如下:

  1. @Override 
  2. protected void configure(HttpSecurity http) throws Exception { 
  3.     http.authorizeRequests().anyRequest().authenticated() 
  4.             .and() 
  5.             .formLogin() 
  6.             .loginPage("/login.html"
  7.             .successHandler((req,resp,authentication)->{ 
  8.                 resp.getWriter().write("success"); 
  9.             }) 
  10.             .permitAll() 
  11.             .and() 
  12.             .csrf().csrfTokenRepository(new LazyCsrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())); 

4.小結(jié)

今天主要和小伙伴聊了一下 Spring Security 中 csrf 防御的原理。

整體來說,就是兩個(gè)思路:

生成 csrfToken 保存在 HttpSession 或者 Cookie 中。

請(qǐng)求到來時(shí),從請(qǐng)求中提取出來 csrfToken,和保存的 csrfToken 做比較,進(jìn)而判斷出當(dāng)前請(qǐng)求是否合法。

本文轉(zhuǎn)載自微信公眾號(hào)「江南一點(diǎn)雨」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系江南一點(diǎn)雨公眾號(hào)。

 

責(zé)任編輯:武曉燕 來源: 江南一點(diǎn)雨
相關(guān)推薦

2021-06-03 10:16:12

CSRF攻擊SpringBoot

2022-05-19 11:29:14

計(jì)時(shí)攻擊SpringSecurity

2016-09-30 15:59:41

2021-04-28 06:26:11

Spring Secu功能實(shí)現(xiàn)源碼分析

2021-04-23 07:33:10

SpringSecurity單元

2016-09-21 10:11:19

2021-04-19 07:57:23

Spring 源碼GetBean

2013-05-22 18:32:57

2022-12-07 08:02:43

Spring流程IOC

2020-09-16 08:07:54

權(quán)限粒度Spring Secu

2023-11-03 07:58:54

CORSSpring

2022-11-26 00:00:02

2021-08-29 18:36:57

項(xiàng)目

2022-05-05 10:40:36

Spring權(quán)限對(duì)象

2020-09-02 08:09:10

攻擊防御Shiro

2011-05-16 14:26:28

2020-06-17 08:31:10

權(quán)限控制Spring Secu

2021-07-27 10:49:10

SpringSecurity權(quán)限

2022-08-17 07:52:31

Spring循環(huán)依賴單例池

2017-05-16 10:39:02

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)