WEB 会话管理杂谈

Posted by 卡神 on January 16, 2022

1 前言

某一天,有同事在产品问题反馈大群里扔出一个问题:登录后隔两天再打开页面就需要重新登录。在查问题的过程中,我们有提到 7 天过期,老板对此也扔出了一个问题:为什么是 7 天。于是有了今天这篇文章。首先我们先看一下 WEB 会话管理的概念。

2 概念

在说 WEB 会话管理之前,我们需要先说一下 HTTP 协议。这里我们暂且不提 HTTP2.0 和 HTTP3.0,本文的 HTTP 协议主要是指现在使用最广泛,使用历史最久的 HTTP1.x 版本。

HTTP 协议是一个无连接无状态的协议。

  • 无连接是指限制每次连接只处理一个请求,服务端处理完客户端的请求,并收到客户端的应答后,即断开连接。后面协议中增加了 Keep-Alive,这个特性使得客户端到服务端的连接持续有效,当出现对服务端的后继请求时,Keep-Alive 功能避免了建立或者重新建立连接。
  • 无状态是指在协议层面对于事务处理没有记忆能力,服务端不知道客户端是什么状态。当我们给服务端发送 HTTP 请求之后,服务端根据请求内容给我们返回数据,但是返回数据后,服务端不会记录任何信息。

在互联网早期,这样的协议完全能满足 WEB 应用的需要,但随着 WEB 应用复杂度的提升,识别用户是谁,记录用户的行为,有用户状态变成了刚性需求,或者说是核心功能点。 此时,会话管理应运而生,会话管理是一种控制和维持用户交互状态的机制,它定义了一系列用于管理用户和 WEB 应用系统交互状态的措施。

会话管理有两个关键点,一个是怎样存储,另一个是存储什么。

3 存储方式

3.1 服务端 session

在早期的有会话管理的 WEB 应用中,会话经常存储在服务端的 session 中,常见的 WEB 编程语言都自带 session 的处理逻辑,如 PHP 和 Java 都默认带有 session 的扩展或组件,如 PHP 中的全局变量 $_SESSION。客户端存储 sessionid,通过 cookie / 隐藏的表单字段 / 重写 URL 的方式传递 sessionid,后两种方式一般在禁用 cookie 的时候启用。

服务端 seesion 的优势

  • 安全性好,客户端与服务端保持会话状态的媒介始终只是一个 sessionid 串,我们一般会保证这个串的随机性,以防止攻击者遍历或穷举;除非通过 CSRF 或 HTTP 劫持等方式,才有可能冒充别人进行操作。最坏的情况就算是冒充成功,在服务端我们也会有一些登录凭证的验证来进行纵深的防御;
  • 可控度高,在服务端集中存储,后台同学可以对其进行操作,比如踢下线之类的操作。

针对服务端的存储方式,就会出现了我们在前言中提到的过期问题,为什么要有过期?

  1. 为了节省服务器资源,登录态相关信息存储在服务器还是需要一定资源的,如果一直不过期,几千万上亿的用户登录态数据将是较大的一个成本,而这个成本是完全可以省下来的;
  2. 为了安全,用户长时间未操作,如果有人用了他的账号干了点别的,这样就存在较大的风险;另外在业务逻辑上可以实现抢登等逻辑以规避一些安全上的风险。

有了过期,就会有过期时间,常见的过期时间有 1 小时( 3600 秒),2 小时( 7200 秒),1 天, 7 天(一周),15 天,30 天(一个月)。这个过期时间更多的是经验值,或者说是一个大家认为合适的值。这个值对业务的影响,貌似没有一个客观的评价。

服务端的 seesion 的常见存储方案如下:

  • 单机方案,存储要么是硬盘,要么是内存,二者的区别在于获取和写入的速度,对于海量的互联网应用来说,存储到内存方案更常见一些。单机存储方案是有一定上限的,其上限是单机存储的上限。
  • 分布式方案,分布式的内存数据库如 Redis / Memecached,Tomcat 等都有现成的共享 session 方案。分布式存储方案从理论上来讲是没有存储上限的,可以给 Redis 做分布式部署。

考虑到服务器的负担和架构的复杂性,依赖于浏览器每次请求都会带上 cookie 的特性,我们可以把用户的登录凭证直接存到客户端(浏览器)中。当用户登录成功之后,把登录凭证写到 cookie 里面,并给 cookie 设置有效期,后续请求直接验证存有登录凭证的 cookie 是否存在以及凭证是否有效,即可判断用户的登录状态。当年 Rails 的默认会话存储方案是用 cookie 方案。

优点

  • 实现了服务端的无状态化,彻底移除了服务端对会话管理的逻辑,服务端只负责创建和验证登录 cookie 即可;
  • 不同应用间的登录态可以通过算法或密钥的一致性来保持,以实现会话共享。

缺点

  • cookie 有大小限制,存储不了太多数据,同时会话会占用其它业务场景的空间,从而导致 cookie 空间紧张;
  • cookie 在每次请求时都会带上,当会话中有较多的冗余数据时,会对性能有一些影响,并且产生一些不必要的网络带宽;
  • 多应用时可能会有跨域问题,浏览器在发起请求时会检查所有存储的 cookie ,如果某个 cookie 所声明的作用范围大于等于将要请求的资源所在的位置,则会把该 cookie 附在请求资源的 HTTP 请求头上发送给服务器。当作用范围小了,或者交错了,则会出现跨域问题。

3.3 令牌方案

相对于前面两种方案,令牌方案是前后端分离后的常见方案,session 的概念在这个方案里面更淡一些。

在讲令牌方案之前我们先讲一下安全上两个非常重要的点:认证和授权。有时候会把这两个东西弄混,其定义如下:

  • 认证是这样一种验证过程:通过让用户、网站、应用程序通过提供合法证书或验证方式,以证明他们符合自己所宣称的身份。
  • 授权是指的是一个验证某用户能访问什么的过程。在授权过程中,某用户 / 应用程序的权限级别被确定后,才被允许访问特定的 APIs / 模块。通常,授权发生在用户身份被认证之后。
区别 认证 授权
作用 确定用户所宣称的身份 确定用户可访问的权限
方式 通过合法凭证校验用户 通过规则和策略校验访问
时机 早于授权 在认证成功后执行
实现 通过 ID tokens 实现 用 Access Tokens 实现

基于令牌的认证和授权是现在最流行的的一种技术,当用户在某处输入一次其用户名和密码后,作为交换会得到一个唯一生成的已加密令牌。该令牌随后会替代登陆凭证,用以访问受保护的页面或资源。当我们要进行下一步的业务操作,会通过令牌获取到用户的的信息和会话的信息,此时一般是通过进程内的缓存或者某个特定的微服务来获取,这些微服务的后端的存储因公司技术架构和服务而异。一般来说,对于海量的互联网应用来说,这里最终还是从内存中获取的数据,如前面说到的分布式 NoSQL 数据库。

一个令牌就是服务器生成的一段数据,包含了唯一性识别一个用户的信息,一般被生成为一长串随机字符和数字。

比如看起来可能像这样:bb74324734bcf34748bb08bu2842f3288 或更复杂些比如: eyJ0eXAiOiJqd3QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ1bXMiLCJzdWIiOjY1NDA1NjI5NCwiYXVkIjoiZ2FvZGluZ3giLCJleHAiOjE2NDE5MDgzOTZ9.8MZaB55vlScSDZxBmO-N9ol9UTvYPVvgUFab7dFe6fY 这个令牌本身是无意义和无用的,但结合适当的令牌体系,就会变成保证应用安全性的重要一环。

基于令牌的实现过程一般如下:

  1. 用户通过用户名和密码(或者其它登录方式,如国内的微信扫码)请求访问;
  2. 应用验证登录凭证;
  3. 应用向客户端发放已签名的令牌;
  4. 客户端存储令牌,并将其附加在其后的每次请求中一同发送(一般是带在 cookie 中,和 sessionid 类似);
  5. 服务器验证令牌并响应数据。

我们常用的基于令牌的方案有两个:

关于这两个方案的更详细的说明见:深入 OAuth2.0 和 JWT

在令牌的生命周期中存在两个较大的安全薄弱环节:

  1. 会话令牌生成过程中的薄弱环节,令牌的生成依赖于用户通过用户名/邮箱或密码生成,这里存在较大的泄漏风险;
  2. 所有处理会话令牌的薄弱环节,如传输过程中(没有加密的链路或被劫持等),日志记录中等。

4 存储内容

会话从本质上来讲是一种缓存,一种强关联用户的缓存,所以与用户相关的一些常用数据可以放在会话里面,如头像、用户名、真实姓名、积分等等。这些缓存数据都只读,当要更新时还是需要从数据库中获取后再次更新写入,而不是依赖于会话的数据。

除了常用数据我们经常还会把购物车或者菜单、权限等写入会话中,此时除了用户属性,还带上了业务属性,我们可以称之为业务会话。业务会话一般是指某个业务场景中需要临时缓存起来的数据,而且这部分数据大多数是要落库的,当用户会话过期重新登录后这部分数据还是要从数据库重新获取,加载到会话中。

5 小结

最后这个问题的原因是业务逻辑里面针对不同的渠道有抢登的业务逻辑,而这个逻辑在某个时候是不需要触发的。 此外,老板最后在群里问了一个触及灵魂的问题:

我们做一件事情的时候是依着惯性做事,还是基于深度思考后的决策?

自我反省中~~

这篇文章墨迹了几周,一直不知道如何写好,总觉得写得有点不如人意,如果往协议本身来写,好像也没有想写的,就这样吧。

你好,我是潘锦,超过 10 年的研发管理和技术架构经历,出过书,创过业,带过百人团队,也在腾讯,A 股上市公司呆过一些年头,现在在一家 C 轮的公司负责一些技术方面的管理工作。早年做过 NOI 和 ACM,对前端架构、跨端、后端架构、云原生、DevOps 等技术始终保持着浓厚的兴趣,平时喜欢读书、思考,终身学习实践者,欢迎一起交流学习。微信公众号:架构和远方,博客: www.phppan.com