一、CSRF
CSRF(Cross-site request forgery)跨站请求伪造:攻击者诱导受害者进入第三方网站,在第三方网站中,向信任网站发送跨站请求。利用受害者在信任网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对信任网站执行某项操作的目的。
一个典型的CSRF攻击有着如下的流程:
- 用户C打开浏览器,访问受信任网站A,输入用户名和密码请求登录网站A;
- 在用户信息通过验证后,网站A产生sessionid并返回给浏览器,存在Cookie中,此时用户登录网站A成功,可以正常发送请求到网站A;
- 用户未退出网站A之前,在同一浏览器中,打开一个TAB页访问网站B;
- 网站B接收到用户请求后,返回一些攻击性代码,并发出一个请求要求访问第三方站点A;
- 浏览器在接收到这些攻击性代码后,根据网站B的请求,在用户不知情的情况下携带Cookie信息,向网站A发出请求(虽然网站B并不能获得sessionid,但从浏览器向网站A发出的任何请求中无论是从哪个网站发出都会携带它的 Cookie)。网站A并不知道该请求其实是由B发起的,所以会根据用户C的Cookie信息以C的权限处理该请求,导致来自网站B的恶意代码被执行。
二、CSRF Token
我们知道,CSRF的一个特征是,攻击者无法直接窃取到用户的信息(Cookie,Header,网站内容等),仅仅是冒用Cookie中的信息。
而CSRF攻击之所以能够成功,是因为服务器误把攻击者发送的请求当成了用户自己的请求。那么我们可以要求所有的用户请求都携带一个CSRF攻击者无法获取到的Token。服务器通过校验请求是否携带正确的Token,来把正常的请求和攻击的请求区分开,也可以防范CSRF的攻击。
大概的流程是这样的:
- 首先,用户打开页面的时候,服务器需要生成一个Token发给用户(该Token通过加密算法对数据进行加密,一般Token都包括随机字符串和时间戳的组合),并将其存到session中。
- 浏览器收到Token,为了防止CSRF,自然不能将其再放入cookie中,而是放在local storage中。
- 再次访问A网站时,将Token放在http请求头中,传到A网站。
- A网站将收到的Token与自己存储在session中的做比对,校验合法则进行接下来的操作。
三、JWT
说到这个JWT,就必须谈一下cookie和session。我们知道,HTTP是无状态的协议,而Cookie+Session作为用来跟踪浏览器用户身份的一种非常经典会话方式。这里不做过多解释,如果不清楚,推荐轩辕志远在知乎上的回答。
对于这种模式,其第一个问题在于,扩展性不好。单机当然没有问题,如果是服务器集群,或者是跨域的服务导向架构,就要求 session 数据共享,每台服务器都能够读取 session。
举例来说,A 网站和 B 网站是同一家公司的关联服务。现在要求,用户只要在其中一个网站登录,再访问另一个网站就会自动登录,请问怎么实现?
一种解决方案是 session 数据持久化,写入数据库或别的持久层。各种服务收到请求后,都向持久层请求数据。这种方案的优点是架构清晰,缺点是工程量比较大。另外,持久层万一挂了,就会单点失败。
另一种方案是服务器索性不保存 session 数据了,所有数据都保存在客户端,每次请求都发回服务器。JWT 就是这种方案的一个代表。
再来看第二个问题,也就是上面讲的CSRF攻击问题,我们知道,防止CSRF攻击的一种方法,就是利用Token,然后我们再来看看JWT,JWT的全称是JSON Web Token。很显然了,这就是一种Token而已。
来看一下JWT的实现过程吧:
-
用户提交用户名密码给服务端,如果登录成功,使用JWT创建一个Token,返回给用户。该Token是一个三段式的形式,如下图:
第一段,header,包含签名算法"alg"+token类型"typ"。将右边对应的json对象用base64URL算法转成字符串(左边红色部分)。
第二段,payload,七个官方字段(略)+自定义私有字段。将右边对应的json对象用base64URL算法转成字符串(左边紫色部分)。
第三段,signature,将前两部分的字符串拼接起来,然后对前两部分密文+自己的密钥使用header中定义的"alg"方法加密,加密后的密文再使用base64URL算法。image
-
以后用户再来访问时携带token,后端需要对token进行校验
校验方式:
第一步,对第一段进行base64URL解密,获取签名算法信息。
第二步,对第二段进行base64URL解密,获取payload信息,查看"exp"字段看是否过期。
第三步,拼接第1、2段,利用服务端自己保存的密钥和第一部获得的签名算法对前两段加密,与第三段对比,相等则验证成功。
四、总结
回顾二与三中的内容,你可能会有疑问,前文在CSRF Token中所说的最后一步,不是依旧会有Token和session中存储值的对比这一步么?那session中不是存东西了?这就要说到JWT的高明之处了,观察JWT设计的整个流程,服务器端并不需要将接收到的Token与session进行对比,只利用Token自己加上前面所说的巧妙地方法,就可以校验真伪了。
也就是说,CSRF Token中所说的解决方案,可以理解为一种概念指导性的东西,对于Token,你当然可以那么简单的实现,但是JWT提供了一种更好的不需要session存值就可以验证Token正确性的方法。
某种意义上JWT只不过是定义了一种更具体的token实现方式,而利用token的方案可以用来解决CSRF攻击问题。然而JWT不只是这样而已,它在token中加了用户信息在里面,解决CSRF同时,又解决cookie+session模式的扩展性问题。何乐而不为呢?
网友评论