1. 基礎知識##
csrf就是誘導已登錄過的用戶在不知情的情況下,使用自己的登錄憑據來完成一些不可告人之事。比如利用img標簽或者script標簽的src屬性自動訪問一些敏感api,或者是偽造一個form標簽,action寫的是一些敏感api,通過js自動提交表單等。
1.1 防御手段###
原則上修改功能的API,都要避免使用GET方式。然后就是兩種防護手段,一個是校驗referer,一個是csrftoken,前者用curl就能破,后者稍微麻煩一點點,也能破。雖然沒法完美防御,但是網站這些基礎功能還是要有,要不然漏掃都過不去。
2. springboot中的實現##
springboot是用的csrftoken值來實現的,就是每個post請求會生成一個token,這個值不在cookie里面,所以偽造沒用,到時服務器端會進行比對,發現不一致就拒絕服務。
弊端就是改造舊系統時要每個form都要改,ajax那種post的提交也需要寫額外的函數獲取token在放到所有請求里面,所以這個功能要提前規划,后面再改就比較麻煩了。
和cors類似,也是用了一個filter,CsrfFilter來實現的過濾功能,底層結構是HttpSessionCsrfTokenRepository,提供了3個方法。
CsrfFilter.java
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
request.setAttribute(HttpServletResponse.class.getName(), response);
//獲取服務器保存的token
CsrfToken csrfToken = this.tokenRepository.loadToken(request);
final boolean missingToken = csrfToken == null;
//缺少token 重新生成並保存
if (missingToken) {
csrfToken = this.tokenRepository.generateToken(request);
this.tokenRepository.saveToken(csrfToken, request, response);
}
request.setAttribute(CsrfToken.class.getName(), csrfToken);
request.setAttribute(csrfToken.getParameterName(), csrfToken);
//如果url不匹配需要校驗的csrf 就直接略過
if (!this.requireCsrfProtectionMatcher.matches(request)) {
filterChain.doFilter(request, response);
return;
}
//獲得客戶端token
String actualToken = request.getHeader(csrfToken.getHeaderName());
if (actualToken == null) {
actualToken = request.getParameter(csrfToken.getParameterName());
}
//token不匹配
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);
}
然后就是tokenRepository,基本都是使用LazyCsrfTokenRepository封裝了HttpSessionCsrfTokenRepository。作用就是只有在實際取token時才會保存session,節省服務器資源,HttpSessionCsrfTokenRepository實現了CsrfTokenRepository接口定義三個關於token的方法
CsrfToken generateToken(HttpServletRequest request);
void saveToken(CsrfToken token, HttpServletRequest request,
HttpServletResponse response);
CsrfToken loadToken(HttpServletRequest request);
3. 如何開啟csrf防御##
csrf默認是開啟的,配下忽略的url就可以了。