原文: http://feclub.cn/post/content...html
这里再也不介绍CSRF,已经了解CSRF原理的同窗能够直接跳到:“三、先后端分离下有何不一样?”。前端
不太了解的同窗能够看这两篇对CSRF介绍比较详细的参考文章:vue
CSRF 攻击的应对之道webpack
浅谈CSRF攻击方式nginx
若是来不及了解CSRF的原理,能够这么理解:有一我的发给你一个搞(mei)笑(nv)图片连接,你打开这个连接以后,便马上收到了短信:你的银行里的钱已经转移到这我的的账户了。git
上面这个例子固然有点危言耸听,固然能够肯定的是确实会有这样的漏洞:你打开了一个未知域名的连接,而后你就自动发了条广告帖子、你的Gmail的邮件内容就泄露了、你的百度登陆状态就没了……github
防护方案在上面的两篇文章里也有提到,总结下,无外乎三种:web
用户操做限制,好比验证码;ajax
请求来源限制,好比限制HTTP Referer才能完成操做;算法
token验证机制,好比请求数据字段中添加一个token,响应请求时校验其有效性;
第一种方案明显严重影响了用户体验,并且还有额外的开发成本;第二种方案成本最低,可是并不能保证100%安全,并且颇有可能会埋坑;第三种方案,可取!
token验证的CSRF防护机制是公认最合适的方案,也是本文讨论的重点。
《CSRF 攻击的应对之道》这篇文章里有提到:
要把全部请求都改成 XMLHttpRequest 请求,这样几乎是要重写整个网站,这代价无疑是不能接受的
咱们前端架构早已经告别了服务端语言(PHP/JAVA等)绑定路由、携带数据渲染模板引擎的方式(毕竟是2011年的文章了,咱们笑而不语)。
固然, 前端不要高兴的太早:先后端分离以后,Nodejs不具有完善的服务端SESSION、数据库等功能。
总结一下,在“更先进”的前端架构下,与以往的架构会有一些区别:
Nodejs层不处理SESSION,没法直接实现会话状态数据保存;
全部的数据经过Ajax异步获取,能够灵活实现token方案;
如上文提到,这里仅仅讨论在“更先进”的前端后端架构背景下的token防护方案的实现。
token防护的总体思路是:
第一步:后端随机产生一个token,把这个token保存在SESSION状态中;同时,后端把这个token交给前端页面;
第二步:下次前端须要发起请求(好比发帖)的时候把这个token加入到请求数据或者头信息中,一块儿传给后端;
第三步:后端校验前端请求带过来的token和SESSION里的token是否一致;
上文提到过,先后端分离状态下,Nodejs是不具有SESSION功能的。那这种token防护机制是否是就没法实现了呢?
确定不是。咱们能够借助cookie把这个流程升级下:
第一步:后端随机产生一个token,基于这个token经过SHA-56等散列算法生成一个密文;
第二步:后端将这个token和生成的密文都设置为cookie,返回给前端;
第三步:前端须要发起请求的时候,从cookie中获取token,把这个token加入到请求数据或者头信息中,一块儿传给后端;
第四步:后端校验cookie中的密文,以及前端请求带过来的token,进行正向散列验证;
固然这样实现也有须要注意的:
散列算法都是须要计算的,这里会有性能风险;
token参数必须由前端处理以后交给后端,而不能直接经过cookie;
cookie更臃肿,会不可避免地让头信息更重;
如今方案肯定了,具体该如何实现呢?
咱们的技术栈是 koa(服务端)
+ Vue.js(前端)
。有兴趣能够看这些资料:
在服务端,实现了一个token生成的中间件,koa-grace-csrf:
// 注意:代码有作精简 const tokens = require('./lib/tokens'); return function* csrf(next) { let curSecret = this.cookies.get('密文的cookie'); // 其余若是要获取参数,则为配置参数值 let curToken = '请求http头信息中的token'; // token不存在 if (!curToken || !curSecret) { return this.throw('CSRF Token Not Found!',403) } // token校验失败 if (!tokens.verify(curSecret, curToken)) { return this.throw('CSRF token Invalid!',403) } yield next; // 不管何种状况都种两个cookie // cookie_key: 当前token的cookie_key,httpOnly let secret = tokens.secretSync(); this.cookies.set(options.cookie_key, secret); // cookie_token: 当前token的的content,不须要httpOnly let newToken = tokens.create(secret); this.cookies.set(options.cookie_token, newToken) }
在前端代码中,对发送ajax请求的封装稍做优化:
this.$http.post(url, data, { headers: { 'http请求头信息字段名': 'cookie中的token' } }).then((res) => {})
总结一下:
Nodejs生成一个随机数,经过随机数生成散列密文;并将随机数和密文存到cookie;
客户端JS获取cookie中的随机数,经过http头信息交给Nodejs;
Nodejs响应请求,校验cookie中的密文和头信息中的随机数是否匹配;
这里依旧有个细节值得提一下:Nodejs的上层通常是nginx,而nginx默认会过滤头信息中不合法的字段(好比头信息字段名包含“_”的),这里在写头信息的时候须要注意。
上文也提到,经过cookie及http头信息传递加密token会有不少弊端;有没有更优雅的实现方案呢?
回溯下CSRF产生的根本缘由:cookie会被第三方发起的跨站请求携带,这本质上是HTTP协议设计的漏洞。
那么,咱们能不能经过cookie的某个属性禁止cookie的这个特性呢?
好消息是,在最新的RFC规范中已经加入了“samesite”属性。细节这里再也不赘述,能够参考:
固然,目前为止,客户端对samesite属性的支持并非特别好;回到先后端分离架构下,咱们明确下先后端分离框架的基本原则:
后端(Java / PHP )职责:
服务层颗粒化接口,以便前端Nodejs层异步并发调用;
用户状态保存,实现用户权限等各类功能;
前端(Nodejs + Javascript)职责:
Nodejs层完成路由托管及模板引擎渲染功能
Nodejs层不负责实现任何SESSION和数据库功能
咱们提到,前端Nodejs层不负责实现任何SESSION和数据库功能
,但有没有可能把后端缓存系统作成公共服务提供给Nodejs层使用呢?想一想感受前端整条路都亮了有木有?!这里先挖一个坑,后续慢慢填。
这里再顺便提一下,新架构下的XSS防护。
犹记得,在狼厂使用PHP的年代,常常被安所有门曝出各种XSS漏洞,而后就在smaty里添加各类escape
滤镜,可是添加以后发现居然把原始数据也给转义了。
固然,如今更多要归功于各类MVVM
单页面应用:使得前端彻底不须要经过读取URL中的参数来控制VIEW。
不过,还有一点值得一提:先后端分离框架下,路由由Nodejs控制;我本身要获取的后端参数和须要用在业务逻辑的参数,在主观上前端同窗更好把握一些。
因此, 在koa(服务端)
+ Vue.js(前端)
架构下基本不用顾虑XSS问题(至少不会被全安组追着问XSS漏洞啥时候修复)。
要不学PHP、看Java、玩Python作全栈好了?