node.js极速入门课程:进入学习
目前绝大多数的系统都少不了登录验证的功能,这主要是为了保存用户的状态,以此来限制用户的各种行为,从而方便有效的控制用户的权限。比如一个用户登陆微博,发布、关注、评论的操作都应是在登录后的用户状态下进行的。
实现登录验证的功能主要有Cookie&Session
、JWT
两种方式,这一节我们将先对 Cookie&Session
的工作原理 做详细的介绍,在之后的文章中会陆续对JWT
,以及如何使用Cookie&Session
和JWT
来完善前几节我们搭建的简易用户管理系统进行讲解。【相关教程推荐:nodejs视频教程】
1️⃣ Cookie&Session
我们知道,HTTP 是无状态的。也就是说,HTTP 请求方和响应方间无法维护状态,都是一次性的,它不知道前后的请求都发生了什么。但有的场景下,我们需要维护状态。最典型的,一个用户登陆微博,发布、关注、评论,都应是在登录后的用户状态下的。
这个时候就可以引入Cookie
与Session
来保存用户的登录状态。
本篇文章主要介绍使用
Cookie-Session
来做登录验证的工作原理,关于Cookie
与Session
的详细介绍可查阅这位大佬的文章:Cookie和Session详解
为什么不单独使用Cookie?
Cookie
是存放在浏览器中的,可以在浏览器中打开控制台
,选择应用
,找到存储
中的Cookie
进行查看:
当客户端向服务端发送网络请求时浏览器会自动将Cookie
添加到请求头中,这样服务端就能获取这个Cookie
,如下:
知道了这个原理后,我们就可以想到,如果在用户登录系统时:客户端由用户的部分登录信息(比如username
、id
等)生成一个Cookie
存放到浏览器中,那么在这之后的每一次网络请求都会自动携带上该Cookie
。
之后让服务端根据请求中是否携带Cookie
并且携带的Cookie
中是否存在有效的username
、id
来判断用户是否已经登录过了,这样一来用户的登录状态不就被保存下来了吗。
回到上面我们提到的微博的例子,按照这种过程来说,当用户登录过后Cookie
已经被保存,这时当用户进行发布、关注、评论等需要登录才能使用的操作时我们就能提前判断是否存在Cookie
,如果存在并且Cookie
中含有该用户的id
,那么我们就可以允许该用户的这些操作(这些操作一般都是需要用户的id
的,这时就可以从Cookie
中进行获取)。相反的,如果Cookie
不存在或者Cookie
无效,那么就禁止该用户的这些操作。
说到这,你可能会问:既然一个Cookie
就能实现我们想要的效果,那为何还要使用Session
呢?
这是因为 Cookie
很容易被伪造! ,如果我们知道了Cookie
中存放的信息是username
和id
(就算不知道,也可以在登录后的网络请求的请求体中找到Cookie
),那么我们完全可以在不登录的情况下手动向浏览器存储一个伪造的Cookie
:
说到这,你应该就能明白为什么不能单独使用Cookie
了吧。
Session是如何与Cookie结合的?
Session
其实是基于Cookie
实现的,并且Session
存储在服务端的内存或者数据库中。
当用户登录成功时,使用Cookie&Session
的登录验证会进行以下操作:
-
由服务端生成
Session
与SessionId
;Session
一般是根据用户登录的信息,如用户名、id
等进行生成。
如果把Session
比作是一把锁,那么SessionId
就相当于是这把锁的钥匙。 -
服务端将
Session
存储到内存或者数据库中; -
服务端将
SessionId
存放到请求的响应头(response
对象)中的Set-Cookie
字段中发送给客户端; -
客户端收到
Set-Cookie
后会自动将Set-Cookie
的值(也就是SessionId
)存放到Cookie
中; -
之后的每次网络请求都会自动带上
Cookie
,也就是带上这个SessionId
; -
服务端收到后续请求时获取请求上的
Cookie
,也就是获取到了SessionId
,然后通过SessionId
查询并校验服务端存储的Session
,若校验成功说明这个SessionId
有效则通过此次请求,反之则阻止此次请求。
图示:
2️⃣ Cookie&Session的缺陷
存储问题
为了保存用户的登录状态,我们需要为每一位登录的用户生成并存储Session
,这势必就会造成以下问题:
- 如果
Session
存放到内存中,那么当服务端重启时,这些内存中的Session
都将被清除,那么所有用户的登录状态都将会过期,并且当用户量较大时,过多的内存占用也势必会影响服务端的性能。 - 如果
Session
存放到数据库中,虽然能够解决因服务端重启造成用户登录状态过期的问题,但当用户量较大时,对于这个数据库的维护也会变得相对困难。 - 如果前端页面中调用的接口来自两个服务器(也就是两套数据库),为了实现
Session
在两个服务器间共享通常会将Session
存放到一个单独的数据库中,这样就使得整个项目变得更为复杂也更加难以维护。
CSRF问题
CSRF全称为 Cross-site request forgery 即 跨站请求伪造,使用Cookie
进行验证的网站都会面临或大或小的CSRF
威胁,我们以一个银行网站的例子来介绍CSRF的攻击原理:
假如一家银行网站A
的登录验证采用的是Cookie&Session
,并且该网站上用以运行转账操作的Api地址
为:http://www.grillbankapi.com/?account=AccoutName&amount=1000
api
参数:account
代表账户名,amount
代表转账金额。
那么,一个恶意攻击者可以在另一个网站B
上放置如下代码:
<img src="http://www.grillbankapi.com/?account=Ailjx&amount=1000">
注意:
img
标签的src
是网站A
转账操作的api地址
,并且参数account
为Ailjx,amount
为1000,也就是说这个api地址
相当于是账户名为 Ailjx 转账1000 时调用的api
。
如果有账户名为 Ailjx 的用户刚访问过网站A
不久,登录信息尚未过期(网站A
的Cookie
存在且有效)。
那么当 Ailjx 访问了这个恶意网站B
时,上面的img
标签将被加载,浏览器就会自动请求img
标签的src
路由,也就是请求http://www.grillbankapi.com/?account=Ailjx&amount=1000
(我们将这个请求记为请求Q
),并且因为Cookie
存放在浏览器中且浏览器发送请求时会自动带上Cookie
,所以请求Q
上就会自动携带 Ailjx 在网站A
上的Cookie
凭证,结果就是这个 请求Q
将会被通过,那么 Ailjx 就会损失1000资金。
这种恶意的网址可以有很多种形式,藏身于网页中的许多地方。 此外,攻击者也不需要控制放置恶意网址的网站。例如他可以将这种地址藏在论坛,博客等任何用户生成内容的网站中。这意味着如果服务端没有合适的防御措施的话,用户即使访问熟悉的可信网站也有受攻击的危险。
透过例子能够看出,攻击者并不能通过CSRF攻击来直接获取用户的账户控制权,也不能直接窃取用户的任何信息。他们能做到的,是欺骗用户浏览器,让其以用户的名义运行操作。
这些就是使用Cookie&Session
来做登录验证的问题所在,那么我们如何解决这些问题呢?这就需要引入JWT的概念,使用token
来做登录验证,这些我们将在之后的文章中进行讲解。