springSecurity 防止csrf攻击

Posted by hcy on July 14, 2020

springSecurity 防止csrf攻击

​ 启用csrf filter。

1
2
3
4
5
6
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf();
}	

​ CsrfFilter处理方法

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
39
40
41
42
43
44
45
46
47
48
49
50
protected void doFilterInternal(HttpServletRequest request,
			HttpServletResponse response, FilterChain filterChain)
					throws ServletException, IOException {
		request.setAttribute(HttpServletResponse.class.getName(), response);
		
    	//从存储token容器内获取,默认是session中获取
		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);
		
    
    	//这里判断某些请求不进行csrf过滤,默认将"GET", "HEAD", "TRACE", "OPTIONS"请求方式排除掉
    	//因为不建议使用上面四种方式做数据更新
		if (!this.requireCsrfProtectionMatcher.matches(request)) {
			filterChain.doFilter(request, response);
			return;
		}
		
    	//从head或paran里获取csrf token
		String actualToken = request.getHeader(csrfToken.getHeaderName());
		if (actualToken == null) {
			actualToken = request.getParameter(csrfToken.getParameterName());
		}
    	//比较请求上传的token与session存储的token是否一致
		if (!csrfToken.getToken().equals(actualToken)) {
			if (this.logger.isDebugEnabled()) {
				this.logger.debug("Invalid CSRF token found for "
						+ UrlUtils.buildFullRequestUrl(request));
			}
            //不一致或session里缺少token走拒绝分支,返回403啥的
			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);
	}

​ 上面要求客户端在进行如 post,put,delete请求时,在参数或head里携带登录时生成的token。get head 等不拦截,要求服务端符合Restful规范,不要用get更新用户数据。

前端获取token

​ 像上面那样简单的启用csrf,用户登录时会设置tokensession里,那前端怎么获取呢? 可以开一个接口返回此token,因为跨域原因,第三方网站是无法获取的,自己网站在打开页面时获取一下存储在页面上。

Token有效期和服务重启问题

​ 因为token存储在session里的,有效期就是session的有效期,在此期间如果服务器被强制重启session来不及钝化这将导致客户端使用正确的token,但无法验证通过,影响用户体验,可以将token存储在redis或数据库里,并设置有效期定期删除。

百度贴吧的例子

​ 参考一下百度贴吧发帖请求时需要参数tbs,这个参数可以从接口 http://tieba.baidu.com/dc/common/tbs 上获取,每次请求都返回一个不一样的token,发帖时将携带token才能发帖成功。他的做法应该是每次调用接口都生成一个token,服务端保存最新的若干个token,发帖时只要和其中一个token一致即可验证通过。

其他方案

​ 除了将 token 存储在session里外,还可以存储在cookie里。

​ 用户登录成功后,生成token放入cookie里,客户端发起请求时将cookie里的token取出来放在参数上。服务端比较参数上的tokencookie里的token是否一致即可。因为第三方网站无法获取本站的cookie,也就无法伪造参数上的token,所以这种方案也是安全的。

springSecurity csrf的主要配置方法

  
  //设置session策略,登录成功后会回调此策略
  http.csrf().sessionAuthenticationStrategy();
  //设置token存储仓库,默认是存储在session里,可以设置存储在数据库里,或客户端cookie里      
  http.csrf().csrfTokenRepository();
  //需要忽略的路径
  http.csrf().ignoringAntMatchers();
  //强制验证的路径,即使是get也验证
  http.csrf().requireCsrfProtectionMatcher()
      

转载请注明出处:https://www.huangchaoyu.com/2020/07/14/springSecurity-防止csrf攻击/