链接:https://zhuanlan.zhihu.com/p/88185448
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
众所周知,http协议是无状态的协议,服务器不能清楚判定发送方的身份,但在类似网络购物等交互式应用场景中,服务器又需要能够真实的区分发送请求的用户身份,否则把张三购买的物品发送给了李四,对于张三来说,肯定是零容忍。
那这一切又是如何做到的呢?这就不得不提一下cookie、session和token了。
Cookie的定义
cookie是指小段的文本信息(key-value格式),是浏览器实现的一种数据存储功能。cookie由服务器生成,发送给浏览器,浏览器把cookie以键值对形式保存到客户端某个目录下的文本文件内,下一次请求同一网站时会把该cookie发送给服务器。通过对cookie中信息的解析,服务器可以识别请求发送者的身份。由于cookie是存在客户端上的,所以浏览器加入了一些限制,确保cookie不会被恶意使用,同时为了不占据太多磁盘空间,每个域的cookie数量是有限的。
打个比方,每个中国公民成年后会去派出所办个身份证,上面会有你的姓名、性别、民族、住址和身份证号码等信息(同样是以KV形式展示),当你办理任何对公事务时,诸如高考等,都需要出示身份证来表明你的身份。
PS:cookie是不可以跨域名的,隐私安全机制禁止网站非法获取其他网站的cookie。一般来说,同一个一级域名下的两个二级域名也不能交互使用cookie。比如http://a.test.com和http://b.test.com,因为二者的域名不完全相同。如果想要http://test.com名下的二级域名都可以使用该cookie,需要设置cookie的domain参数为.http://test.com,这样http://a.test.com和http://b.test.com就能访问同一个cookie。
既然可以通过cookie的方式表明发送者的身份,为什么一般不采用这种方案呢?
主要是cookie有以下缺点:
1、 cookie的数量和长度都有限制
2、潜在的安全风险:cookie可能被截取篡改,如果cookie被拦截,就可能会获取到所有的信息
3、用户可能会配置禁用浏览器或者客户端设备接受cookie的能力,因此可能会限制了这一功能
4、有些状态不可能保存在客户端
Session的定义
session是一种将会话状态保存在服务器端的技术 ,可以比喻成是银行发放给客户的银行卡和银行为每个客户保留的账户档案的结合方式 。客户到银行柜台办理业务时,需要出示自身办理的银行卡,银行办理人员通过银行卡上的卡号查找到对应的客户信息,进而就可以进行相关业务处理了。
与银行卡类似,服务器要知道当前发请求给自己的是谁。为了做这种区分,服务器就要给每个客户端分配不同的“身份标识”,然后客户端每次向服务器发请求的时候,都带上这个“身份标识”,服务器就知道这个请求来自于谁了。
至于客户端怎么保存这个“身份标识”,可以有很多种方式。对于浏览器客户端,大家都默认采用 cookie 的方式。服务器使用session把用户的信息临时保存在了服务器上,用户离开网站后session会被销毁。
这种用户信息存储方式相对cookie来说更安全,可是session有一个缺陷:如果web服务器做了负载均衡,那么下一个操作请求到了另一台服务器的时候,由于另一台服务器没有对应的session,会直接返回认证错误,进而需要用户重新登录。
为了避免类似的问题,需要将session复制到所有的应用服务器,同时由于互联网的巨量用户必然会带来的海量session信息的存储需求,这对服务器存储能力是一个巨大的开销,而且严重限制了服务器的动态扩展能力(为了应对用户的巨量增长,互联网常用的做法是加服务器来保证服务能力的可用性),当然可以采用分布式缓存来对session信息进行集中存储,这又会带来新的问题,比如单点故障等。因为上述种种不便,互联网公司一般采用token的方式来进行验证。
Token的定义
token即“令牌”,是服务端生成的一串字符串,作为客户端进行请求的一个标识。当用户第一次登录后,服务器生成一个token并将此token返回给客户端,客户端收到token后,会把它存储起来(一般采用cookie或者localstorage的方式进行存储),以后客户端只需带上这个token前来请求数据即可,无需再次带上用户名和密码,服务器如果对token验证成功,就会返回客户端请求的数据。
token一般是由uid(用户唯一的身份标识)、time(当前时间的时间戳)等信息和sign(签名)构成。如下图所示:
服务器一般是通过什么方式来保证该token是否是自己签发,并且信息没有篡改过呢?
这个就需要从token的签名说起了,签名通常是把token除签名部分用一定的加密算法和秘钥(只有服务端知晓)进行加密生成,当客户端把token发送过来时,服务端用同样的加密算法和秘钥对token的除签名部分重新计算,如果和token中的签名一致,很明显,这个token是自己签发,并且信息没有篡改过。
整个过程如下图所示:
当然,如果对应加密的哈希算法和秘钥泄露,token同样可以被篡改。
因为token中的数据是明文存储的(虽然会用base64编码,但那不是加密),所以一般不能用来存储密码等敏感信息,且一般通过https协议来保证传输安全性。
从上面的token的认证机制来看,token自身就可以完成自身的校验,那token是否需要进行存储呢?
这个需要根据token的实际应用场景来看,比如登录场景,类似微信的一次性accesstoken等,都需要进行相关存储,因为类似登录场景,需要进行一系列精细化控制,诸如权限,token的有效期、自身注销、非法用户的拦截等操作进行精确控制。
那为什么需要对token进行有效期设置呢?
我们先从两个例子入手吧。
一个例子是登录密码,一般要求定期改变密码,以防止泄漏(参加工作的同学经常会遇到的一个问题是电脑登录密码一个季度或者半年就强制变更一次),所以密码是有有效期的。
另一个例子是安全证书。SSL 安全证书都有有效期,目的是为了解决吊销的问题,当网站的私钥丢失时,网站应该向证书颁发机构(CA)申请将他们的证书加入到证书吊销列表(CRL)里。这样当用户访问https站点时,浏览器会自动向CA请求吊销列表,如果用户访问的站点提供的证书在CRL里,浏览器就不信任这个证书,因为攻击者可能拥有同样的证书。所以如果证书永久有效,随着越来越多的私钥丢失,吊销列表也越来越大(因为只有加进去的,没有剔除出去的),这既给CA增加流量压力,也会增加浏览器的流量。而一旦有效期只有几年,那么CA就可以将那些已经过期了的证书从CRL里剔除,因为反正浏览器也不信任过期证书。所以无论是从安全的角度考虑,还是从吊销的角度考虑,token 都需要设有效期。
那么有效期设置多长合适呢?
只能说,从系统的安全需要和用户的体验需求出发,尽可能的寻求一个平衡。在保证用户体验的前提下,token应该尽可能的短,但也不能短得离谱。这样新问题产生了,如果用户在正常操作的过程中,token 可能会过期失效,要求用户重新登录……用户体验岂不是很糟糕?
为了解决在操作过程不能让用户感到 token 失效这个问题,有以下两种通用方案。
方案一是在服务器端保存 token 状态,用户每次操作都会自动刷新(推迟) token 的过期时间(session 就是采用这种策略来保持用户登录状态的)。然而仍然存在这样一个问题,在前后端分离、单页 App 这些情况下,每秒钟可能发起很多次请求,每次都去刷新过期时间会产生非常大的代价。如果 token 的过期时间被持久化到数据库或文件,代价就更大了。所以通常为了提升效率,减少消耗,会把 token 的过期时间保存在缓存或者内存中。
方案二是使用 Refresh Token(也就是所谓的长token),它可以避免频繁的读写操作。这种方案中,服务端不需要刷新 token 的过期时间,一旦 token 过期,就反馈给前端,前端使用Refresh Token申请一个全新 token 继续使用。这种方案中,服务端只需要在客户端请求更新token的时候对Refresh Token的有效性进行一次检查,大大减少了更新有效期的操作,也就避免了频繁读写。当然Refresh Token也是有有效期的,但是这个有效期就可以长一点了,比如,以天为单位的时间。
以上是关于token、cookie以及session的一点自我思考,如有不当之处,欢迎各位指正