这种方法要比检查 Referer 要安全一些,token可以在用户登陆后产生并放于 session 之中,然后在每次请求时把token从 session 中拿出,与请求中的 token 进行比对,但这种方法的难点在于如何把token以参数的形式加入请求 。对于 GET 请求,token 将附在请求地址之后,这样 URL 就变成 http://url?csrftoken=tokenvalue 。而对于 POST 请求来说,要在 form 的最后加上 <input type=”hidden” name=”csrftoken” value=https://www.isolves.com/it/aq/wl/2021-10-13/”tokenvalue”/>,这样就把 token 以参数的形式加入请求了 。但是,在一个网站中,可以接受请求的地方非常多,要对于每一个请求都加上 token 是很麻烦的,并且很容易漏掉,通常使用的方法就是在每次页面加载时,使用 JAVAscript 遍历整个 dom 树,对于 dom 中所有的 a 和 form 标签后加入 token 。这样可以解决大部分的请求,但是对于在页面加载之后动态生成的 html 代码,这种方法就没有作用,还需要程序员在编码时手动添加 token 。
该方法还有一个缺点是难以保证 token 本身的安全 。特别是在一些论坛之类支持用户自己发表内容的网站,黑客可以在上面发布自己个人网站的地址 。由于系统也会在这个地址后面加上 token,黑客可以在自己的网站上得到这个 token,并马上就可以发动 CSRF 攻击 。为了避免这一点,系统可以在添加 token 的时候增加一个判断,如果这个链接是链到自己本站的,就在后面添加 token,如果是通向外网则不加 。不过,即使这个 csrftoken 不以参数的形式附加在请求之中,黑客的网站也同样可以通过 Referer 来得到这个token值以发动 CSRF 攻击 。这也是一些用户喜欢手动关闭浏览器 Referer 功能的原因 。
三、在HTTP头中自定义属性并验证
这种方法也是使用 token 并进行验证,和上一种方法不同的是,这里并不是把 token 以参数的形式置于 HTTP 请求之中,而是把它放到 HTTP 头中自定义的属性里 。通过 XMLHttpRequest 这个类,可以一次性给所有该类请求加上 csrftoken 这个 HTTP 头属性,并把 token 值放入其中 。这样解决了上种方法在请求中加入 token 的不便,同时,通过 XMLHttpRequest 请求的地址不会被记录到浏览器的地址栏,也不用担心 token 会透过 Referer 泄露到其他网站中去 。
然而这种方法的局限性非常大 。XMLHttpRequest 请求通常用于 Ajax方法中对于页面局部的异步刷新,并非所有的请求都适合用这个类来发起,而且通过该类请求得到的页面不能被浏览器所记录下,从而进行前进,后退,刷新,收藏等操作,给用户带来不便 。另外,对于没有进行 CSRF防护的遗留系统来说,要采用这种方法来进行防护,要把所有请求都改为 XMLHttpRequest 请求,这样几乎是要重写整个网站,这代价无疑是不能接受的 。
SpringSecurity配置对csrf攻击防护从springsecurity3.2.0版本开始就已经对csrf攻击提供防护 。
版本升级配置
<dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-core</artifactId><version>3.2.0.RELEASE(及以上版本)</version></dependency>
HTTP配置
<!-- http安全配置 --><sec:http access-decision-manager-ref="accessDecisionManager" auto-config="true" entry-point-ref="authenticationEntryPoint"><sec:logout/><sec:anonymous granted-authority="A_ANONYMOUS,A_0" /><sec:custom-filter ref="authenticationFilter" before="FORM_LOGIN_FILTER" /><sec:custom-filter ref="filterSecurityInterceptor" before="FILTER_SECURITY_INTERCEPTOR" /><sec:custom-filter ref="switchRoleProcessingFilter" before="SWITCH_USER_FILTER" /><sec:custom-filter ref="tokenAuthenticationFilter" before="ANONYMOUS_FILTER" /><sec:custom-filter ref="sessionManagementFilter" before="SESSION_MANAGEMENT_FILTER" /><sec:custom-filter ref="loginOutFilter" before="LOGOUT_FILTER" /><!--CSRF防护配置--><sec:csrf/></sec:http>
前端配置
一般我们的项目中都有一个公用文件(如公用js文件),每个页面都会引用该文件,所以我们可以在通用文件中做类似如下配置:
<meta name="_csrf" content="${_csrf.token}"/><meta name="_csrf_header" content="${_csrf.headerName}"/><meta name="_csrf_parameter_name" content="${_csrf.parameterName}"/><script type="text/JavaScript">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>
推荐阅读
- apache-4-请求头和响应头
- Java调用外部REST请求的几种方式
- 超级赞!SpringMVC中的请求参数的完美处理技巧
- Python-快速添加headers请求头
- 增加的 I/O 请求滞后时间会降低虚拟机性能
- CSRF、XSS攻防原理及解决方案
- 从网络请求过程看OkHttp拦截器
- 如何在Safari Mac浏览器中阻止跨站跟踪?
- HTTP请求之gzip压缩知多少
- API 请求失败后发生了什么?