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); 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); if (!this.requireCsrfProtectionMatcher.matches(request)) { filterChain.doFilter(request, response); return; } 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)); } return; }
filterChain.doFilter(request, response); }
|
上面要求客户端在进行如 post,put,delete
请求时,在参数或head里携带登录时生成的token。get head
等不拦截,要求服务端符合Restful
规范,不要用get更新用户数据。
前端获取token
像上面那样简单的启用csrf
,用户登录时会设置token
到session
里,那前端怎么获取呢? 可以开一个接口返回此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
取出来放在参数上。服务端比较参数上的token
和cookie
里的token
是否一致即可。因为第三方网站无法获取本站的cookie
,也就无法伪造参数上的token,所以这种方案也是安全的。
springSecurity csrf的主要配置方法
1 2 3 4 5 6 7 8 9 10
|
http.csrf().sessionAuthenticationStrategy();
http.csrf().csrfTokenRepository();
http.csrf().ignoringAntMatchers();
http.csrf().requireCsrfProtectionMatcher()
|