发展史
- 很久以前,web基本上就是文档的浏览而已,既然是浏览,作为服务器,不需要记录谁在某一段时间里都浏览了什么文档,每次请求都是一个新的HTTP协议,就是请求加响应,尤其是我不用记住是谁刚刚发送了HTTP请求,每个请求对我来说都是全新的。
- 但是随着交互式web应用的兴起,像在线购物网站,需要登陆的网站等等,马上就面临一个问题,那就是要管理会话,必须记住哪些人登录系统,哪些人往自己的购物车中放商品,也就是说我必须要把每个人都区分开,因为HTTP请求是无状态的,所以相处的办法就是给大家发一个会话标识(session id ),说白了就是一个随机字符串,每个人收到的都不一样,每次大家向我发起请求的时候,把这个字符串一起发送过来,这样我就能区分开谁是谁了。
- 这样的话,服务器的压力就增加了,每个人只需要保存自己的session id,而服务器要保存所有人的session id。
- 于是就有人在思考,为什么服务器一定要保存session呢?直接让每个客户端去保存就可以了。可是如果不保存这些session id,服务器怎么验证客户端发给服务器的session id的确是它生成的呢?所以关键点就在于验证。
比如说小a已经登陆了系统,我给他发一个令牌(token),里边包含了小a的user id,下一次小a再次通过HTTP请求访问服务器的时候,把这个token通过http header带过来就可以了。
不过这和session没有本质区别,任何人都可以伪造,所以要想个办法让别人伪造不了
对数组做一个签名!比如说服务器使用HMAC-SHA256算法,加上一个只有服务器才知道的密钥,对数据做一个签名,把这个签名和数据一起作为token,由于密钥别人不知道,就无法伪造token了。这个token服务器不保存,当用户小a把这个token发送给服务器的时候,服务器再用同样的HMAC-SHA256算法和同样的密钥,对数据再做计算一次签名,和token中的签名做个比较,如果相同,就知道小a已经登陆过了,并且可以直接取到小a的user id,如果不相同,数据部分肯定被人纂改过,就可以发送给小a:对不起,没有认证。
token中的数据是明文保存的(虽然服务器会有base64做一下编码,但是那不是加密),还是可以被别人看见的,所以服务器不能在其中保存像密码那样敏感的信息。
但是如果一个人的token被偷走了,服务器就会认为小偷是合法用户,就跟一个人的session id被别人偷走一样。
这样一来,服务器就不用保存session id了,它只是生成token,然后用它的CPU计算时间获取了session存储空间。
cookie
cookie是一个非常具体的东西,值得就是浏览器里面能永久存储的一种数据,仅仅是浏览器实现的一种数据存储功能。
cookie由服务器生成,发送给浏览器,浏览器把cookie以kv形式保存到某个目录下的文本文件中,下一次请求同一网站时会把该cookie发送给服务器。所以cookie是存储在客户端上的,所以浏览器加入了一些限制确保cookie不会被恶意使用,同时不会占据太多磁盘空间,所以每个域的cookie数量都是有限的。
session
session从字面上将,就是会话。这个就类似于和一个人交谈我们要怎么知道当前交谈的是张三而不是李四呢?
sission就是类似的道理,服务器要知道当前发送请求给自己的是谁。为了做这种区分,服务器就要给每个客户端分配不同的“身份标识”,然后客户端每次向服务器发送请求的时候,都带上这个“身份标识”,服务器就知道这个请求来自于谁了至于客户端怎么保存这个“身份标识”,可以由很多种方式,对于浏览器客户端,大家都默认采用cookie的方式。
服务器使用session把用户的信息临时保存在了服务器上,用户离开网站后session会被销毁。这种用户信息存储方式相对cookie来说更安全,可是session有一个缺陷:如果web服务器做了均衡负载,那么下一个操作请求到了另一台服务器的时候session会丢失。
token
- token的引入: token是在客户端频繁向服务端请求数据,服务端频繁的去数据库查询用户名和密码并进行比对,判断用户名和密码正确与否,并作出响应提示,在这样的背景下,token便产生了。
- token的定义:token是服务端生成的遗传字符串,以作客户端进行请求的一个令牌,当第一次登录后,服务器生成一个token便将此token返回给客户端,以后客户端只需带上这个token前来请求数据即可,无需再次带上用户名和密码。最贱的token组成
uid(用户唯一的身份标识)、time(当前的时间戳)、sign(签名,由token的前几位+哈希算法压缩成一定长的十六进制字符串,可以放置恶意第三方拼接token请求服务器)
- 使用token的目的:token的目的是为了减轻服务器的压力,减少插叙数据库,使服务器更加健壮。
token的特性:
- 无状态、可扩展
- 支持移动设备
- 跨程序调用
- 安全
大部分的API和web应用都是用tokens,例如facebook、twitter、google等
使用token的身份验证是无状态的,我们不用讲信息存在服务器或者是session中。
传统身份验证
HTTP是一种没有状态的协议,也就是它并不知道是谁访问应用。这里我们把用户看成是客户端,客户端使用用户名还有密码通过了身份验证,不过下回这个客户端再发送请求的时候,还得再验证一下。
解决的办法就是,当用户请求登录的时候,如果没有问题,我们在服务端生成一条记录,这个记录里可以说明一下登陆的用户是谁,然后把这条记录的id号发送给客户端,客户端收到以后把这个id号存储在cookie里,下次这个用户再向服务端发送请求的时候,可以带着这个cookie,这样服务端会验证这个cookie里的信息,看看能不能在服务端找到对应的记录,如果可以说明用户已经通过了身份验证,就把用户请求的数据返回给客户端。
上面说的就是session,我们需要在服务端存储为登录的用户生成的session,这些session可能会存储在内存,磁盘,或者数据库里。我们可能需要在服务端定期的清理过期的session
基于token的身份验证
使用token的身份验证方法,在服务端不需要存储用户的登录行为。大概的流程是这样的:
- 客户端使用用户名跟密码请求登录
- 服务端收到请求,去验证用户名与密码
- 验证成功后,服务端会签发一个token,再把这个token发送给客户端
- 客户端收到token后把它存储起来,比如放在cookie或者localstorage里面
- 客户端每次向服务器请求资源的时候需要带着服务端签发的token
- 服务端收到请求,然后去验证客户端请求里面带着的token,如果验证成功,就向客户端返回请求的数据
app登录的时候 发送加密的用户名和密码到服务器,服务器验证用户名和密码,如果成功,以某种方式比如随机生成32位的字符串作为token,存储到服务器中,并返回token到app,以后app请求时,凡是需要验证的地方都要带上该token,然后服务器端验证token,成功返回所需要的结果,失败返回错误信息,重新登陆。其中服务器上token设置一个有效期,每次app请求的时候都要验证token和有效期。
每一次请求都需要token,token应该在HTTP头部发送从而保证了HTTP的请求无状态。我们同样通过设置服务器属性Acess-Control-Allow-Origin:*
让服务器能接收到所有域的请求。
问题:1、服务器上的token存储到数据库中,每次查询会不会很费时。如果不存储到数据库,应该存储到哪里呢?2、客户端得到的token肯定要加密存储的,发送token的时候再解密,存储到数据库还是配置文件呢?
token是一个易失数据,丢了无非让用户重新登录一下,如果觉得普通的数据库表撑不住了,可以放到MSSQL/MYSQL的内存表里,可以放到memcache里,可以放到redis里,甚至可以放到openresty的变量字典里。
token是个凭条,token丢失的呆久是可以忍受的,只需要重新认证一下就可以了
基于这个出发点,如果我们认为使用数据库来保存token查询事件太长,会成为我们系统的瓶颈或者隐患,可以放在内存当中。比如memcache、redis、kv方式很适合对token方式的查询的需求。
这个不会太占内存,比如token的字符串时32位字符串,要是用户量在百万级或千万级,也没有多少内存。要是数据量真的大到单机扛不住,或者存在数据丢失风险,只要这个token生成时足够均匀的,高低位切一下分到不同的机器上就可以了,内存绝对不是一个问题。
客户端方面除非我们有一个非常安全的办法,比如操作系统提供的隐私数据存储,那token肯定会存在泄露的文通。比如我拿你的收集,把你的token拷贝出来,在过期之前都可以以你的身份在别的地方登录。
解决这个问题的一个简单办法:
- 在存储的时候把token进行对称加密,用时解开
- 将请求URL、时间戳、token三者进行合并盐签名,服务端校验有效性
这两种办法的出发点都是,窃取你存储的数据较为容易,而反汇编你的程序hack你的加密解密和签名算法是比较难的。方法1它拿到的存储密文解不开,方法2它不知道你的签名算法和盐,两者可以结合使用。
在网络上token铭文传输的话会非常危险,所以建议一定要使用HTTPs,并且把token放在post body
里
token的优势
- 无状态、可扩展
在客户端存储的tokens是无状态的,并且能够被扩展。基于这种无状态和不存储session信息,负载均衡器能够将用户信息从一个服务器传到其他服务器上。
如果我们将已验证的用户的信息保存在session中,则每次请求都需要用户向已验证的服务器发送验证信息。用户量大时可能会造成一些拥堵。使用token之后,这些问题都解决了,因为tokens自己hold住了用户的验证信息
- 安全性
请求中发送token而不再是发送cookie能够防止CSRF。即使在客户端使用cokkie存储token、cookie也仅仅是一个存储机制而不是用于认证。不讲信息存储在session中,让我们少了对session的操作。
token是有时效的,一段时间之后用户需要重新验证。我们也不一定需要等到token自动失效,token有撤回的操作,通过token revocataion可以使一个特定的token或者是一组有相同认证的token无效
可扩展性
tokens能够创建与其他程序共享权限的程序。例如,能将一个随便的社交账号和自己的大号联系起来。当通过服务登录twitter时,我们可以将这些buffer附到twitter的数据流上。使用token时,可以提供可选的权限给第三方应用程序。当用户想让另一个应用程序访问它们的数据,我们可以通过建立自己的API,得出特殊权限的tokens多平台跨域
只要用户有一个听过了验证的token,数据和资源能够在任何域上被请求到。基于标准
创建token的时候,我们可以设定一些选项。
cookie与session的区别
- cookie数据存储在客户端上,session数据发在服务器上
- cookie不是很安全,别人可以分析存放在本地的cookie并进行cookie七篇,考虑到安全应当使用session
- session会在一定时间内保存在服务器上。当访问增多,会比较占用服务器的性能,考虑到减轻服务器性能方面,应当使用cookie
- 单个cookie保存的数据不能超过4k,很多浏览器都限制一个站点最多保存20个cookie
- 所以:将登录信息等重要信息存放位session,其他信息如果需要保留,可以放在cookie中
session和token的区别
session和token并不矛盾,作为身份认证,token安全性比session好,因为每个请求都有签名还能防止监听以及重防攻击,而session就必须靠链路层来保障通讯安全了。如果我们需要实现有状态的会话,仍然可以增加session来在服务端保存一些状态
app通常用restful api跟server打交道。rest是stateless的,也就是app不需要像brower那样用cookie来保存session,因此用session token标识自己就够了,session/state由阿皮server的逻辑处理。如果后端不是stateless的rest api,那么可能需要在app里保存session,可以在app里嵌入webkit,用一个隐藏的browser来管理cookie session
session是一种HTTP存储机制,目的是为了无状态的HTTP提供的持久机制。所谓session认证只是简单的把user信息存储到session里,因为SID的不可预测性,暂且认为是安全的。这是一种认证手段。而token,如果值得是OAuth token或类似的机制的话,提供的是认证和授权,认证是针对用户,授权是针对app。其目的是让某app有权利访问某用户啊的信息。这里的token是唯一的,不可转移到其他app上,也不可以转到其他用户上。转过来说session。session只提供一种简单的认证,即有此SID,即认为有此user的全部权力。是需要严格保密的,这个数据应该只保存在站方,不应该共享给其他网站或者第三方app。所以简单来说,如果你的用户数据可能需要和第三方共享,或者第三方调用API接口,用token。如果永远只自己的网站,自己的app,用什么就无所谓了。
注意:对于session;来说,除非程序通知服务器删除一个session,否则服务器会一致保留,程序一般都是在用户做log off的时候发个指令去删除session。
然而浏览器从来不会主动在关闭之前通知服务器它要挂壁,因此服务器根本不会有机会知道浏览器已经关闭,之所以会有这种错觉,是大部分session机制都使用会话cookie来保存session id,而关闭浏览器后这个session id就消失了,再次链接服务器时也无法找到原来的session。如果服务器设置的cookie被保存在硬盘上,或者使用某种手段改写浏览器发出的HTTP请求头,把原来的session id发送给服务器,则再次打开浏览器仍然能够打开原来的session
恰恰时由于关闭浏览器并不会导致session被删除,迫使服务器位session设置了一个失效时间,当距离客户端上一次使用session的时间超过这个失效时间时,服务器就可以以为客户端已经停止了活动,才会把session删除以节省存储空间。