Cookie、Session和Token都是为了解决Web身份校验而产生的,这里对它们的概念做一个简单了解。
Web身份校验的发展
很久很久以前,Web基本上就是文档的浏览而已。既然是浏览,作为服务器,不需要记录谁在某一段时间里都浏览了什么文档,每次请求都是一个新的HTTP协议,就是请求加响应。并不需要记住是谁刚刚发了HTTP请求,每个请求对服务器来说都是全新的。
但是随着交互式Web应用的兴起,像在线购物网站,需要登陆的网站等等,马上就面临一个问题,那就是要管理会话,必须要记住是哪些用户登陆了系统,哪些用户往自己的购物车中放了商品。意思就是说,服务器必须把每个用户区分开,这就是一个不小的挑战,因为HTTP请求是无状态的,于是有人就想出了一个办法,这个办法就是给客户端发一个会话标识(sessionId,就是一个随机的字符串),每个客户端收到的都不一样,每次客户端向服务端发起HTTP请求的时候,就会把这个随机字符串(sessionId)一并捎过来,这样就能区分开是哪个用户发起的请求了。
这样,服务器就增加了压力,因为服务器需要保存所有用户的sessionId。如果有上千上万个sessionId,对服务器来说就是一个巨大的开销,严重地限制了服务器的扩展能力。比如说用两个机器组成了一个集群,小明通过机器A登陆了系统,那sessionId就会保存在机器A上,假设小明的下一次请求被转发到机器B怎么办?机器B可没有小F的sessionId啊。有时候会采用session sticky的小技巧,就是让小明的请求一直粘连在机器A上。但是这也不管用,要是机器A挂掉了,还得转到机器B上去。
为了解决A机器挂掉的问题,只好做sessionId的复制了,把sessionId在两个机器之间搬来搬去。
再后来,有个叫Memcached的小伙子支了招:把sessionId集中存储到一个地方,所有的机器都来访问这个地方的数据,这样就不用复制了。但是这样则增加了单点失败的可能性,要是那个负责session的机器挂了,所有的用户都要重新登陆一遍,估计得被人骂死。
也尝试把这个单点的机器也搞出集群,增加可靠性。但不管如何,这小小的session对服务器来说都是一个沉重的负担。
于是,就有人提出了摆脱session的想法。具体就是不再在服务端保存sessionId了,让客户端去保存一个服务端生成的token,每次请求的时候附加上这个token,服务端只要对这个token进行相应的校验就可以完成身份验证。比如说,小明已经登陆了系统,服务端给他返回了一个令牌(token),里面包含了小明的userId,下一次小明再通过HTTP请求访问服务端的时候,就把这个token通过HTTP的Header带过来就可以了。
因为服务端不再存sessionId了,也不会存token,那么就可能会造成有心怀不轨的人伪造token来做坏事。因此,服务端需要对token做一些防伪造的操作,具体是对数据做一个签名。比如说用HMAC-SHA256算法加上一个只有服务器知道的密钥,对数据做一个签名,然后把签名和数据一起作为token,由于密钥别人不知道,就无法伪造token了。
服务器并不会保存这个token,当小明再次将token发到服务器的时候,服务端会用同样的HMAC-SHA256算法和同样的密钥去对数据再计算一次签名,并和token中的签名做一个比较,如果相同的话,就可以判断出小明已经登陆过,并且可以直接取到小明的userId;如果不相同,则数据部分肯定被人篡改过,这时就能够做一些身份校验失败的相应处理。
token中的数据默认是明文保存的(虽然会用Base64URL算法做下编码,但这并不是加密),因此最好不要在其中保存像密码之类的敏感信息。当然了,如果一个用户的token被别人偷走了,服务器并没有办法去辨别,只会认为小偷就是合法的用户,这其实和一个用户的sessionId被别人偷走是一样的。
这样一来,服务器就不需要保存sessionId了,只需要生成token,然后校验token,相当于用CPU计算时间换回了存储空间。解决了sessionId这个负担,可以说是无事一身轻。机器集群现在可以轻松地做水平扩展,用户访问量增大的时候直接加机器就OK。
Cookie
Cookie是一个非常具体的东西,指的就是浏览器里面能永久存储的一种数据,仅仅是浏览器实现的一种数据存储功能。
Cookie是由服务器生成,发送给浏览器,浏览器把Cookie以Key-Value的形式保存到某个目录下的文本文件内,下一次请求同一个网站的时候会把该Cookie发送给服务器。由于Cookie是存在客户端上的,所以浏览器加入了一些限制确保Cookie不会被恶意使用,同时不会占据太多的磁盘空间,每个域的Cookie数量都是有限的。
Session
Session从字面上讲,就是会话。这个就类似于与一个人交谈,你怎么知道当前和你交谈的是张三而不是李四呢?对方肯定有某种特征(长相等)表明他就是张三。Session也是类似的道理,服务器要知道当前发送请求给自己的是谁。为了做这种区分,服务器就要给每个客户端分配不同的身份标识,然后客户端每次向服务器发送请求的时候,都会带上这个身份标识,服务器就会知道这个请求是来自于谁了。至于客户端怎么保存这个身份标识,可以有很多方式,对于浏览器客户端来说一般都默认采用Cookie的方式。
服务器使用Session把用户的信息临时保存在了服务器上,用户离开网站之后Session会被销毁。这种用户信息存储方式相对Cookie来说更加安全,可是Session有一种缺陷,如果Web服务器做了负载均衡,那么下一个操作请求到了另一台服务器的时候Session就会丢失。
Token
在Web领域基于Token的身份验证随处可见。在大多数使用Web API的互联网公司中,Token是多用户下处理认证的最佳方式。大部分你见到过的API和Web应用都是使用Token,例如Facebook、Twitter、Google+和Github等。
在介绍基于Token的身份验证的原理与优势之前,不妨先看看之前的认证都是怎么做的。我们都是知道HTTP协议是无状态的,这种无状态意味着程序需要验证每一次请求,从而辨别客户端的身份。在这之前,程序都是通过在服务端存储的登陆信息(Session)来辨别请求的。随着Web应用程序以及移动端的兴起,这种验证方式逐渐暴露出了问题,尤其是在可扩展性方面。
基于服务器验证方式暴露的一些问题
1.Session:每次认证用户发起请求时,服务器需要去创建一个记录来存储信息。当越来越多的用户发请求时,内存的开销也会不断增加。
2.可扩展性:在服务端的内存中使用Session存储登陆信息,伴随而来的是可扩展性问题。
3.CORS(跨域资源共享):当我们需要让数据跨多台移动设备上使用的时候,跨域资源的共享就会是一个让人头疼的问题。在使用Ajax抓取另一个域的资源,就可能会出现禁止请求的情况。
4.CSRF(跨域请求伪造):用户在访问银行网站时,这种验证方式很容易受到跨站请求伪造的攻击,并且能够被利用其访问其他的网站。
在这些问题中,可扩展性是最突出的,因此我们有必要去寻求一种更加行之有效的方法。
基于Token的验证方式
基于Token的身份验证是无状态的,不将用户信息存在服务器或Session中,这种概念就解决了在服务端存储信息时的很多问题。No Session意味着程序可以根据需要去增减机器,而不用去担心用户是否登陆的问题。基于Token的身份验证的过程如下:
1.用户通过用户名和密码发送请求。
2.程序验证登陆信息,并返回一个签名的Token给客户端。
3.客户端存储Token。
4.客户端再次发起HTTP请求,将Token附加在请求中,服务端验证Token并返回数据。
Token的优势
1.无状态、可扩展。在客户端存储的Token是无状态的,并且能够被扩展。基于这种无状态和不存储Session信息,负载均衡器就能够将用户信息从一个服务传到其他服务器上。如果我们将已验证的用户的信息保存在Session中,则每次请求都需要用户向已验证的服务器发送验证信息(称为Session亲和性),在用户量大的时候,可能会造成一些拥堵(集群中的某一台服务器压力过大)。使用Token则不会有这些问题,因为Token不需要依赖特定的服务器。
2.支持移动设备。移动设备只需要有存储Token的能力,就能够使用这种身份验证的方式。
3.跨程序调用。只要用户有了一个验证通过的Token,数据和资源就能够在任何有相同校验规则的服务器(域)上请求到。
4.安全性较高。请求中发送Token而不再是发送Cookie,这样就能够防止CSRF(跨站请求伪造)。即使在客户端使用Cookie存储Token,Cookie也仅仅是一个存储机制而不是用于认证。不将信息存储在Session中,让我们少了对Session的操作。Token是有时效的,一段时间之后用户就需要重新验证。同时,Token还能有撤回的操作,可以通过Token Revocatation使一个特定的Token或一组Token失效。
"人生就像骑单车,要想保持平衡就得始终往前走,于是昨天越来越多,明天越来越少。"