日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

springsecurity sessionregistry session共享_要学就学透彻!Spring Security 中 CSRF 防御源码解析...

發(fā)布時間:2024/9/15 javascript 47 豆豆
生活随笔 收集整理的這篇文章主要介紹了 springsecurity sessionregistry session共享_要学就学透彻!Spring Security 中 CSRF 防御源码解析... 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

今日干貨

剛剛發(fā)表查看:66666回復:666

公眾號后臺回復 ssm,免費獲取松哥純手敲的 SSM 框架學習干貨。

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

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

本文是本系列第 19 篇,閱讀本系列前面文章有助于更好的理解本文:

  • 挖一個大坑,Spring Security 開搞!
  • 松哥手把手帶你入門 Spring Security,別再問密碼怎么解密了
  • 手把手教你定制 Spring Security 中的表單登錄
  • Spring Security 做前后端分離,咱就別做頁面跳轉(zhuǎn)了!統(tǒng)統(tǒng) JSON 交互
  • Spring Security 中的授權操作原來這么簡單
  • Spring Security 如何將用戶數(shù)據(jù)存入數(shù)據(jù)庫?
  • Spring Security+Spring Data Jpa 強強聯(lián)手,安全管理只有更簡單!
  • Spring Boot + Spring Security 實現(xiàn)自動登錄功能
  • Spring Boot 自動登錄,安全風險要怎么控制?
  • 在微服務項目中,Spring Security 比 Shiro 強在哪?
  • SpringSecurity 自定義認證邏輯的兩種方式(高級玩法)
  • Spring Security 中如何快速查看登錄用戶 IP 地址等信息?
  • Spring Security 自動踢掉前一個登錄用戶,一個配置搞定!
  • Spring Boot + Vue 前后端分離項目,如何踢掉已登錄用戶?
  • Spring Security 自帶防火墻!你都不知道自己的系統(tǒng)有多安全!
  • 什么是會話固定攻擊?Spring Boot 中要如何防御會話固定攻擊?
  • 集群化部署,Spring Security 要如何處理 session 共享?
  • 松哥手把手教你在 SpringBoot 中防御 CSRF 攻擊!so easy!
  • 本文主要從兩個方面來和大家講解:

  • 返回給前端的 _csrf 參數(shù)是如何生成的。
  • 前端傳來的 _csrf 參數(shù)是如何校驗的。
  • 1.隨機字符串生成

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

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

    public?interface?CsrfToken?extends?Serializable?{
    ?String?getHeaderName();
    ?String?getParameterName();
    ?String?getToken();

    }

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

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

    默認情況下使用的是 DefaultCsrfToken,我們來稍微看下 DefaultCsrfToken:

    public?final?class?DefaultCsrfToken?implements?CsrfToken?{
    ?private?final?String?token;
    ?private?final?String?parameterName;
    ?private?final?String?headerName;
    ?public?DefaultCsrfToken(String?headerName,?String?parameterName,?String?token)?{
    ??this.headerName?=?headerName;
    ??this.parameterName?=?parameterName;
    ??this.token?=?token;
    ?}
    ?public?String?getHeaderName()?{
    ??return?this.headerName;
    ?}
    ?public?String?getParameterName()?{
    ??return?this.parameterName;
    ?}
    ?public?String?getToken()?{
    ??return?this.token;
    ?}
    }

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

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

    public?interface?CsrfTokenRepository?{
    ?CsrfToken?generateToken(HttpServletRequest?request);
    ?void?saveToken(CsrfToken?token,?HttpServletRequest?request,
    ???HttpServletResponse?response);
    ?CsrfToken?loadToken(HttpServletRequest?request);
    }

    這里三個方法:

  • generateToken 方法就是 CsrfToken 的生成過程。
  • saveToken 方法就是保存 CsrfToken。
  • loadToken 則是如何加載 CsrfToken。
  • CsrfTokenRepository 有四個實現(xiàn)類,在上篇文章中,我們用到了其中兩個:HttpSessionCsrfTokenRepository 和 CookieCsrfTokenRepository,其中 HttpSessionCsrfTokenRepository 是默認的方案。

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

    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;
    ?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);
    ??}
    ?}
    ?public?CsrfToken?loadToken(HttpServletRequest?request)?{
    ??HttpSession?session?=?request.getSession(false);
    ??if?(session?==?null)?{
    ???return?null;
    ??}
    ??return?(CsrfToken)?session.getAttribute(this.sessionAttributeName);
    ?}
    ?public?CsrfToken?generateToken(HttpServletRequest?request)?{
    ??return?new?DefaultCsrfToken(this.headerName,?this.parameterName,
    ????createNewToken());
    ?}
    ?private?String?createNewToken()?{
    ??return?UUID.randomUUID().toString();
    ?}
    }

    這段源碼其實也很好理解:

  • saveToken 方法將 CsrfToken 保存在 HttpSession 中,將來再從 HttpSession 中取出和前端傳來的參數(shù)做筆記。
  • loadToken 方法當然就是從 HttpSession 中讀取 CsrfToken 出來。
  • generateToken 是生成 CsrfToken 的過程,可以看到,生成的默認載體就是 DefaultCsrfToken,而 CsrfToken 的值則通過 createNewToken 方法生成,是一個 UUID 字符串。
  • 在構造 DefaultCsrfToken 是還有兩個參數(shù) headerName 和 parameterName,這兩個參數(shù)是前端保存參數(shù)的 key。
  • 這是默認的方案,適用于前后端不分的開發(fā),具體用法可以參考上篇文章。

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

    public?final?class?CookieCsrfTokenRepository?implements?CsrfTokenRepository?{
    ?static?final?String?DEFAULT_CSRF_COOKIE_NAME?=?"XSRF-TOKEN";
    ?static?final?String?DEFAULT_CSRF_PARAMETER_NAME?=?"_csrf";
    ?static?final?String?DEFAULT_CSRF_HEADER_NAME?=?"X-XSRF-TOKEN";
    ?private?String?parameterName?=?DEFAULT_CSRF_PARAMETER_NAME;
    ?private?String?headerName?=?DEFAULT_CSRF_HEADER_NAME;
    ?private?String?cookieName?=?DEFAULT_CSRF_COOKIE_NAME;
    ?private?boolean?cookieHttpOnly?=?true;
    ?private?String?cookiePath;
    ?private?String?cookieDomain;
    ?public?CookieCsrfTokenRepository()?{
    ?}
    ?@Override
    ?public?CsrfToken?generateToken(HttpServletRequest?request)?{
    ??return?new?DefaultCsrfToken(this.headerName,?this.parameterName,
    ????createNewToken());
    ?}
    ?@Override
    ?public?void?saveToken(CsrfToken?token,?HttpServletRequest?request,
    ???HttpServletResponse?response)?{
    ??String?tokenValue?=?token?==?null???""?:?token.getToken();
    ??Cookie?cookie?=?new?Cookie(this.cookieName,?tokenValue);
    ??cookie.setSecure(request.isSecure());
    ??if?(this.cookiePath?!=?null?&&?!this.cookiePath.isEmpty())?{
    ????cookie.setPath(this.cookiePath);
    ??}?else?{
    ????cookie.setPath(this.getRequestContext(request));
    ??}
    ??if?(token?==?null)?{
    ???cookie.setMaxAge(0);
    ??}
    ??else?{
    ???cookie.setMaxAge(-1);
    ??}
    ??cookie.setHttpOnly(cookieHttpOnly);
    ??if?(this.cookieDomain?!=?null?&&?!this.cookieDomain.isEmpty())?{
    ???cookie.setDomain(this.cookieDomain);
    ??}

    ??response.addCookie(cookie);
    ?}
    ?@Override
    ?public?CsrfToken?loadToken(HttpServletRequest?request)?{
    ??Cookie?cookie?=?WebUtils.getCookie(request,?this.cookieName);
    ??if?(cookie?==?null)?{
    ???return?null;
    ??}
    ??String?token?=?cookie.getValue();
    ??if?(!StringUtils.hasLength(token))?{
    ???return?null;
    ??}
    ??return?new?DefaultCsrfToken(this.headerName,?this.parameterName,?token);
    ?}
    ?public?static?CookieCsrfTokenRepository?withHttpOnlyFalse()?{
    ??CookieCsrfTokenRepository?result?=?new?CookieCsrfTokenRepository();
    ??result.setCookieHttpOnly(false);
    ??return?result;
    ?}
    ?private?String?createNewToken()?{
    ??return?UUID.randomUUID().toString();
    ?}
    }

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

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

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

    2.參數(shù)校驗

    那接下來就是校驗了。

    校驗主要是通過 CsrfFilter 過濾器來進行,我們來看下核心的 doFilterInternal 方法:

    protected?void?doFilterInternal(HttpServletRequest?request,
    ??HttpServletResponse?response,?FilterChain?filterChain)throws?ServletException,?IOException?{
    ?request.setAttribute(HttpServletResponse.class.getName(),?response);
    ?CsrfToken?csrfToken?=?this.tokenRepository.loadToken(request);
    ?final?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))?{
    ??filterChain.doFilter(request,?response);
    ??return;
    ?}
    ?String?actualToken?=?request.getHeader(csrfToken.getHeaderName());
    ?if?(actualToken?==?null)?{
    ??actualToken?=?request.getParameter(csrfToken.getParameterName());
    ?}
    ?if?(!csrfToken.getToken().equals(actualToken))?{
    ??if?(this.logger.isDebugEnabled())?{
    ???this.logger.debug("Invalid?CSRF?token?found?for?"
    ?????+?UrlUtils.buildFullRequestUrl(request));
    ??}
    ??if?(missingToken)?{
    ???this.accessDeniedHandler.handle(request,?response,
    ?????new?MissingCsrfTokenException(actualToken));
    ??}
    ??else?{
    ???this.accessDeniedHandler.handle(request,?response,
    ?????new?InvalidCsrfTokenException(csrfToken,?actualToken));
    ??}
    ??return;
    ?}
    ?filterChain.doFilter(request,?response);
    }

    這個方法我來稍微解釋下:

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

    3.LazyCsrfTokenRepository

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

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

    if?(missingToken)?{
    ?csrfToken?=?this.tokenRepository.generateToken(request);
    ?this.tokenRepository.saveToken(csrfToken,?request,?response);
    }

    生成 CsrfToken 并保存,但實際上卻沒什么用,因為 GET 請求不需要 CSRF 攻擊校驗。

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

    LazyCsrfTokenRepository 實際上不能算是一個真正的 CsrfTokenRepository,它是一個代理,可以用來增強 HttpSessionCsrfTokenRepository 或者 CookieCsrfTokenRepository 的功能:

    public?final?class?LazyCsrfTokenRepository?implements?CsrfTokenRepository?{
    ?@Override
    ?public?CsrfToken?generateToken(HttpServletRequest?request)?{
    ??return?wrap(request,?this.delegate.generateToken(request));
    ?}
    ?@Override
    ?public?void?saveToken(CsrfToken?token,?HttpServletRequest?request,
    ???HttpServletResponse?response)?{
    ??if?(token?==?null)?{
    ???this.delegate.saveToken(token,?request,?response);
    ??}
    ?}
    ?@Override
    ?public?CsrfToken?loadToken(HttpServletRequest?request)?{
    ??return?this.delegate.loadToken(request);
    ?}
    ?private?CsrfToken?wrap(HttpServletRequest?request,?CsrfToken?token)?{
    ??HttpServletResponse?response?=?getResponse(request);
    ??return?new?SaveOnAccessCsrfToken(this.delegate,?request,?response,?token);
    ?}
    ?private?static?final?class?SaveOnAccessCsrfToken?implements?CsrfToken?{
    ??private?transient?CsrfTokenRepository?tokenRepository;
    ??private?transient?HttpServletRequest?request;
    ??private?transient?HttpServletResponse?response;

    ??private?final?CsrfToken?delegate;

    ??SaveOnAccessCsrfToken(CsrfTokenRepository?tokenRepository,
    ????HttpServletRequest?request,?HttpServletResponse?response,
    ????CsrfToken?delegate)?{
    ???this.tokenRepository?=?tokenRepository;
    ???this.request?=?request;
    ???this.response?=?response;
    ???this.delegate?=?delegate;
    ??}
    ??@Override
    ??public?String?getToken()?{
    ???saveTokenIfNecessary();
    ???return?this.delegate.getToken();
    ??}
    ??private?void?saveTokenIfNecessary()?{
    ???if?(this.tokenRepository?==?null)?{
    ????return;
    ???}

    ???synchronized?(this)?{
    ????if?(this.tokenRepository?!=?null)?{
    ?????this.tokenRepository.saveToken(this.delegate,?this.request,
    ???????this.response);
    ?????this.tokenRepository?=?null;
    ?????this.request?=?null;
    ?????this.response?=?null;
    ????}
    ???}
    ??}

    ?}
    }

    這里,我說三點:

  • generateToken 方法,該方法用來生成 CsrfToken,默認 CsrfToken 的載體是 DefaultCsrfToken,現(xiàn)在換成了 SaveOnAccessCsrfToken。
  • SaveOnAccessCsrfToken 和 DefaultCsrfToken 并沒有太大區(qū)別,主要是 getToken 方法有區(qū)別,在 SaveOnAccessCsrfToken 中,當開發(fā)者調(diào)用 getToken 想要去獲取 csrfToken 時,才會去對 csrfToken 做保存操作(調(diào)用 HttpSessionCsrfTokenRepository 或者 CookieCsrfTokenRepository 的 saveToken 方法)。
  • LazyCsrfTokenRepository 自己的 saveToken 則做了修改,相當于放棄了 saveToken 的功能,調(diào)用該方法并不會做保存操作。
  • 使用了 LazyCsrfTokenRepository 之后,只有在使用 csrfToken 時才會去存儲它,這樣就可以節(jié)省存儲空間了。

    LazyCsrfTokenRepository 的配置方式也很簡單,在我們使用 Spring Security 時,如果對 csrf 不做任何配置,默認其實就是 LazyCsrfTokenRepository+HttpSessionCsrfTokenRepository 組合。

    當然我們也可以自己配置,如下:

    @Override
    protected?void?configure(HttpSecurity?http)?throws?Exception?{
    ????http.authorizeRequests().anyRequest().authenticated()
    ????????????.and()
    ????????????.formLogin()
    ????????????.loginPage("/login.html")
    ????????????.successHandler((req,resp,authentication)->{
    ????????????????resp.getWriter().write("success");
    ????????????})
    ????????????.permitAll()
    ????????????.and()
    ????????????.csrf().csrfTokenRepository(new?LazyCsrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()));
    }

    4.小結(jié)

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

    整體來說,就是兩個思路:

  • 生成 csrfToken 保存在 HttpSession 或者 Cookie 中。
  • 請求到來時,從請求中提取出來 csrfToken,和保存的 csrfToken 做比較,進而判斷出當前請求是否合法。
  • 好啦,不知道小伙伴們有沒有 GET 到呢?如果覺得有收獲,記得點個在看鼓勵下松哥哦~

    今日干貨

    剛剛發(fā)表查看:13500回復:135

    公眾號后臺回復 SpringBoot,免費獲取 274 頁SpringBoot修煉手冊。

    總結(jié)

    以上是生活随笔為你收集整理的springsecurity sessionregistry session共享_要学就学透彻!Spring Security 中 CSRF 防御源码解析...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。