详解给HTTP添加状态的两种方法

我们知道,http 是无状态的,也就是说上一次请求和下一次请求之间没有任何关联。但是我们要实现应用的功能,很多时候是需要有状态的,比如登录之后,再添加购物车,那就应该识别出是登录用户做的。
怎么给 http 请求添加上状态呢?
这个问题的解决有两种方案:服务端存储的 session + cookie 的方案,客户端存储的 token 的方案。
但其实这两种方案都不怎么样,都不够完美。
为什么这么说呢?我们分别来看一下:

服务端存储的 session + cookie

给 http 添加状态,那就给每个请求打上个标记,然后在服务端存储这个标记对应的数据。这样每个被标记的请求都可以找到对应的数据,自然可以做到登录、权限等状态的存储。
这个标记应该是自动带上的,所以 http 设计了 cookie 的机制,在里面存储的数据是每次请求都会带上的。
然后根据 cookie 里的标记去查找的服务端对应的数据叫做 session,这个标记就是 session 的 id。


image.png

如图,因为请求自动带上 cookie,那两次请求就都可以找到 id 为 1 对应的 session,自然就知道当前登录的用户是谁,也可以存储其他的状态数据。
这就是 session + cookie 的给 http 添加状态的方案。
大家觉得这种方案有问题么?
有问题,而且问题还挺多的。
最大的一个问题就是臭名昭著的 CSRF(跨站请求伪造):

CSRF

因为 cookie 会在请求时自动带上,那你在一个网站登录了,再访问别的网站,万一里面有个按钮会请求之前那个网站的,那 cookie 依然能带上。而这时候就不用再登录了。
这样万一点了这个按钮之后做了一些危险的操作呢?
是不是就很危险。
而且一般这种利用 CSRF 漏洞的网站都会伪装的很好,让你很难看出破绽来,这种网站叫做钓鱼网站。
为了解决这个问题,我们一般会验证 referer,就是请求是哪个网站发起的,如果发起请求的网站不对,那就阻止掉。
但这样依然不能完全解决问题,万一你用的浏览器也是有问题的,能伪造 referer 呢?
所以一般会用随机值来解决,每次登录随机生成一个值,放到 session 中,后面的请求需要包含这个值才行,否则就认为是非法的。
这个随机值叫做 token,可以放在参数中,也可以放在 header 中,因为钓鱼网站拿不到这个随机值,就算带了 cookie 也没发通过服务端的验证。
这是 session + cookie 这种方案的一个缺点,但是是有解决方案的。
它还有别的缺点,比如分布式的时候:

分布式 session

session 是把状态数据保存在服务端,那么问题来了,如果有多台服务器呢?
当并发量上去了,单台服务器根本承受不了,自然需要做集群,也就需要多台服务器来提供服务。
而且现在后端还会把不同的功能拆分到不同的服务中,也就是微服务架构,自然也需要多台服务器。
那不同服务器之间的 session 怎么同步?


image.png

登录之后 session 是保存在某一台服务器的,之后可能会访问到别的服务器,这时候那台服务器是没有对应的 session 的,就没法完成对应的功能。
这个问题的解决有两种方案:
一种是 session 复制,也就是通过一种机制在各台机器自动复制 session,并且每次修改都同步下。这个有对应的框架来做,比如 java 的 spring-session。
各台服务器都做了 session 复制了,那你访问任何一台都能找到对应的 session。
还有一种方案是把 session 保存在 redis,这样每台服务器都去那里查,只要一台服务器登录了,其他的服务器也就能查到 session,这样就不需要复制了。
还好,session 在分布式时的这个问题也算是有解决方案的。
但你你以为这就完了么?session + cookie 还有跨域的问题:

跨域

cookie 为了安全,是做了 domain 的限制的,设置 cookie 的时候会指定一个 domain,只有这个 domain 的请求才会带上这个 cookie。
而且还可以设置过期时间、路径等:


image.png

那万一是不同 domain 的请求呢?也就是跨域的时候,怎么带 cookie 呢?
a.guang.com 和 b.guang.com 这种还好,只要把 domain 设置为顶级域名 guang.com 就可以了,那二三级域名不同也能自动带上。
但如果顶级域名也不同就没办法了,这种只能在服务端做下中转,把这俩个域名统一成同一个。
上面说的不是 ajax 请求,ajax 请求有额外的机制:
ajax 请求跨域的时候是不会挟带 cookie 的,除非手动设置 withCredentials 为 true 才可以。
而且也要求后端代码设置了对应的 header:

Access-Control-Allow-Origin: "当前域名";
Access-Control-Allow-Credentials: true

这里的 allow origin 设置 * 都不行,必须指定具体的域名才能接收跨域 cookie。
这是 session + cookie 方式的第三个坑,好在也是有解决方案的。
我们做下小结:
session + cookie 的给 http 添加状态的方案是服务端保存 session 数据,然后把 id 放入 cookie 返回,cookie 是自动携带的,每个请求可以通过 cookie 里的 id 查找到对应的 session,从而实现请求的标识。这种方案能实现需求,但是有 CSRF、分布式 session、跨域等问题,不过都是有解决方案的。
session + cookie 的方案确实不太完美,我们再来看另一种方式怎么样:

客户端存储的 token

session + cookie 的方案是把状态数据保存在服务端,再把 id 保存在 cookie 里来实现的。既然这样的方案有那么多的问题,那我反其道而行之,不把状态保存在服务端了,直接全部放在请求里,也不放在 cookie 里了,而是放在 header 里,这样是不是就能解决那一堆问题了呢?
token 的方案常用 json 格式来保存,叫做 json web token,简称 JWT,我们就拿这个来说吧。
JWT 是保存在 request header 里的一段字符串(比如用 header 名可以叫 authorization),它分为三部分:


image.png

如图 JWT 是由 header、payload、verify signature 三部分组成的:
header 部分保存当前的加密算法,payload 部分是具体存储的数据,verify signature 部分是把 header 和 payload 还有 salt 做一次加密之后生成的。(salt,盐,就是一段任意的字符串,增加随机性)
这三部分会分别做 Base64,然后连在一起就是 JWT 的 header,放到某个 header 比如 authorization 中:

authorization: barer xxxxx.xxxxx.xxxx

请求的时候把这个 header 带上,服务端就可以解析出对应的 header、payload、verify signature 这三部分,然后根据 header 里的算法也对 header、payload 加上 salt 做一次加密,如果得出的结果和 verify signature 一样,就接受这个 token。
把状态数据都保存在 payload 部分,这样就实现了有状态的 http:


image.png

而且这种方式是没有 session + cookie 那些问题的,不信我们分别来看一下:
CSRF:因为不是通过自动带的 cookie 来关联服务端的 session 保存的状态,所以没有 CSRF 问题,没法通过 cookie 攻击。
分布式 session:因为状态不是保存在服务端,所以无论访问哪台服务器都行,只要能从 token 里解析出状态数据就行。
跨域:因为不是 cookie 那一套,自然也没有跨域的限制,只要手动带上 JWT 的 header 就行。
看起来这种方式好像很完美?
其实也不是,JWT 有 JWT 的问题:

安全性

因为 JWT 把数据直接 Base64 之后就放在了 header 里,那别人就可以轻易从中拿到状态数据,比如用户名等敏感信息,也能根据这个 JWT 去伪造请求。
所以 JWT 要搭配 https 来用,让别人拿不到 header。

性能

JWT 把状态数据都保存在了 header 里,每次请求都会带上,比起只保存个 id 的 cookie 来说,请求的内容变多了,性能也会差一些。
所以 JWT 里也不要保存太多数据。

没法让 JWT 失效

session 因为是存在服务端的,那我们就可以随时让它失效,而 JWT 不是,因为是保存在客户端,那我们是没法手动让他失效的。
所以 JWT 的过期时间不要设置的太长。
所以说,JWT 的方案虽然解决了很多 session + cookie 的问题,但也不完美。

总结

http 是无状态的,也就是请求和请求之间没有关联,但我们很多功能的实现是需要保存状态的。
给 http 添加状态有两种方式:

session + cookie:把状态数据保存到服务端,session id 放到 cookie 里返回,这样每次请求会带上 cookie ,通过 id 来查找到对应的 session。这种方案有 CSRF、分布式 session、跨域的问题。
jwt:把状态保存在 json 格式的 token 里,放到 header 中,需要手动带上,没有 cookie + session 的那些问题,但是也有安全性、性能、没法控制和使用一次就失效的问题。

上面这两种方案都不是完美的,但那些问题也都有解决方案。
软件领域很多情况下都是这样的,某种方案都解决了一些问题,但也相应的带来了一些新的问题。没有银弹,还是要熟悉它们的特点,根据不同的需求灵活选用。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容

  • 本文章出自【码同学软件测试】 对于登录功能大家应该都不陌生,无论我们去逛电商平台还是去看一些文章论坛,都会有一些访...
    码同学软件测试阅读 2,707评论 0 1
  • 前言 无状态的HTTP协议 很久很久之前, Web基本都是文档的浏览而已。既然是浏览, 作为服务器, 不需要记录在...
    IOneStar阅读 2,412评论 0 4
  • 一、什么是JWT JWT(JSON Web Token) 是一个开放标准(RFC 7519),它定义了一种紧凑的、...
    AaronSimon阅读 19,822评论 2 10
  • 一、概念(载录于:http://www.cnblogs.com/EricaMIN1987_IT/p/3837436...
    yuantao123434阅读 8,322评论 6 152
  • Http协议详解 标签(空格分隔): Linux 声明:本片文章非原创,内容来源于博客园作者MIN飞翔的HTTP协...
    Sivin阅读 5,193评论 3 82