假设服务器有一个接口,通过请求这个接口,可以添加一个管理员
但是,不是任何人都有权力做这种操作的
那么服务器如何知道请求接口的人是有权力的呢?
答案是:只有登录过的管理员才能做这种操作
可问题是,客户端和服务器的传输使用的是http协议,http协议是无状态的,什么叫无状态,就是服务器不知道这一次请求的人,跟之前登录请求成功的人是不是同一个人
由于http协议的无状态,服务器忘记了之前的所有请求,它无法确定这一次请求的客户端,就是之前登录成功的那个客户端。
于是,服务器想了一个办法
它按照下面的流程来认证客户端的身份
服务器发扬了认证不认人的优良传统,就可以很轻松的识别身份了。
但是,用户不可能只在一个网站登录,于是客户端会收到来自各个网站的出入证,因此,就要求客户端要有一个类似于卡包的东西,能够具备下面的功能:
能够满足上面所有要求的,就是cookie
cookie类似于一个卡包,专门用于存放各种出入证,并有着一套机制来自动管理这些证件。
卡包内的每一张卡片,称之为一个cookie。
cookie是浏览器中特有的一个概念,它就像浏览器的专属卡包,管理着各个网站的身份信息。
每个cookie就相当于是属于某个网站的一个卡片,它记录了下面的信息:
baidu.com
,表示这个cookie是属于baidu.com
这个网站的/news
,表示这个cookie属于/news
这个路径的。(后续详细解释)当浏览器向服务器发送一个请求的时候,它会瞄一眼自己的卡包,看看哪些卡片适合附带捎给服务器
如果一个cookie同时满足以下条件,则这个cookie会被附带到请求中
baidu.com
,则可以匹配的请求域是baidu.cpm
、www.baidu.com
、dev.baidu.com
等等www.baidu.com
,则只能匹配www.baidu.com
这样的请求域/news
,则可以匹配的请求路径可以是/news
、/news/detail
、/news/a/b/c
等等,但不能匹配/blogs
/
,可以想象,能够匹配所有的路径https
,否则不会发送该cookiehttp
,也可以是https
如果一个cookie满足了上述的所有条件,则浏览器会把它自动加入到这次请求中
具体加入的方式是,浏览器会将符合条件的cookie,自动放置到请求头中,例如,当我在浏览器中访问百度的时候,它在请求头中附带了下面的cookie:
看到打马赛克的地方了吗?这部分就是通过请求头cookie
发送到服务器的,它的格式是键=值; 键=值; 键=值; ...
,每一个键值对就是一个符合条件的cookie。
cookie中包含了重要的身份信息,永远不要把你的cookie泄露给别人!!! 否则,他人就拿到了你的证件,有了证件,就具备了为所欲为的可能性。
由于cookie是保存在浏览器端的,同时,很多证件又是服务器颁发的
所以,cookie的设置有两种模式:
服务器可以通过设置响应头,来告诉浏览器应该如何设置cookie
响应头按照下面的格式设置:
set-cookie: cookie1
set-cookie: cookie2
set-cookie: cookie3
...
通过这种模式,就可以在一次响应中设置多个cookie了,具体设置多少个cookie,设置什么cookie,根据你的需要自行处理
其中,每个cookie的格式如下:
键=值; path=?; domain=?; expire=?; max-age=?; secure; httponly
每个cookie除了键值对是必须要设置的,其他的属性都是可选的,并且顺序不限
当这样的响应头到达客户端后,浏览器会自动的将cookie保存到卡包中,如果卡包中已经存在一模一样的卡片(其他key、path、domain相同),则会自动的覆盖之前的设置。
下面,依次说明每个属性值:
/login
,服务器响应了一个set-cookie: a=1
,浏览器会将该cookie的path设置为请求的路径/login
http://www.baidu.com
,服务器响应了一个set-cookie: a=1
,浏览器会将该cookie的domain设置为请求的域www.baidu.com
baidu.com
,服务器响应的cookie是set-cookie: a=1; domain=demo.com
,这样的域浏览器是不认的。Fri, 17 Apr 2020 09:35:59 GMT
,表示格林威治时间的2020-04-17 09:35:59
,即北京时间的2020-04-17 17:35:59
。当客户端的时间达到这个时间点后,会自动销毁该cookie。max-age
为1000
,浏览器在添加cookie时,会自动设置它的expire
为当前时间加上1000秒,作为过期时间。
https
请求发送。如果不设置,则表示该cookie会随着所有请求发送。下面来一个例子,客户端通过post
请求服务器http://baidu.com/login
,并在消息体中给予了账号和密码,服务器验证登录成功后,在响应头中加入了以下内容:
set-cookie: token=123456; path=/; max-age=3600; httponly
当该响应到达浏览器后,浏览器会创建下面的cookie:
key: token
value: 123456
domain: baidu.com
path: /
expire: 2020-04-17 18:55:00 #假设当前时间是2020-04-17 17:55:00
secure: false #任何请求都可以附带这个cookie,只要满足其他要求
httponly: true #不允许JS获取该cookie
于是,随着浏览器后续对服务器的请求,只要满足要求,这个cookie就会被附带到请求头中传给服务器:
cookie: token=123456; 其他cookie...
现在,还剩下最后一个问题,就是如何删除浏览器的一个cookie呢?
如果要删除浏览器的cookie,只需要让服务器响应一个同样的域、同样的路径、同样的key,只是时间过期的cookie即可
所以,删除cookie其实就是修改cookie
下面的响应会让浏览器删除token
cookie: token=; domain=yuanjin.tech; path=/; max-age=-1
浏览器按照要求修改了cookie后,会发现cookie已经过期,于是自然就会删除了。
无论是修改还是删除,都要注意cookie的域和路径,因为完全可能存在域或路径不同,但key相同的cookie
因此无法仅通过key确定是哪一个cookie
既然cookie是存放在浏览器端的,所以浏览器向JS公开了接口,让其可以设置cookie
document.cookie = "键=值; path=?; domain=?; expire=?; max-age=?; secure";
可以看出,在客户端设置cookie,和服务器设置cookie的格式一样,只是有下面的不同
const express = require('express')
const app = express()
const port = 3000
app.get('/', (req, res) => {
res.header['set-cookie'] = 'id=a3fWa;';
res.send();
})
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})
var express = require('express')
var cookieParser = require('cookie-parser')
var app = express()
app.use(cookieParser())
app.get('/', function (req, res) {
// Cookies that have not been signed
console.log('Cookies: ', req.cookies)
// Cookies that have been signed
console.log('Signed Cookies: ', req.signedCookies)
})
app.listen(8080)
以上,就是cookie原理部分的内容。
如果把它用于登录场景,就是如下的流程:
登录请求
后续请求