session? cookie?登录?还在分不清楚?

2020-02-28 22:12:37 +08:00
 zzzzzzggggggg

之前的一篇文章,发出来跟大家交流一下,有误导的部分还请评论在下面,大家互相进步

还记得在上家公司做全干工程师的时候,基本从页面写到运维,当时做登录这块的时候,被 session、cookie、token 各种概念差点整蒙圈了,上网查询相关概念,发现很多人都是类似的疑惑,比如:

来了新公司之后,前端很少接触 HTTP 请求之后的事情,而且登录相关的 SDK 封装的很好,所以这篇文章就简单的学习记录一下。

为什么会有登录这回事

首先这是因为HTTP是无状态的协议,所谓无状态就是在两次请求之间服务器并不会保存任何的数据,相当于你和一个人说一句话之后他就把你忘掉了。所以,登录就是用某种方法让服务器在多次请求之间能够识别出你,而不是每次发请求都得带上用户名密码这样的识别身份的信息。 从登录成功到登出的这个过程,服务器一直维护了一个可以识别出用户信息的数据结构,广义上来说,这个过程就叫做 session,也就是保持了一个会话。

常见的两种登录

忽然想到一点,看了网上很多问题,我觉得大家应该区分两个概念:广义的 session狭义的 session

**广义的 session:**广义的 session 就是从登录成功到登出的过程,在这个过程中客户端和服务器端维持了保持登录的状态,至于具体怎么维持住这种登录的状态,没有要求。

**狭义的 session:**狭义的 session 就是登录成功后,服务器端存储了一些必须的用户信息,这部分存在服务器端的用户信息就叫做 session,也就是接下来要说的第一种登录的实现方式。

服务器 session+客户端 sessionId

先用图来看:

详细说的说一下,这里面主要是这么几个过程:

  1. 客户端带着用户名和密码去访问 /login 接口,服务器端收到后校验用户名和密码,校验正确就会在服务器端存储一个 sessionId 和 session 的映射关系

  2. 服务器端返回 response,并且将 sessionId 以 set-cookie 的方式种在客户端,这样一来,sessionId 就存在了客户端。这里要注意的是,将 sessionId 存在 cookie 并不是一种强制的方案,而是大家一般都这么做,而且发请求的时候符合 domain 和 path 的时候,会自动带上 cookie,省去了手动塞的过程。

  3. 客户端发起非登录请求时,服务端通过 cookie 中的 sessionId 找到对应的 session 来知道此次请求是谁发出的。

token

前面说到 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 方式

  1. session 方式由于会在服务器端维护 session 信息,单机还好说,如果是多机的话,服务器之间需要同步 session 信息,服务横向扩展不方便。

  2. session 数量随着登录用户的增多而增多,存储会增加很多。

  3. session+cookie 里面存 sessionId 的方式可能会有 csrf 攻击的问题,常见的方式是使用csrf_token来解决

jwt 方式

  1. jwt 的过期时间需要结合业务做设置,而且 jwt 一旦派发出去,后端无法强行使其作废

后话

理清概念,一身轻松

--------------------------------------------

鉴于有人说我太明显,我就不放二维码了 欢迎关注我的公众号:fe-yagushou

3315 次点击
所在节点    程序员
16 条回复
zzzzzzggggggg
2020-02-28 22:13:23 +08:00
欢迎交流和斧正
gwy15
2020-02-28 22:18:46 +08:00
JWT 当然是可以作废的,校验的时候对单个用户要求全部 iat 大于阈值就可以 O(1) 作废用户全部当前 JWT。如果要作废单个 JWT 需要维护黑名单,利用 redis 维护带过期时间的哈希表也可以 O(1) 但是要 O(n) 储存空间。
freakxx
2020-02-28 22:22:53 +08:00
这个问题有类似的:
https://v2ex.com/t/641834
zzzzzzggggggg
2020-02-28 22:29:38 +08:00
@gwy15 嗯嗯是的可以做到;不过我感觉要求 iat 大于阈值这个操作,如果后台强制某用户此时此刻就下线的话,这个阈值就得改一下,如果是 session-cookie 的形式的话,把它 session 清掉就行了;作废单个 jwt 确实可以使用黑名单的方式,不过就是需要点存储
zzzzzzggggggg
2020-02-28 22:32:09 +08:00
@freakxx 嗯嗯,确实比较类似,不过他是在提问
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 方案会更合适。
poorcai
2020-02-29 17:36:04 +08:00
图片挂了
zzzzzzggggggg
2020-02-29 21:13:48 +08:00
@encro 关于 jwt 作废这个我有点异议,和你讨论一下,因为 jwt 里面的信息都是在生成的时候编码好的,如果要主动作废的话,据我了解可以用配黑名单的方式;个人觉得 session 和 jwt 的使用跟是否前后端分离关系不到,不过确实是 APP、第三方调用的时候用 jwt 会更合适
zzzzzzggggggg
2020-02-29 21:14:03 +08:00
@poorcai 我也发现了。。不知道咋搞
JamesMackerel
2020-03-01 09:49:19 +08:00
@encro 很多人对单点登录自动跳转有误解,认为它可以管理接入服务的 session。实际上单点登录并不管理甚至不关心你的 session。单点登录的职责是认证用户,并且把用户的身份提供给接入方,用户的登录态还是要服务自己管理。
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 更加方便。
JamesMackerel
2020-03-01 09:55:18 +08:00
另外,楼主的文章里的说法:用 jwt 可以防止 csrf。csrf 本质上是用户不在你的网站,却能向你的网站发送请求,并且这个请求中还能带上他的凭据而导致的问题。所以要解决这个问题,要么你验证请求是不是你自己的网站上发来的(验证 referer ),要么你想个办法不要让浏览器自动把用户凭据带到请求里。

把 jwt 放在心里 authorization 里的操作一般写在 js 里,而不是利用浏览器自动携带 cookie 的行为,所以天然避免了 csrf。但是如果有人把 jwt 扔到 cookie 里,而且还没做 csrf token,那么其实还是会有 csrf 的问题。
JamesMackerel
2020-03-01 09:57:09 +08:00
@JamesMackerel 放在心里-> 放在
encro
2020-03-01 09:58:46 +08:00
很多成熟的框架,为 session 和 jwt 提供了中间件,
你加载哪个就支持那种方式,
你加载两个就同时支持两种方式。

我所知道的有 Django REST framework, Yii, gin 等
encro
2020-03-01 10:37:13 +08:00
@JamesMackerel
是的,单点登录只是完成了登录,对于多个子系统实现了统一账户认证,这里面明线只传递一个认证身份钥匙,往往还需要一条服务器对服务器的暗线来传递用户数据。

@zzzzzzggggggg
某人跟你借东西,
你给了家里钥匙让他自己去拿,这时候钥匙就是 key,所有人拿了钥匙都能去你家拿东西;
如果你装了人脸识别电子锁,你给他远程开通一个有时间限制的人像识别,这个时候就是 jwt ;
虽然有了人脸识别电子锁,但是不能防止他拿走其他东西,也不能防止他已经被挟持(抓包)了;
你不让他去拿,你自己拿给他,这时需要鉴权或者 RPC。
zzzzzzggggggg
2020-03-01 21:34:32 +08:00
@JamesMackerel 嗯嗯,老铁说的对; csrf 关键还是解决浏览器自动带用户凭证的问题

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/648454

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX