计算机中的会话(Session)
前言
首先明确,这里主要论述的Session并不是指具体的各个语言的实现,而是单指Session(会话)的技术。
定义
标准HTTP协议请求本身是无状态的,也就是说,用户A和B同时向服务器发送了一个请求,服务器是无法判断这两个请求分别是来自于A还是B还是C。
为了实现有状态的HTTP请求,即在业务上服务端需要知道每一个请求分别来自于谁,就需要在HTTP的基础上,在应用业务上实现这么一个功能来维持用户的状态,从而将登录、和后续用户操作等等多个独立的HTTP请求联系起来,这就是Session。
换个说法,将客户端和服务端之间的多个独立HTTP请求起来关联,并识别请求的用户身份,能够实现这样一种功能的,就是Session(会话)
实现
那么,Session的实现方案有哪些?Token、JWT、PHP Session和Tomcat Session等等都是什么鬼?
一个个来说这些东西,但是先说一下Cookie。
Cookie
cookie是由浏览器实现的一个最基本的功能,诞生之初就是为了分辨用户身份而存储在客户端内存或硬盘上的数据块,大小通常为4kb。
优点:
1. Cookie历史较久,功能基本比较完善,具有生存周期Expires、有效访问路径Path、域名等等属性完善其安全性和有效性。
2. 可以由服务端在请求的Response中带上Set-Cookie属性对客户端的Cookie进行设置而不需要前端额外实现。
3. 浏览器在请求时会自动带上该网站对应的Cookie发送给服务端。
缺点:
1. 通常大小为4kb,在现代化网站中,可能不够用。
2. 浏览器会在每个HTTP请求中,都自动带上Cookie进行请求,若Cookie较多,占用服务器带宽。
3. 容易被污染,从而导致隐私泄漏,商家可依此完善用户画像,实现广告跟踪。
4. Cookie可被用户禁用,影响业务。
5. 非浏览器应用,如App,没有Cookie。
PHP、Tomcat 默认的Session
大多数Web容器、应用框架都提供了Session的一种默认实现方式,基本原理就是在客户端的Cookie中放一个SessionID,然后在服务器的内存中以SessionID作为key,存储对应用户的状态数据。PHP中是PHPSESSID,Tomcat是JSESSIONID。
Token、JWT
Token(令牌)是将用户的状态数据直接存储到客户端,而不是存储在服务端中。用户在登录成功之后,服务端将用户信息和其他数据通过某种规则和编码转换成一个Token字符串,并返回给客户端,客户端在请求服务端时,带上这个Token,服务端只需要验证这个Token是否正确、有效,就可以从里面得到用户信息,而不需要在服务器上花费空间进行存储。
类似的比喻,比如试卷、合同上的签名和印章,在验证文件内容真伪时,只需要验证签名或印章的痕迹是否真实有效,即可认为文件内容是否有效。
JWT(JSON Web Tokens),就是Token的一种标准(RFC 7519),定义了一种基于Json的Token方案,包含了用户数据、校验字段以及签名策略,使jwt本身可以包含用户数据,并且不会被篡改。
演化
这里以一个简单的web应用进行演化。
在Http协议诞生之初,只是为了从服务端获取简单的静态Html数据,静态页面嘛,直接Get完请求就可以了,也不需要处理上下文之间的交互。但是渐渐的,网站有了用户账号的设计,BBS、电商、即时聊天、游戏对战等等一系列需要客户端和服务端有状态的会话需求,服务端需要知道每一个无状态的HTTP请求对应的用户是谁,所以就需要在HTTP请求中带上身份识别,这时候就可以利用Cookie。
用户在登录成功时,服务端通过Set-Cookie在客户端的Cookie中保存用户身份,比如用户的ID,那么用户在登录成功后,选好商品添加购物车的时候,欸就可以把Cookie中的用户id带上,那么服务端就可以知道,这个购物车的操作是由这个id的用户做的。
乍一看,好像这个方法不错,可以实现用户的状态维持。但是Cookie在客户端上是明文保存的,可以随意修改,如果某个人把cookie里的id进行修改,把id修改成别人的,那不是就可以伪造用户请求了?而且随着业务的发展,用户需要保存的信息也越来越多,比如用户权限、用户身份、上下文一些关联的业务数据等,如果这些数据也都明文保存在客户端,这明显很不安全。
那么就到了session(狭义上的),把用户数据存储在服务器的内存里,用户和服务端建立联系的时候,服务端创建一个SessionID,把SessionId放到Cookie里,每次请求带上SessionID,服务端就可以根据这个来查到存储在内存里的用户信息。同时客户端只需要存储SessionID,并且这个id本身只是一个Key,那么就可以使用一些算法,使id变的较长且无规律,不容易被随意修改伪造。到这里,这也是现在大多数web框架内置session的实现方案,简单好用。
经过了多年的发展,这个web应用的用户数量也逐渐多了起来,一台服务器显然不能够很好的满足逐渐膨胀的用户和业务发展,那么就需要添加服务器做负载均衡共同提供服务。
问题来了,之前的用户数据是存放在服务器的内存里的,也就是用户上一次是在A服务器里处理的登录请求,那下一次用户的请求发到了B里,不是就变的没有登录了吗?
处理的方案也有几种:服务器间session复制,服务器间互相同步、互相备份;在负载均衡的时候做粘连,把来自同一个用户的请求粘连在同一个服务器上;session集中存储,利用Redis等高性能第三方存储把session放到业务服务器外等等方案,这些方案都互相有优缺点,这里暂不详细展开。
再回到开始的直接存储用户id的方案上,这个方案最大的问题就是单纯的用户id容易被篡改,那么是不是只要不让他被随意篡改就可以了?一个不会被随意篡改、又不用在服务器上面存储方案是不是就很完美?
这是Token的方案,在登录成功后,服务端给用户一个身份凭证,上面有用户的信息,同时,服务端对这个凭证签名,让他不能被随便修改,比如对用户信息和时效信息加上服务端的私钥签名,在没有拿到服务端的私钥下,谁都不能伪造出这份签名,同时又可以通过服务端的公钥进行验证。
Token的方案可以自行设计,不过业界已经有一个广泛应用的标准JWT,大部分场景直接根据这个标准进行开发就可以了。
Token实现了HTTP请求的有状态和服务端的无状态,服务端上的不同服务器不需要花费空间来存储每一个用户的用户数据,转而只需要对token进行验证就可以了,经典的时间换空间,而且可以提高服务器的伸缩性,可以随意的添加业务服务器,而不需要额外处理session的各种问题。
但是把Token当作凭证发放给客户端后,又怎么对客户端的用户进行管理?比如用户修改了密码,之前颁发的凭证需要失效,但是Token是在客户端上的,服务端无法主动失效Token,这是一个问题。那有人肯定会说,可以把Token在服务端上存一份呀,如果失效了给他打个标记,或者直接删除,不是就可以管理了吗?是的,这样确实可以在服务端上对Token进行管理,但是这样,服务端也从无状态变回了有状态,又需要处理Token在服务端上的存储、共享等等问题,和session有什么本质上的区别吗?
上面的不论sessionID还是token都是存放在cookie里的,但是要注意,这个cookie只是浏览器都支持自动处理的一个headers而已,所以在具体实现上,不论是sessionID还是token都可以放到其他的header头里,区别就是需要客户端代码自行处理而已,包括App等非浏览器应用。
综上所述,各种方案实现都有相对应的好处和问题,没有哪种方案是完美的,在一个应用中,需要根据业务本身的用户规模、服务器情况、安全程度、业务逻辑和用户体验,选择适合的方案,或是互相结合在不同的场景使用不同的方案。
安全
互联网应用最重要的问题就是安全,各种Session技术、加密算法都是为了增加攻击成本、提高安全性。
上面的方案中,很明显有一个致命的问题,每个方案基本都依赖于服务端给客户端发送的一组识别用户身份的字符串,那假如有人在中间抓包,拿到这个字符串,是不是就不安全了。是的,这是中间人攻击的一种方式。
如何避免,这个问题如果基于HTTP是没有很好的方案的,较好的方案就是使用HTTPS,基于可信根证书和端到端的加密来实现整个链路上的传输安全。
通过其他字段综合对用户身份进行验证,比如设备MAC、IP地址等信息综合判断。
在核心重要功能如支付,使用二次验证(密码、短信等)。
减少token的有效时间,并在每次请求中对token进行刷新。
安全的前提是客户端安全,如果客户端本身状态危险,那么任何的安全措施都是无效的。
用户需要做的:
- 保持操作系统安全,不被挂马、不被其他人操作,这是设备的安全。
- 使用可靠的浏览器、确认浏览器的各种设置。
- 避免安装不可信的CA根证书(数字证书),证书是保证HTTPS安全的前提,通常受信的证书是内置在操作系统中的,以及用户自行安装的,比如常见的网银证书。如果安装不可信的假CA证书,那么攻击者就可以伪造CA证书,自己对自己的请求进行签发,欺骗客户端,由于客户端安装了假CA证书,并不会对中间人的伪造证书报警。
- 确认网站地址正确,确认证书的有效性和签发机构。
- 代理,使用代理上网,那么代理本身就是一个中间人……
- 注意防范社工,社会工程学攻击。
服务端需要做的:
- 使用HTTPS
- 业务上对XSS、CROS等等攻击手段进行防范
- 使用验证码防止暴力
- 保证服务器本身及内网安全
- 敏感操作要求二次验证
- 对用户设备、ip进行绑定和验证
- 不使用任何自己想的奇奇怪怪的加密方式和算法。古典密码学的编码和破译通常依赖于算法本身的设计和对手的想象力和创造力,一但算法公开就不具备安全性,而现代密码学的安全性依赖于密钥而不是算法本身的保密性。而目前常用的密码学算法都是开源且经过了各种专家的验证和分析,适用场景、破解成本、优缺点已经很明确且公开,在不出现重大技术突破的情况下,可以基本保证安全。
题外话
上面所说的都只是互联网应用中认证与授权中的认证,即“我是谁”的问题。而授权则是控制用户对于系统的访问权限。
在系统庞大、功能复杂的情况下,可能会有多个完全不同的域名,或者是其他第三方登录的实现。那么就有了SSO和OAuth
SSO是单点登录,主要是处理一个公司的不同系统下的访问登录问题(如淘宝和天猫,他们的域是不同的,但是可以相互跳转不需要重新认证)
OAuth主要是不同公司间的授权方案,授权用户在第三方应用中访问自己在自己系统的一些隐私数据,比如一些网站的qq登录、微博登录等第三方账号登录。
这部分具体的内容在此不展开描述只是作为相关点提一下,后续可能会继续更新相应文章展开。