总结Web开发中会话管理的三种方式:基于Server端Session的管理方式、基于Cookie的管理方式、基于Token的管理方式。
HTTP是无状态性的,一次请求结束,连接断开,下次服务器再收到请求,就不知道这个请求是哪个用户发过来的。为了让服务端准确定位HTTP请求是由哪个用户发起的,从而判断该用户是否有权限访问请求的服务,需要有用户状态管理,即常说的会话管理,其涉及周期是一个用户从登录到退出应用的一段时间。常见的web应用的会话管理有三种方式:
- 基于Server端Session的管理方式
- 基于Cookie的管理方式
- 基于Token的管理方式
一、基于Server端Session的管理方式
在早期的web应用中,通常使用服务端session来管理用户的会话,其流程图如下:
(1)服务端session是用户第一次访问(可以是未登录的访问)应用时,服务器就会创建的对象,代表用户的一次会话过程,可以用来存放数据。服务器为每一个session都分配一个唯一的sessionid,以保证每个用户都有一个不同的session对象。
(2)服务器在创建完session后,会把sessionid通过cookie返回给用户所在的浏览器,这样当用户第二次及以后向服务器发送请求的时候,就会通过cookie把sessionid传回给服务器,以便服务器能够根据sessionid找到与该用户对应的session对象。
(3)session通常有失效时间的设定,比如2个小时。当失效时间到,服务器会销毁之前的session,并创建新的session返回给用户。但是只要用户在失效时间内,有发送新的请求给服务器,通常服务器都会把他对应的session的失效时间根据当前的请求时间再延长2个小时。
(4)session在一开始并不具备会话管理的作用。它只有在用户登录认证成功之后,并且往sesssion对象里面放入了用户登录成功的凭证,才能用来管理会话。管理会话的逻辑也很简单:只要拿到用户的session对象,看它里面有没有登录成功的凭证,就能判断这个用户是否已经登录。当用户主动退出的时候,会把它的session对象里的登录凭证清掉。所以在用户登录前或退出后或者session对象失效时,肯定都是拿不到需要的登录凭证的。
优点:
(1)主流的web开发平台(java/.net/php)都原生支持这种会话管理模式,且开发简单,很多后端开发人员入门都使用过。
(2)安全性好,只要服务端创建的、cookie中保存的sessionid这个字串足够随机,攻击者就不能轻易冒充他人的sessionid进行操作,除非通过CSRF(跨站请求伪造,攻击者挟持了登录还未过期的浏览器,从而盗用了用户的身份,执行非用户本意的操作)或HTTP劫持的方式。即使冒充成功,也必须被冒充用户的session中包含有效的登录凭证才行。
缺点:
(1)session会话信息存储在服务器的内存中,当在线量比较多时消耗大量内存。
(2)同个应用集群部署时,不同服务器间session共享的问题。某个用户的session只存在于一台服务器的内存中,下次请求如果没有被转发到那台服务器,就拿不到自己session中登录凭证等的信息了。
(3)多个应用共享session时的跨域问题。多个应用可能部署的主机不一样,要在各个应用做好cookie跨域的处理。
二、基于Cookie的管理方式
上一种方式将登录凭证放在服务器的session中,会增加服务器的内存负担和服务端架构的复杂性。本方案将用户的登录凭证直接放在客户端的cookie中,后续请求会带着这个登录凭证去访问服务器,流程图如下:
(1)用户发起登录请求,服务端根据传入的用户密码之类的身份信息,验证用户是否满足登录条件,如果满足,就根据用户信息创建一个登录凭证,这个登录凭证简单来说就是一个对象,最简单的形式可以只包含用户id,凭证创建时间和过期时间三个值。
(2)服务端把上一步创建好的登录凭证,先对它做数字签名,然后再用对称加密算法做加密处理,将签名、加密后的字串,写入cookie。cookie的名字必须固定(如ticket),因为后面再获取的时候,还得根据这个名字来获取cookie值。这一步添加数字签名的目的是防止登录凭证里的信息被篡改,因为一旦信息被篡改,那么下一步做签名验证的时候肯定会失败。做加密的目的,是防止cookie被别人截取的时候,无法轻易读到其中的用户信息。
(3)用户登录后发起后续请求,服务端根据上一步存登录凭证的cookie名字,获取到相关的cookie值。然后先做解密处理,再做数字签名的认证,如果这两步都失败,说明这个登录凭证非法;如果这两步成功,接着就可以拿到原始存入的登录凭证了。然后用这个凭证的过期时间和当前时间做对比,判断凭证是否过期,如果过期,就需要用户再重新登录;如果未过期,则允许请求继续。
优点:
(1)实现无状态的服务端。服务端只需要对用户传来的cookie登录凭证进行验证即可,无需保存用户的状态信息,解决了服务端内存压力大的问题
(2)同一个应用集群部署的时候,因为验证登录凭证的代码在不同的服务器上都是一样的,因此不管是哪个服务器在处理用户请求,都能够对用户的登录凭证进行相同的验证逻辑。
(3)不同应用的集群,只要这些应用的登录逻辑相同,也能够实现会话共享。保证不同应用登录逻辑里的签名算法、加密算法、解密算法、密钥等一致即可。总而言之,需要算法保持完全一致。
缺点:
(1)cookie大小有限制,如果加密签名串太长导致登录凭证太大,就会占用大量的cookie空间,这样,当别的业务场景需要用cookie存储数据的时候,可能就不够用了。
(2)客户端的每次请求都会传送cookie,对访问性能有影响。
(3)依然存在和第一种基于session管理方式相同的跨域问题,因为毕竟还是要用到cookie。
(4)登录凭证过期时间刷新的问题。
三、基于Token的管理方式
前两种会话管理方案都有一个共同的缺陷:因为都用到cookie,所以只适合做浏览器应用,不适合做native app,都不适合来用作纯api服务(比如RESTful API)的登录认证。因此有第三种基于token的管理方式,流程图如下:
虽然从流程和实现上来说,这种方式和基于cookie的管理方式没有太多区别,但不通过cookie进行登录凭证的传递,而是每次请求的时候,主动把token(登录凭证)加到http header里面或者url后面,所以即使在native app中也能使用它来调用我们通过web发布的api的接口,可以用来做纯api服务的登录认证。
优点:
(1)第二种基于cookie的管理方式的全部优点。
(2)如上所述,同时适用于网页app和native app。在网页应用中,token可以存于localStorage或者sessionStorage中,每次发ajax请求的时候,都把token拿出来放到请求头里即可。不过如果是非接口的请求,比如通过点击链接请求一个页面这种,是无法自动带上token的,因此只适用于纯api接口的请求服务(比如RESTful API)。
缺点:
(1)在web应用里,即使是不使用cookie,使用ajax请求也会存在跨域问题。比如应用部署在a.com,api服务部署在b.com,从a.com里发ajax请求到b.com,默认情况下也会报跨域错误,这个问题可以用CORS(跨域资源共享)的方式解决。
(2)与第二种基于cookie的管理方式相同的登录凭证刷新的问题。可以在每次请求过来验证token有效之后,自动把token的失效时间延长,再把新的token返回给客户端,客户端监测到这个返回的新的token,就替换掉原来的token。