CSRF介紹
CSRF(Cross-site request forgery),中文名稱:跨站請求偽造,也被稱為:one click attack/session riding,縮寫為:CSRF/XSRF。
具體SCRF的介紹和攻擊方式請參看百度百科的介紹和一位大牛的分析:
CSRF百度百科
配置步驟
1.依賴jar包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
< properties > </ properties > < dependency > < groupId >org.springframework.security</ groupId > < artifactId >spring-security-core</ artifactId > < version >${spring.security.version}</ version > </ dependency > < dependency > < groupId >org.springframework.security</ groupId > < artifactId >spring-security-web</ artifactId > < version >${spring.security.version}</ version > </ dependency > < dependency > < groupId >org.springframework.security</ groupId > < artifactId >spring-security-config</ artifactId > < version >${spring.security.version}</ version > </ dependency > |
2.web.xml配置
1
2
3
4
5
6
7
8
9
|
< filter > < filter-name >springSecurityFilterChain</ filter-name > < filter-class >org.springframework.web.filter.DelegatingFilterProxy</ filter-class > </ filter > < filter-mapping > < filter-name >springSecurityFilterChain</ filter-name > < url-pattern >/*</ url-pattern > </ filter-mapping > |
3.Spring配置文件配置
1
2
3
4
5
6
7
8
|
< bean id = "csrfSecurityRequestMatcher" class = "com.xxx.CsrfSecurityRequestMatcher" ></ bean > < security:http auto-config = "true" use-expressions = "true" > < security:headers > < security:frame-options disabled = "true" /> </ security:headers > < security:csrf request-matcher-ref = "csrfSecurityRequestMatcher" /> </ security:http > |
4.自定義RequestMatcher的實現類CsrfSecurityRequestMatcher
這個類被用來自定義哪些請求是不需要進行攔截過濾的。如果配置csrf,所有http請求都被會CsrfFilter攔截,而CsrfFilter中有一個私有類DefaultRequiresCsrfMatcher。
源碼1:DefaultRequiresCsrfMatcher類
1
2
3
4
5
6
7
8
9
10
11
|
private static final class DefaultRequiresCsrfMatcher implements RequestMatcher { private final HashSet<String> allowedMethods; private DefaultRequiresCsrfMatcher() { this .allowedMethods = new HashSet(Arrays.asList( new String[]{ "GET" , "HEAD" , "TRACE" , "OPTIONS" })); } public boolean matches(HttpServletRequest request) { return ! this .allowedMethods.contains(request.getMethod()); } } |
從這段源碼可以發現,POST方法被排除在外了,也就是說只有GET|HEAD|TRACE|OPTIONS這4類方法會被放行,其它Method的http請求,都要驗證_csrf的token是否正確,而通常post方式調用rest接口服務時,又沒有_csrf的token,所以會導致我們的rest接口調用失敗,我們需要自定義一個類對該類型接口進行放行。來看下我們自定義的過濾器:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public class CsrfSecurityRequestMatcher implements RequestMatcher { private Pattern allowedMethods = Pattern.compile( "^(GET|HEAD|TRACE|OPTIONS)$" ); private RegexRequestMatcher unprotectedMatcher = new RegexRequestMatcher( "^/rest/.*" , null ); @Override public boolean matches(HttpServletRequest request) { if (allowedMethods.matcher(request.getMethod()).matches()){ return false ; } return !unprotectedMatcher.matches(request); } } |
說明:一般我們定義的rest接口服務,都帶上 /rest/ ,所以如果你的項目中不是使用的這種,或者項目中沒有rest服務,這個類完全可以省略的。
5.post請求配置
一般我們的項目中都有一個通用的jsp文件,就是每個頁面都會引用的,所以我們可以在通用文件中做如下配置:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
<meta name= "_csrf" content= "${_csrf.token}" /> <meta name= "_csrf_header" content= "${_csrf.headerName}" /> <script> var token = $( "meta[name='_csrf']" ).attr( "content" ); var header = $( "meta[name='_csrf_header']" ).attr( "content" ); $.ajaxSetup({ beforeSend: function (xhr) { if (header && token ){ xhr.setRequestHeader(header, token); } }} ); </script> |
$.ajaxSetup的意思就是給我們所有的請求都加上這個header和token,或者放到form表單中。注意,_csrf這個要與spring security的配置文件中的配置相匹配,默認為_csrf。
源碼解析
我們知道,既然配置了csrf,所有的http請求都會被CsrfFilter攔截到,所以看下CsrfFilter的源碼就對原理一目了然了。這里我們只看具體過濾的方法即可:
源碼3:CsrfFilter的doFilterInternal方法
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
|
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { request.setAttribute(HttpServletResponse. class .getName(), response); CsrfToken csrfToken = this .tokenRepository.loadToken(request); boolean missingToken = csrfToken == null ; if (missingToken) { //如果token為空,說明第一次訪問,生成一個token對象 csrfToken = this .tokenRepository.generateToken(request); this .tokenRepository.saveToken(csrfToken, request, response); } request.setAttribute(CsrfToken. class .getName(), csrfToken); //把token對象放到request中,注意這里key是csrfToken.getParameterName()= _csrf,所以我們頁面上才那么寫死。 request.setAttribute(csrfToken.getParameterName(), csrfToken); //這個macher就是我們在Spring配置文件中自定義的過濾器,也就是GET,HEAD, TRACE, OPTIONS和我們的rest都不處理 if (! this .requireCsrfProtectionMatcher.matches(request)) { filterChain.doFilter(request, response); } else { 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)); } } else { filterChain.doFilter(request, response); } } } |
從源碼中可以看到,通過我們自定義的過濾器以外的post請求都需要進行token驗證。
本來呢,是想截圖弄個案例上去的,然后通過斷點看看頁面和后臺的傳值情況....不過,我這里沒法上傳圖片抓狂。好吧,就總結這么多吧!如果有寫的不對的或者有其他問題可以留言交流。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。
原文鏈接:https://blog.csdn.net/u013185616/article/details/70446392