CSRF-protection-with-angularjs-spring-security

使用AnaularJS和Spring-Security防范CSRF攻击

HTTP协议无状态

HTTP协议是无状态的,这意味着每个请求都是独立的,而现代网页应用有大量的交互,交互需要承前启后,比如保持我的登陆状态、保持我的购物车数据。当前有两种用于保持HTTP状态的主要技术,一个是Cookie,而另一个则是Session。Cookie主要用于客户端,而Session主要在服务器端。
Cookie是保存在硬盘上的文本文件,浏览器会去读写它。cookie数据始终在同源的http请求中携带(即使不需要),既会在浏览器和服务器间来回传递。

例子:
在登陆页登陆成功时,将登陆成功的状态保存在Cookie中,在后续的操作中,从Cookie中获取我们是否已登陆的状态,从而决定我们能执行哪些操作。
我们在首页点击购买时,将商品信息保存在Cookie中,当我们转到购物车页面时,获取Cookie中的商品信息,并在页面上显示出来。
Session通常代表了一个服务器端的连接,是一段运行中的程序,通常每一个Session有一个ID,通常Session会与Cookie一起使用
例子;
在Cookie中我们保存SessionID,当与服务器连接时,服务器根据ID切换到对应的Session,这里保留了所有的用户状态。

什么是CSRF,如何防范?

CSRF(Cross-site request forgery)跨站请求攻击,是一种网络攻击方式,该攻击可以在受害者毫不知情的情况下以受害者名义伪造请求发送给受攻击站点,从而在未授权的情况下执行在权限保护之下的操作。
例子;
我们访问一个银行网站,www.bank.com,登陆成功后,访问www.bank.com/transfer,可以进行转帐,如果没有关闭浏览器,在客户端和服务器端都保存了我们的登陆状态,这时去访问另一个网站,而那个网站的页面上有这样一个标签<img src=’www.bank.com/transfer’ />,银行服务器端会判定这次请求合法,从而转帐成功。这就是CSRF攻击。
要防范CSRF攻击,我们在客户端的请求要加一些攻击者没法提供的信息给服务器,由服务器判断。比如在表单、Cookie中加入token(在计算机身份认证中是令牌的意思)数据,服务器端进行验证。每次请求如果加一个验证码完全解决了CSRF攻击的问题,但是用户体验差。

AngularJS是如何做的

不用做什么事情,$http服务会自动完成了两件事:
在当前域名下寻找名字为XSRF-TOKEN的cookie
如果找到,读取它的值,并把它加到http请求的头部X-XSRF-TOKEN字段。
如果需要,可以通过$httpProvider改变这两个默认名字。
用Cookie存放token的可以起到作用的原理是,正常情况下,网页只能获取所在域的Cookie
代码片断:

1
2
3
4
5
6
7
8
//angularjs.js
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',

var xsrfValue = urlIsSameOrigin(config.url)? $$cookieReader()[config.xsrfCookieName || defaults.xsrfCookieName]: undefined;
if (xsrfValue) {
reqHeaders[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue;
}

Spring Security是如何做的

Spring Security是Spring的子项目,提供了一揽子web安全解决方案,它能生成token,并在之后通过验证这个token来判断请求是否合法。在这里我们生成带token信息的Cookie,事实上我们这么做是为了配合AngularJS的方案,因为token也可以放在其它地方,比如表单中。
代码片断:

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
@Configuration
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
protected static class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().and().authorizeRequests()
.antMatchers("/**").permitAll().anyRequest()
.authenticated().and().csrf()
.csrfTokenRepository(csrfTokenRepository()).and()
.addFilterAfter(csrfHeaderFilter(), CsrfFilter.class);
}
private Filter csrfHeaderFilter() {
return new OncePerRequestFilter() {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException
{

CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
if (csrf != null) {
Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
String token = csrf.getToken();
if (cookie == null || token != null && !token.equals(cookie.getValue())) {
cookie = new Cookie("XSRF-TOKEN", token);
cookie.setPath("/");
response.addCookie(cookie);
}
}
filterChain.doFilter(request, response);
}
};
}
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
}

小结

CSRF只是网络安全中的一个例子,网络攻击的危害从来没有像现在这样严重,近年来频发的互联网安全事件就是证明。作为一名web开发者,要做到防范于未然,开发出更安全的web应用。

原创文章 欢迎转载但请注明出处。http://beetle2013.github.io/blog/