3种web会话管理的方式

http是无状态的,一次请求结束,链接断开,下次服务器再收到请求,它就不知道这个请求是哪一个用户发过来的。固然它知道是哪一个客户端地址发过来的,可是对于咱们的应用来讲,咱们是靠用户来管理,而不是靠客户端。因此对咱们的应用而言,它是须要有状态管理的,以便服务端可以准确的知道http请求是哪一个用户发起的,从而判断他是否有权限继续这个请求。这个过程就是常说的会话管理。它也能够简单理解为一个用户从登陆到退出应用的一段期间。本文总结了3种常见的实现web应用会话管理的方式:php

1)基于server端session的管理方式html

2)cookie-base的管理方式前端

3)token-base的管理方式java

这些内容能够帮助加深对web中用户登陆机制的理解,对实际项目开发也有参考价值,欢迎阅读与指正。web

1. 基于server端session的管理

在早期web应用中,一般使用服务端session来管理用户的会话。快速了解服务端session:ajax

1) 服务端session是用户第一次访问应用时,服务器就会建立的对象,表明用户的一次会话过程,能够用来存放数据。服务器为每个session都分配一个惟一的sessionid,以保证每一个用户都有一个不一样的session对象。redis

2)服务器在建立完session后,会把sessionid经过cookie返回给用户所在的浏览器,这样当用户第二次及之后向服务器发送请求的时候,就会经过cookie把sessionid传回给服务器,以便服务器可以根据sessionid找到与该用户对应的session对象。算法

3)session一般有失效时间的设定,好比2个小时。当失效时间到,服务器会销毁以前的session,并建立新的session返回给用户。可是只要用户在失效时间内,有发送新的请求给服务器,一般服务器都会把他对应的session的失效时间根据当前的请求时间再延长2个小时。express

4)session在一开始并不具有会话管理的做用。它只有在用户登陆认证成功以后,而且往sesssion对象里面放入了用户登陆成功的凭证,才能用来管理会话。管理会话的逻辑也很简单,只要拿到用户的session对象,看它里面有没有登陆成功的凭证,就能判断这个用户是否已经登陆。当用户主动退出的时候,会把它的session对象里的登陆凭证清掉。因此在用户登陆前或退出后或者session对象失效时,确定都是拿不到须要的登陆凭证的。json

以上过程可简单使用流程图描述以下:
image

主流的web开发平台(java,.net,php)都原生支持这种会话管理的方式,并且开发起来很简单,相信大部分后端开发人员在入门的时候都了解并使用过它。它还有一个比较大的优势就是安全性好,由于在浏览器端与服务器端保持会话状态的媒介始终只是一个sessionid串,只要这个串够随机,攻击者就不能轻易冒充他人的sessionid进行操做;除非经过CSRF或http劫持的方式,才有可能冒充别人进行操做;即便冒充成功,也必须被冒充的用户session里面包含有效的登陆凭证才行。可是在真正决定用它管理会话以前,也得根据本身的应用状况考虑如下几个问题:

1)这种方式将会话信息存储在web服务器里面,因此在用户同时在线量比较多时,这些会话信息会占据比较多的内存;

2)当应用采用集群部署的时候,会遇到多台web服务器之间如何作session共享的问题。由于session是由单个服务器建立的,可是处理用户请求的服务器不必定是那个建立session的服务器,这样他就拿不到以前已经放入到session中的登陆凭证之类的信息了;

3)多个应用要共享session时,除了以上问题,还会遇到跨域问题,由于不一样的应用可能部署的主机不同,须要在各个应用作好cookie跨域的处理。

针对问题1和问题2,我见过的解决方案是采用redis这种中间服务器来管理session的增删改查,一来减轻web服务器的负担,二来解决不一样web服务器共享session的问题。针对问题3,因为服务端的session依赖cookie来传递sessionid,因此在实际项目中,只要解决各个项目里面如何实现sessionid的cookie跨域访问便可,这个是能够实现的,就是比较麻烦,先后端有可能都要作处理。

若是不考虑以上三个问题,这种管理方式比较值得使用,尤为是一些小型的web应用。可是一旦应用未来有扩展的必要,那就得谨慎对待前面的三个问题。若是真要在项目中使用这种方式,推荐结合单点登陆框架如CAS一块儿用,这样会使应用的扩展性更强。

2. cookie-based的管理方式

因为前一种方式会增长服务器的负担和架构的复杂性,因此后来就有人想出直接把用户的登陆凭证直接存到客户端的方案,当用户登陆成功以后,把登陆凭证写到cookie里面,并给cookie设置有效期,后续请求直接验证存有登陆凭证的cookie是否存在以及凭证是否有效,便可判断用户的登陆状态。使用它来实现会话管理的总体流程以下:

1)用户发起登陆请求,服务端根据传入的用户密码之类的身份信息,验证用户是否知足登陆条件,若是知足,就根据用户信息建立一个登陆凭证,这个登陆凭证简单来讲就是一个对象,最简单的形式能够只包含用户id,凭证建立时间和过时时间三个值。

2)服务端把上一步建立好的登陆凭证,先对它作数字签名,而后再用对称加密算法作加密处理,将签名、加密后的字串,写入cookie。cookie的名字必须固定(如ticket),由于后面再获取的时候,还得根据这个名字来获取cookie值。这一步添加数字签名的目的是防止登陆凭证里的信息被篡改,由于一旦信息被篡改,那么下一步作签名验证的时候确定会失败。作加密的目的,是防止cookie被别人截取的时候,没法轻易读到其中的用户信息。

3)用户登陆后发起后续请求,服务端根据上一步存登陆凭证的cookie名字,获取到相关的cookie值。而后先作解密处理,再作数字签名的认证,若是这两步都失败,说明这个登陆凭证非法;若是这两步成功,接着就能够拿到原始存入的登陆凭证了。而后用这个凭证的过时时间和当前时间作对比,判断凭证是否过时,若是过时,就须要用户再从新登陆;若是未过时,则容许请求继续。

image

这种方式最大的优势就是实现了服务端的无状态化,完全移除了服务端对会话的管理的逻辑,服务端只须要负责建立和验证登陆cookie便可,无需保持用户的状态信息。对于第一种方式的第二个问题,用户会话信息共享的问题,它也能很好解决:由于若是只是同一个应用作集群部署,因为验证登陆凭证的代码都是同样的,因此无论是哪一个服务器处理用户请求,总能拿到cookie中的登陆凭证来进行验证;若是是不一样的应用,只要每一个应用都包含相同的登陆逻辑,那么他们也是能轻易实现会话共享的,不过这种状况下,登陆逻辑里面数字签名以及加密解密要用到的密钥文件或者密钥串,须要在不一样的应用里面共享,总而言之,就是须要算法彻底保持一致。

这种方式因为把登陆凭证直接存放客户端,而且须要cookie传来传去,因此它的缺点也比较明显:

1)cookie有大小限制,存储不了太多数据,因此要是登陆凭证存的消息过多,致使加密签名后的串太长,就会引起别的问题,好比其它业务场景须要cookie的时候,就有可能没那么多空间可用了;因此用的时候得谨慎,得观察实际的登陆cookie的大小;好比太长,就要考虑是非是数字签名的算法太严格,致使签名后的串太长,那就适当调整签名逻辑;好比若是一开始用4096位的RSA算法作数字签名,能够考虑换成102四、2048位;

2)每次传送cookie,增长了请求的数量,对访问性能也有影响;

3)也有跨域问题,毕竟仍是要用cookie。

相比起第一种方式,cookie-based方案明显仍是要好一些,目前好多web开发平台或框架都默认使用这种方式来作会话管理,好比php里面yii框架,这是咱们团队后端目前用的,它用的就是这个方案,以上提到的那些登陆逻辑,框架也都已经封装好了,实际用起来也很简单;asp.net里面forms身份认证,也是这个思路,这里有一篇好文章把它的实现细节都说的很清楚:

http://www.cnblogs.com/fish-li/archive/2012/04/15/2450571.html

前面两种会话管理方式由于都用到cookie,不适合用在native app里面:native app很差管理cookie,毕竟它不是浏览器。这两种方案都不适合用来作纯api服务的登陆认证。要实现api服务的登陆认证,就要考虑下面要介绍的第三种会话管理方式。

3. token-based的管理方式

这种方式从流程和实现上来讲,跟cookie-based的方式没有太多区别,只不过cookie-based里面写到cookie里面的ticket在这种方式下称为token,这个token在返回给客户端以后,后续请求都必须经过url参数或者是http header的形式,主动带上token,这样服务端接收到请求以后就能直接从http header或者url里面取到token进行验证:

image

这种方式不经过cookie进行token的传递,而是每次请求的时候,主动把token加到http header里面或者url后面,因此即便在native app里面也能使用它来调用咱们经过web发布的api接口。app里面还要作两件事情:

1)有效存储token,得保证每次调接口的时候都能从同一个位置拿到同一个token;

2)每次调接口的的代码里都得把token加到header或者接口地址里面。

看起来麻烦,其实也不麻烦,这两件事情,对于app来讲,很容易作到,只要对接口调用的模块稍加封装便可。

这种方式一样适用于网页应用,token能够存于localStorage或者sessionStorage里面,而后每发ajax请求的时候,都把token拿出来放到ajax请求的header里便可。不过若是是非接口的请求,好比直接经过点击连接请求一个页面这种,是没法自动带上token的。因此这种方式也仅限于走纯接口的web应用。

这种方式用在web应用里也有跨域的问题,好比应用若是部署在a.com,api服务部署在b.com,从a.com里面发出ajax请求到b.com,默认状况下是会报跨域错误的,这种问题能够用CORS(跨域资源共享)的方式来快速解决,相关细节可去阅读前面给出的CORS文章详细了解。

这种方式跟cookie-based的方式一样都还有的一个问题就是ticket或者token刷新的问题。有的产品里面,你确定不但愿用户登陆后,操做了半个小时,结果ticket或者token到了过时时间,而后用户又得去从新登陆的状况出现。这个时候就得考虑ticket或token的自动刷新的问题,简单来讲,能够在验证ticket或token有效以后,自动把ticket或token的失效时间延长,而后把它再返回给客户端;客户端若是检测到服务器有返回新的ticket或token,就替换原来的ticket或token。

4. 安全问题

在web应用里面,会话管理的安全性始终是最重要的安全问题,这个对用户的影响极大。

首先从会话管理凭证来讲,第一种方式的会话凭证仅仅是一个session id,因此只要这个session id足够随机,而不是一个自增的数字id值,那么其它人就不可能轻易地冒充别人的session id进行操做;第二种方式的凭证(ticket)以及第三种方式的凭证(token)都是一个在服务端作了数字签名,和加密处理的串,因此只要密钥不泄露,别人也没法轻易地拿到这个串中的有效信息并对它进行篡改。总之,这三种会话管理方式的凭证自己是比较安全的。

而后从客户端和服务端的http过程来讲,当别人截获到客户端请求中的会话凭证,就能拿这个凭证冒充原用户,作一些非法操做,而服务器也认不出来。这种安全问题,能够简单采用https来解决,虽然可能还有http劫持这种更高程度的威胁存在,可是咱们从代码能作的防范,确实也就是这个层次了。

最后的安全问题就是CSRF(跨站请求伪造)。这个跟代码有很大关系,本质上它就是代码的漏洞,只不过通常状况下这些漏洞,做为开发人员都不容易发现,只有那些一门心思想搞些事情的人才会专门去找这些漏洞,因此这种问题的防范更多地仍是依赖于开发人员对这种攻击方式的了解,包括常见的攻击形式和应对方法。无论凭证信息自己多么安全,别人利用CSRF,就能拿到别人的凭证,而后用它冒充别人进行非法操做,因此有时间还真得多去了解下它的相关资料才行。举例来讲,假如咱们把凭证直接放到url后面进行传递,就有可能成为一个CSRF的漏洞:当恶意用户在咱们的应用内上传了1张引用了他本身网站的图片,当正常的用户登陆以后访问的页面里面包含这个图片的时候,因为这个图片加载的时候会向恶意网站发送get请求;当恶意网站收到请求的时候,就会从这个请求的Reffer header里面看到包含这个图片的页面地址,而这个地址正好包含了正经常使用户的会话凭证;因而恶意用户就拿到了正经常使用户的凭证;只要这个凭证还没失效,他就能用它冒充用户进行非法操做。

5. 总结

前面这三种方式,各自有各自的优势及使用场景,我以为没有哪一个是最好的,作项目的时候,根据项目未来的扩展状况和架构状况,才能决定用哪一个是最合适的。本文的目的也就是想介绍这几种方式的原理,以便掌握web应用中登陆验证的关键因素。

做为一个前端开发人员,本文虽然介绍了3种会话管理的方式,可是与前端关系最紧密的仍是第三种方式,毕竟如今前端开发SPA应用以及hybrid应用已经很是流行了,因此掌握好这个方式的认证过程和使用方式,对前端来讲,显然是颇有帮助的。好在这个方式的技术其实早就有不少实现了,并且还有现成的标准可用,这个标准就是JWT(json-web-token)。

JWT自己并无作任何技术实现,它只是定义了token-based的管理方式该如何实现,它规定了token的应该包含的标准内容以及token的生成过程和方法。目前实现了这个标准的技术已经有很是多:

image

更多可参阅:https://jwt.io/#libraries-io

为了对第三种会话管理方式的实现有个更全面的认识,我选择用express和上面众多JWT实现中的jsonwebtoken来研究,相关内容我会在下一篇博客详细介绍。本文内容到此结束,谢谢阅读,欢迎关注下一篇博客的内容。