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