前言
前几天,我写了一篇文章,《我设计的一个安全的 web 系统用户密码管理流程》。其中着重点是讲的如何利用非对称加密进行安全的设计,并在讲述了原理之后,又写了 《node 后端和浏览器前端,有关 RSA 非对称加密的完整实践, 前后端匹配的代码演示》 这篇文章,着重讲解了有关 RSA 非对称加密的完整实现。
但是,这些文章里并没有讲到用户登录管理的核心,也就是用户会话管理。这里面有很多概念,对于很多前端开发和很多初级后端开发来说,都是很模糊的。所以,今天,我想通过这一篇文章,把这里面的核心点全部讲明白。
基础概念
我们会听到一些词,大概是 Session
, Session ID
, Cookies
, Token
等等,现在又有了新词儿,新的解决方案,如 JWT
,长短 Token 等等。
我们一个一个来讲。这些词分别有对应的英文意思,但是每个词都有多个含义,并且在不同的环境下,还会有衍生不同的含义。因此,我这边只能从前后端开发的角度去解释这些名词,不能按照字典的含义去解释。
而且,由于网上言论众多,其中谬误的也好,佶屈聱牙的也罢,我都不去管它,我只按照我的理解来讲解,并且,我相信,我的理解是非常深入到位的。
Session
翻译------会话,引申为会话管理。那么,什么是会话呢?
首先,我们要理解一个基础概念------http 服务,是一种无状态服务。怎么理解无状态呢?一句话讲,就是------在默认情况下,后端程序根本不知道前端发过来的请求,谁是谁!
为了解决这个不知道谁是谁的问题,就引申出来解决方案------让后端程序知道,每一个请求,都是谁发出来的,好相应的把对应的资源发回去。
比如,张三请求 profile
接口,返回的内容应该 {name:"张三"}
,而不能是 {name:"李四"}。
那么,我们就可以理解,张三发出来的一系列的请求,应该归属于一个会话,李四发出来的一系列请求,应该归属于另一个会话。
所以,我们就需要把这一系列的会话,进行管理,这就是会话管理。
换句话想,Session,并不是一个单纯的词,而是一个相对很大的概念,为了解决这个问题,不同的语言有不同的解决方案,这些各种不同的解决方案,都可以统称为 Session。
Session ID
理解了上面的 Session 的概念,这个概念就相对比较好理解了。每一个用户,都有一堆的资源,我们不能把这些资源都随时在程序里放着。因此,我们会把这些资源打包存着,然后整出一个 ID 来,方便我们来查询。这样,前端只要给我们这个 ID,我们就可以根据这个ID查找到对应的资源。
这里说的用户资源,是指 用户名,用户昵称,用户角色,过期时间等简短信息,只要方便后端程序快速的分清楚这货是谁,能干些啥事儿即可。不是说,要把所有的用户资源信息,全部存着。如果是那样的话,还要数据库干啥呢对吧。
Token
字面意思,凭证,令牌。是后端下发给前端的一段字符串(可长可短,你要愿意,给数字也不是不行,看你咋设计了。)
前端拿到这个 Token 以后,它每次发起请求时,都需要把这个 Token 给带上,后端根据这个 Token ,就可以去查找对应的资源了。
OK,注意到了吗?上面的 Session ID 是方便我们后端去查找资源的,这个 Token,也是后端去查找对应资源的。
所以------ Token = Session ID ?
答案是不一定。要看你咋设计。
一般而言,我是倾向于设计为 Token,就是 Session ID。当然,也可以不一样,前端给后端 Token,后端先根据 Token 去查找 SessionID,然后根据 ID 再查找对应资源,也是可以的对吧?
但我感觉这样设计有点缺心眼儿。
Cookies
字面意思是一种饼干,我不喜欢吃饼干,所以我不知道它具体是哪一种饼干。
Cookies 是用户浏览器的一种机制,它可以由我们的后端程序去设定。而且,在每次用户发起请求的时候,浏览器都会把 Cookies 自动带上。
换句话讲,如果我们使用 Cookies,可以大幅的减少前端开发的工作量,因为具体是咋实现的,不用管了,浏览器自动实现。
但问题是,现在的前端开发,往往不仅仅是运行在浏览器里面的网页,还有 APP、 小程序或者其他客户端程序,而这些里面,大多是没有 Cookies 的机制的。
因此,为了我们的后端服务可以同时服务多端,我建议就不要管这个玩意儿了。
不是不能用,顺手的时候,也可以兼容。
JWT
JSONWebToken
的缩写。在传统的会话管理里,都涉及一个存储的问题,就是,要把用户资源存着,然后去查询。你可以存在内存里,文件里,数据库里,等等,总之,你要存数据,并且方便查询。

假设,我们存在内存里,而我们的后端服务运行在多台负载均衡的服务器上,那么,当用户的 A 请求访问在甲服务器上,甲服务器在内存里存储了数据,B请求访问在乙服务器上,而乙服务器的内存中并没有用户数据,则会出现读取失败的问题。
怎么解决呢?聪明的你一定想到了解决方案------集中存储,比如单独搞一台 redis 服务器,大家都往这里存。挺好的。
那么假设你的这些服务器不在一个机房呢?也不是不能读是吧,就是慢点儿。
总之,传统的会话管理,有一个问题,就是 IO 问题,也就是存储和读取的问题。
JWT 方案是换了一个思路来解决,其核心原理是,直接将用户的资源信息加密,然后返回给客户端,客户端拿这个加密的长字符串直接作为 Token 来请求,后端程序只要解密字符串,就直接获得了用户的资源信息了。
也就是说, JWT 是用计算复杂度取代了存储复杂度,来解决上面说的问题。
总之,会话管理是一定要消耗资源的,要么是存储复杂度,要么是计算复杂度。
在很多文章和概念里,把 JWT 方案,和 Session 方案并列对比。可以这么比,但是我感觉概念不对。其实在我看来,JWT 方案,只是一种 Session 解决方案而已,两者是上下级的关系,而不是并列的关系。
我这边把传统的通过存储(你别管存哪儿,咋存)解决方案,称之为传统存储方案吧。
和传统存储方案相对比,优缺点如下
特点 | JWT | 存储 |
---|---|---|
服务器状态 | 无状态 | 有状态 |
跨域支持 | 天然支持 | 需CORS配置 |
实时吊销 | 默认不支持 | 支持 |
传输效率 | 大点儿,问题不大 | 小,可忽略 |
其实总结下来,JWT最大的问题就是,一旦 Token 签发,除了超时失效,否则是没办法把用户登出的。
当然,聪明的你,肯定想到了黑名单机制,把登出的 Token 存下来,然后每次读取验证一下。那么我的问题是,那你为啥不用传统的存储会话管理,要用 JWT ?吃饱撑的?
长短 Token
好,通过上面的这些概念,我们知道,JWT 方案,不方便管理用户,传统存储解决方案,有IO性能问题,那么能不能两种方案结合结合,咱们取长补短!
可以,于是,有了长短 Token 的解决方案。
每次用户登录时,用 JWT 方式,签发一个长的 Token,这个 Token 的过期时间比较短,比如5分钟。5分钟后,它就失效了。
同时,用传统存储方案,签一个短的 Token。这个过期时间可以设置得比较长,比如一个月。
在正常业务时,后端直接读取用户传过来的 JWT 信息,就可以得到用户资源了。当前端发现 token 过期了,就拿 短 token 过来,再请求一个长 token,然后再继续使用。
这样,就解决了存储问题,又可以管理用户登出(直接在存储里删掉用户的短Token 即可)
代价是什么呢?代价是提高了各个前端项目的代码复杂度。你可以说,长短 Token 的解决方案是集合了两种解决方案的长处,也可以理解为,两者的短处也全粘上了。
传输数据变大,还是得存储用户信息,还消耗CPU计算资源等等。
小结
综上所述,我们应该对用户会话管理这块的概念都有了一个清晰的理解了。那么我们在开发项目时应该如何选择呢?
首先,无论是哪种解决方案,你都得会!没有什么方案是一招鲜,吃遍天的。
例如,传统存储方案中的所谓 IO 问题,对于绝大多数项目来说,这就是一个不存在的问题。直接往内存一存,有啥性能瓶颈?拢共就跑在一台服务器上,哪里来的什么速度或者进程共享的问题?在我看来,绝大多数项目,连 redis 都是多余。
再说,JWT 方案,有啥登出管理问题?一个小系统,一天登录拢共就几个人,你设置为有效期1天,我也不认为有多大的安全隐患,如果真遇到了什么大问题,直接把 JWT 的签发加密词一改,所有签发的TOKEN,全部失效。
最后说说长短 TOKEN,如果你团队有足够多的人手,项目也确实有很高的安全性,可以考虑使用。否则,是徒增麻烦。
所以,我推荐你根据你自己的项目选择合适的解决方案,我只说我的观点:
- 大多数项目都可以使用 JWT 方案。
- 你有切实的要迫使用户登出的项目,比如论坛等等,可以用传统的存储方案。
- 你团队实力足够,甲方是安全洁癖,项目也确实有高等级的要求,或者产品或老板想装逼忽悠客户,长短TOKEN你值得拥有!
本来,我是想在这篇文章中,引用一些代码,方便各位看官理解的,没想到,光讲概念就讲了这么多了。没办法,周末抽空,我会把每种方案的 NodeJS + Koa 的实战完整代码给写成文章分享给大家。
最近我在参加 CSDN 的一个创作比赛,各位看官在留言区留言可以给我加分。所以希望各位看官帮帮忙,点评一下哦!
当然,我们程序开发都是很忙滴,没时间点评没关系哈,点个赞,收个藏,也不是不可以哈!耽误不了您的事儿,还能让我继续保持创作热情,小可在这边谢谢各位看官了哈!