javascript
springsecurity sessionregistry session共享_要学就学透彻!Spring Security 中 CSRF 防御源码解析...
今日干貨
剛剛發(fā)表查看:66666回復:666公眾號后臺回復 ssm,免費獲取松哥純手敲的 SSM 框架學習干貨。
上篇文章松哥和大家聊了什么是 CSRF 攻擊,以及 CSRF 攻擊要如何防御。主要和大家聊了 Spring Security 中處理該問題的幾種辦法。
今天松哥來和大家簡單的看一下 Spring Security 中,CSRF 防御源碼。
本文是本系列第 19 篇,閱讀本系列前面文章有助于更好的理解本文:
本文主要從兩個方面來和大家講解:
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);
}
這里三個方法:
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();
?}
}
這段源碼其實也很好理解:
這是默認的方案,適用于前后端不分的開發(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);
}
這個方法我來稍微解釋下:
如此之后,就完成了整個校驗工作了。
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;
????}
???}
??}
?}
}
這里,我說三點:
使用了 LazyCsrfTokenRepository 之后,只有在使用 csrfToken 時才會去存儲它,這樣就可以節(jié)省存儲空間了。
LazyCsrfTokenRepository 的配置方式也很簡單,在我們使用 Spring Security 時,如果對 csrf 不做任何配置,默認其實就是 LazyCsrfTokenRepository+HttpSessionCsrfTokenRepository 組合。
當然我們也可以自己配置,如下:
@Overrideprotected?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 防御的原理。
整體來說,就是兩個思路:
好啦,不知道小伙伴們有沒有 GET 到呢?如果覺得有收獲,記得點個在看鼓勵下松哥哦~
今日干貨
剛剛發(fā)表查看:13500回復:135公眾號后臺回復 SpringBoot,免費獲取 274 頁SpringBoot修煉手冊。
總結(jié)
以上是生活随笔為你收集整理的springsecurity sessionregistry session共享_要学就学透彻!Spring Security 中 CSRF 防御源码解析...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mos管防倒灌电路_MOS管自举电路工作
- 下一篇: 基坑计算理论m法弹性支点法_人工冲孔轻型