之前的一篇文章,发出来跟大家交流一下,有误导的部分还请评论在下面,大家互相进步
还记得在上家公司做全干工程师的时候,基本从页面写到运维,当时做登录这块的时候,被 session、cookie、token 各种概念差点整蒙圈了,上网查询相关概念,发现很多人都是类似的疑惑,比如:
来了新公司之后,前端很少接触 HTTP 请求之后的事情,而且登录相关的 SDK 封装的很好,所以这篇文章就简单的学习记录一下。
首先这是因为HTTP是无状态的协议,所谓无状态就是在两次请求之间服务器并不会保存任何的数据,相当于你和一个人说一句话之后他就把你忘掉了。所以,登录就是用某种方法让服务器在多次请求之间能够识别出你,而不是每次发请求都得带上用户名密码这样的识别身份的信息。 从登录成功到登出的这个过程,服务器一直维护了一个可以识别出用户信息的数据结构,广义上来说,这个过程就叫做 session,也就是保持了一个会话。
忽然想到一点,看了网上很多问题,我觉得大家应该区分两个概念:广义的 session和狭义的 session
**广义的 session:**广义的 session 就是从登录成功到登出的过程,在这个过程中客户端和服务器端维持了保持登录的状态,至于具体怎么维持住这种登录的状态,没有要求。
**狭义的 session:**狭义的 session 就是登录成功后,服务器端存储了一些必须的用户信息,这部分存在服务器端的用户信息就叫做 session,也就是接下来要说的第一种登录的实现方式。
先用图来看:
详细说的说一下,这里面主要是这么几个过程:
客户端带着用户名和密码去访问 /login 接口,服务器端收到后校验用户名和密码,校验正确就会在服务器端存储一个 sessionId 和 session 的映射关系
服务器端返回 response,并且将 sessionId 以 set-cookie 的方式种在客户端,这样一来,sessionId 就存在了客户端。这里要注意的是,将 sessionId 存在 cookie 并不是一种强制的方案,而是大家一般都这么做,而且发请求的时候符合 domain 和 path 的时候,会自动带上 cookie,省去了手动塞的过程。
客户端发起非登录请求时,服务端通过 cookie 中的 sessionId 找到对应的 session 来知道此次请求是谁发出的。
前面说到 sessionId 的方式本质是把用户状态信息维护在 server 端,token 的方式就是把用户的状态信息加密成一串 token 传给前端,然后每次发请求时把 token 带上,传回给服务器端;服务器端收到请求之后,解析 token 并且验证相关信息;
所以跟第一种登录方式最本质的区别是:通过解析 token 的计算时间换取了 session 的存储空间
业界通用的加密方式是 jwt (json web token),jwt 的具体格式如图:
简单的介绍一下 jwt,它主要由 3 部分组成:
header 头部 { "alg": "HS256", "typ": "JWT" } payload 负载 { "sub": "1234567890", "name": "John Doe", "iat": 1516239022, "exp": 1555341649998 } signature 签名
header 里面描述加密算法和 token 的类型,类型一般都是 JWT ;
payload 里面放的是用户的信息,也就是第一种登录方式中需要维护在服务器端 session 中的信息;
signature 是对前两部分的签名,也可以理解为加密;实现需要一个密钥( secret ),这个 secret 只有服务器才知道,然后使用 header 里面的算法按照如下方法来加密:
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
总之,最后的 jwt = base64url(header) + "." + base64url(payload) + "." + signature
jwt 可以放在 response 中返回,也可以放在 cookie 中返回,这都是具体的返回方式,并不重要。
客户端发起请求时,官方推荐放在 HTTP header 中:
Authorization: Bearer <token></token>
这样子确实也可以解决 cookie 跨域的问题,不过具体放在哪儿还是根据业务场景来定,并没有一定之规。
session 方式由于会在服务器端维护 session 信息,单机还好说,如果是多机的话,服务器之间需要同步 session 信息,服务横向扩展不方便。
session 数量随着登录用户的增多而增多,存储会增加很多。
session+cookie 里面存 sessionId 的方式可能会有 csrf 攻击的问题,常见的方式是使用csrf_token来解决
理清概念,一身轻松
--------------------------------------------
鉴于有人说我太明显,我就不放二维码了 欢迎关注我的公众号:fe-yagushou
1
zzzzzzggggggg OP 欢迎交流和斧正
|
2
gwy15 2020-02-28 22:18:46 +08:00
JWT 当然是可以作废的,校验的时候对单个用户要求全部 iat 大于阈值就可以 O(1) 作废用户全部当前 JWT。如果要作废单个 JWT 需要维护黑名单,利用 redis 维护带过期时间的哈希表也可以 O(1) 但是要 O(n) 储存空间。
|
3
freakxx 2020-02-28 22:22:53 +08:00
这个问题有类似的:
https://v2ex.com/t/641834 |
4
zzzzzzggggggg OP @gwy15 嗯嗯是的可以做到;不过我感觉要求 iat 大于阈值这个操作,如果后台强制某用户此时此刻就下线的话,这个阈值就得改一下,如果是 session-cookie 的形式的话,把它 session 清掉就行了;作废单个 jwt 确实可以使用黑名单的方式,不过就是需要点存储
|
5
zzzzzzggggggg OP @freakxx 嗯嗯,确实比较类似,不过他是在提问
|
6
encro 2020-02-29 17:11:01 +08:00
一,session 多服务器共享?
1,可以采用阿里云 lbs 或者 nginx 保持会话; 2,也可以客户端加密 cookie 存 session id,然后服务端 session 存在 redis ; 3,采用单点登录系统+自动跳转等方案来实现; 二,JWT 不能强制作废? 1, jwt 没有做服务端验证码?既然做了,肯定可以作废啊。 他们主要不同,看名字就知道了, session 意思是会话,会话是通过 cookie 来保持的,而 cookie 收到 domain 限制。 JWT 名字里面重点在于 token,是一种认证方式,通过传递 token 保持认证有效性。 他们的原理导致实用场景不一样。 一般来说,做 web 前后端不分离,使用 session 方便, 如果前后端分离,比如前后端分离的 web、app、第三方程序调用那么采用 jwt 方案会更合适。 |
7
poorcai 2020-02-29 17:36:04 +08:00
图片挂了
|
8
zzzzzzggggggg OP @encro 关于 jwt 作废这个我有点异议,和你讨论一下,因为 jwt 里面的信息都是在生成的时候编码好的,如果要主动作废的话,据我了解可以用配黑名单的方式;个人觉得 session 和 jwt 的使用跟是否前后端分离关系不到,不过确实是 APP、第三方调用的时候用 jwt 会更合适
|
9
zzzzzzggggggg OP @poorcai 我也发现了。。不知道咋搞
|
10
JamesMackerel 2020-03-01 09:49:19 +08:00 via iPhone
@encro 很多人对单点登录自动跳转有误解,认为它可以管理接入服务的 session。实际上单点登录并不管理甚至不关心你的 session。单点登录的职责是认证用户,并且把用户的身份提供给接入方,用户的登录态还是要服务自己管理。
|
11
encro 2020-03-01 09:51:09 +08:00
@zzzzzzggggggg
jwt 不用作废,只是 jwt 之后,肯定还对了一个用户吧,其实和 session 一样的,你不是要去删除这个 session_id 或者这个 token,而是收到 session 和 token 之后,服务端必须验证他们有效性,这里加一个判断他们是否还真实有效,比如用户状态已经改为删除,这时候就需要返回 token 无效,不需要再做什么黑名单。 前面分析了,jwt 和 session 其实没有区别(加密后的 Session id 就等于 token ),只是 session id 依赖 cookie,所以必须基于浏览器或者模拟 cookiejar,cookie 有 domain 限制,需要在一个 domain 下才能使用,且历史原因,大量 web 程序都为 session 提供了很多解决方案,比如脚本语言都提供了全局变量,session 钩子类或方法,所以做不跨域的 web 时候,可能使用 session 更加方便。而 jwt 无需依赖 cookie,所以没有域名要求,所以对于前后端分离的处于两个域的,客户端不是 web 的,用 jwt 更加方便。 |
12
JamesMackerel 2020-03-01 09:55:18 +08:00 via iPhone 1
另外,楼主的文章里的说法:用 jwt 可以防止 csrf。csrf 本质上是用户不在你的网站,却能向你的网站发送请求,并且这个请求中还能带上他的凭据而导致的问题。所以要解决这个问题,要么你验证请求是不是你自己的网站上发来的(验证 referer ),要么你想个办法不要让浏览器自动把用户凭据带到请求里。
把 jwt 放在心里 authorization 里的操作一般写在 js 里,而不是利用浏览器自动携带 cookie 的行为,所以天然避免了 csrf。但是如果有人把 jwt 扔到 cookie 里,而且还没做 csrf token,那么其实还是会有 csrf 的问题。 |
13
JamesMackerel 2020-03-01 09:57:09 +08:00 via iPhone
@JamesMackerel 放在心里-> 放在
|
14
encro 2020-03-01 09:58:46 +08:00
很多成熟的框架,为 session 和 jwt 提供了中间件,
你加载哪个就支持那种方式, 你加载两个就同时支持两种方式。 我所知道的有 Django REST framework, Yii, gin 等 |
15
encro 2020-03-01 10:37:13 +08:00
@JamesMackerel
是的,单点登录只是完成了登录,对于多个子系统实现了统一账户认证,这里面明线只传递一个认证身份钥匙,往往还需要一条服务器对服务器的暗线来传递用户数据。 @zzzzzzggggggg 某人跟你借东西, 你给了家里钥匙让他自己去拿,这时候钥匙就是 key,所有人拿了钥匙都能去你家拿东西; 如果你装了人脸识别电子锁,你给他远程开通一个有时间限制的人像识别,这个时候就是 jwt ; 虽然有了人脸识别电子锁,但是不能防止他拿走其他东西,也不能防止他已经被挟持(抓包)了; 你不让他去拿,你自己拿给他,这时需要鉴权或者 RPC。 |
16
zzzzzzggggggg OP @JamesMackerel 嗯嗯,老铁说的对; csrf 关键还是解决浏览器自动带用户凭证的问题
|