cookie
cookie 的由来
客户端和服务器的传输使用 HTTP 协议,HTTP 协议是无状态的。
无状态,就是每一次请求响应都是独立的,服务器不知道这一次请求,和之前某次请求是不是同一个人。
有什么办法能够让服务器标识出请求的身份呢?
服务器按照下面的流程来认证客户端的身份:
- 客户端上次请求后(如:登录成功),服务器会给客户端一个出入证
- 后续客户端的每次请求,都必须要附带这个出入证
服务器发扬了认证不认人的优良传统,就可以很轻松的识别身份了。
但是,用户不可能只在一个网站登录,于是客户端会收到来自各个网站的出入证,因此,就要求客户端要有一个类似于卡包的东西,能够具备下面的功能:
- 能够存放多个出入证:这些出入证来自不同的网站,也可能是一个网站有多个出入证,分别用于出入不同的地方
- 能够自动出示出入证:客户端在访问不同的网站时,能够自动的把对应的出入证附带请求发送出去。
- 正确的出示出入证:客户端不能将肯德基的出入证发送给麦当劳。
- 管理出入证的有效期:客户端要能够自动的发现那些已经过期的出入证,并把它从卡包内移除。
- 保证出入证安全:对出入证有一定的安全保障措施。
能够满足上面所有要求的,就是 cookie,cookie 类似于一个卡包,专门用于存放各种出入证,并有着一套机制来自动管理这些证件。
cookie 的组成
cookie 是浏览器中特有的一个概念,它就像浏览器的专属卡包,管理着各个网站的身份信息。
每个 cookie 就相当于是属于某个网站的一个卡片,它记录了下面的信息:
key
:键value
:值domain
:域,表达这个 cookie 是属于哪个网站的- 用于判断某个请求是否携带该 cookie
- 如果不设置,浏览器会自动将其设置为当前的请求域
- 如果服务器响应了一个无效的域(响应的域连服务器的根域都不一样),浏览器会忽略
path
:路径,表达这个 cookie 是属于该网站的哪个基路径的- 用于判断某个请求是否携带该 cookie
- 如果不设置,浏览器会将其自动设置为当前请求的路径
secure
:是否使用安全传输(HTTPS)- 用于判断某个请求是否携带该 cookie
expire
:过期时间,表示该 cookie 在什么时候过期,绝对时间- 是一个有效的 GMT 时间(格林威治标准时间)字符串,如
Fri, 17 Apr 2020 09:35:59 GMT
- 是一个有效的 GMT 时间(格林威治标准时间)字符串,如
max-age
:过期时间,表示该 cookie 在什么时候过期,相对于当前时间expire
和max-age
通常仅设置一个即可- 如果
expire
和max-age
都没有,则表示会话结束后过期(对于大部分浏览器而言,关闭所有浏览器窗口意味着会话结束)
httponly
:设置 cookie 是否仅能用于传输。如果设置了该值,表示该 cookie 仅能用于传输,而不允许在客户端通过 JS 读写- 这对防止跨站脚本攻击(XSS)会很有用
sameSite
:Strict
:严格,请求来自设置 cookie 的站点才会携带该 cookieLax
:宽松,cookie 不会在跨站请求中被发送,如:加载图像或框架(frame)的请求。但 cookie 在用户从外部站点导航到源站时,cookie 也会被发送(例如,访问一个链接)。这是SameSite
属性未被设置时的默认值。None
:无,浏览器在跨站和同站请求中均会发送 cookie。在设置这一属性值时,必须同时设置Secure
属性,就像这样:SameSite=None; Secure
。如果未设置Secure
,则会出现 以下错误。- 用于判断某个请求是否携带该 cookie
- 可以在一定程度上防范跨站请求伪造攻击(CSRF)
SameSite=None
但未设置 Secure
的报错:
Cookie "myCookie" rejected because it has the "SameSite=None" attribute but is missing the "secure" attribute.
This Set-Cookie was blocked because it had the "SameSite=None" attribute but did not have the "Secure" attribute, which is required in order to use "SameSite=None".
当浏览器向服务器发送一个请求的时候,它会瞄一眼自己的卡包,看看哪些卡片适合附带捎给服务器
如果一个 cookie 同时满足以下条件,则这个 cookie 会被附带到请求中:
- cookie 没有过期
- cookie 中的域和这次请求的域是匹配的
- 比如 cookie 中的域是
yuanjin.tech
,则可以匹配的请求域是yuanjin.tech
、www.yuanjin.tech
、blogs.yuanjin.tech
等 - 比如 cookie 中的域是
www.yuanjin.tech
,则只能匹配www.yuanjin.tech
这样的请求域 - cookie 是不在乎端口的,只要域匹配即可
- 比如 cookie 中的域是
- cookie 中的 path 和这次请求的 path 是匹配的
- 比如 cookie 中的 path 是
/news
,则可以匹配的请求路径可以是/news
、/news/detail
、/news/a/b/c
等等,但不能匹配/blogs
- 比如 cookie 中的 path 是
- 验证 cookie 的安全传输
- 如果 cookie 的
secure
属性是true
,则请求协议必须是 HTTPS,否则不会发送该 cookie - 如果 cookie 的
secure
属性是false
,则请求协议可以是 HTTP,也可以是 HTTPS
- 如果 cookie 的
- 验证
sameSite
规则是否符合
具体加入的方式是,浏览器会将符合条件的 cookie,自动放置到 Cookie
请求头中,值的格式是 key1=value1; key2=value2; ...
,每一个键值对就是一个符合条件的 cookie。
cookie 中包含了重要的身份信息,永远不要把你的 cookie 泄露给别人!否则,他人就拿到了你的证件,就具备了为所欲为的可能性。
如何设置 cookie
由于 cookie 是保存在浏览器端的,同时,很多证件又是服务器颁发的
所以,cookie 的设置有两种模式:
- 服务器响应:这种模式是非常普遍的,当服务器决定给客户端颁发一个证件时,它会在响应的消息中包含 cookie,浏览器会自动的把 cookie 保存到卡包中
- 客户端自行设置:这种模式少见一些,不过也有可能会发生,比如用户关闭了某个广告,并选择了「以后不要再弹出」,此时就可以把这种小信息直接通过浏览器的 JS 代码保存到 cookie 中。后续请求服务器时,服务器会看到客户端不想要再次弹出广告的 cookie,于是就不会再发送广告过来了。
服务器端设置 cookie
服务器可以通过设置响应头,来告诉浏览器应该如何设置 cookie,响应头按照下面的格式设置:
Set-Cookie: cookie1
Set-Cookie: cookie2
通过这种模式,就可以在一次响应中设置多个 cookie 了。其中,每个 cookie 的格式如下:
key=value; path=?; domain=?; expire=?; max-age=?; secure; httponly
每个 cookie 除了键值对是必须要设置的,其他的属性都是可选的,并且顺序不限。
当这样的响应头到达客户端后,浏览器会自动的将 cookie 保存到卡包中,如果卡包中已经存在一模一样的卡片(其他信息,如 path、domain 相同),则会自动的覆盖之前的设置。
如何删除浏览器的一个 cookie 呢?
如果要删除浏览器的 cookie,只需要让服务器响应一个同样的域、同样的路径、同样的 key,只是时间已经过期的 cookie 即可:
set-cookie: token=; domain=yuanjin.tech; path=/; max-age=-1
所以,删除 cookie 其实就是修改 cookie 让其过期。浏览器按照要求修改了 cookie 后,会发现 cookie 已经过期,于是自然就会删除了。
注意
无论是修改还是删除,都要注意 cookie 的域和路径,因为完全可能存在域或路径不同,但 key 相同的 cookie,因此无法仅通过 key 确定是哪一个 cookie
客户端设置 cookie
既然 cookie 是存放在浏览器端的,所以浏览器向 JS 公开了接口,让其可以设置 cookie:
document.cookie = "key=value; path=?; domain=?; expire=?; max-age=?; secure";
可以看出,在客户端设置 cookie,和服务器设置 cookie 的格式一样,只是有下面的不同:
- 没有
httponly
path
的默认值- 在服务器端设置 cookie 时,如果没有写 path,使用的是请求的 path。
- 而在客户端设置 cookie 时,也许根本没有请求发生。因此,path 在客户端设置时的默认值是当前网页的 path
domain
的默认值,和 path 同理- 服务器端设置的默认值是服务器的 domain
- 客户端设置时的默认值是当前网页的 domain
cookie 的弊端
- 安全性:不能在本地存储敏感信息、CSRF 攻击等
- 浏览器独有:微信小程序、手机 APP 等没有该技术,一套后端,多套前端的技术实践需要更多的工作
session
原理
cookie 的数据都是存到客户端的,很不安全,session 基于 cookie 实现。
其原理就是:服务器开辟一块空间(可以是狭义的 session 内存空间,也可以是 Redis 等缓存数据库),将该用户相关的数据存储到该空间,给此空间分配一个 session id(sid
)并通过 cookie 返回给客户端,下次该客户端请求服务器就会携带 sid
,服务器可以通过 sid
获取空间中存储的内容
消除 session
session 会占用服务器资源,会话结束就要将其销毁,通常有两种方式:
- 过期时间:当客户端长时间没有传递 session id 过来时,服务器可以在过期时间之后自动清除 session
- 一般都会设置,比较好的解决方案
- 客户端主动通知:可以使用 JS 监听客户端页面关闭或其他退出操作,然后通知服务器清除 session
- 客户端主动通知有不确定性(如客户端直接关闭浏览器或关闭电脑,本地 JS 可能监听不到)
面试题
cookie、sessionStorage、localStorage 的区别
cookie、sessionStorage、localStorage 都是保存本地数据的方式。
cookie 用于标识请求端身份信息
- cookie 兼容性较好,所有浏览器均支持。
- 浏览器针对 cookie 会有一些默认行为,比如当响应头中出现
set-cookie
字段时,浏览器会自动保存 cookie 的值;浏览器发送请求时,会附带匹配的 cookie 到请求头中。这些默认行为,使得 cookie 长期以来担任着维持登录状态的责任。 - 与此同时,也正是因为浏览器的默认行为,给了恶意攻击者可乘之机,CSRF 攻击就是一个典型的利用 cookie 的攻击方式。虽然 cookie 不断的改进,但前端仍然需要时刻注意 cookie 的安全问题
- 不要在 cookie 中存放敏感信息,敏感信息应在服务器或数据库中,即使是数据库也不能明文存储,如密码需要进行二次加密、敏感数据需要脱敏处理
- cookie 的大小是有限制的,一般浏览器会限制同一个域下的 cookie 总量不超过 4KB
- cookie 会与服务器的 domain、path 关联,端口任意
HTML5 新增了 sessionStorage 和 localStorage,前者用于保存会话级别的数据,后者用于更持久的保存数据。
- 浏览器针对它们没有任何默认行为,这样一来,就把保存数据、读取数据的工作交给了前端开发者,这就让恶意攻击者难以针对登录状态进行攻击。
- sessionStorage 和 localStorage 拥有更大的空间,多数浏览器一般在 2.5MB~10MB
- sessionStorage 和 localStorage 只与客户端的域关联,包括协议、域名、端口
cookie、session、token 的区别
cookie 和 session:
- cookie 的数据保存在浏览器端;session 的数据保存在服务器
- cookie 的存储空间有限;session 的存储空间不限
- cookie 只能保存字符串;session 可以保存任何类型的数据
- cookie 中的数据容易被获取;session 中的数据难以获取
- session 基于 cookie,cookie 基于浏览器
token(如:JWT):
- token 将数据存到浏览器端,服务端通过验证签名防止篡改和伪造
- token 不基于浏览器实现
- token 不能放敏感信息,当然可以基于 token 实现 session 就可以在服务器放敏感信息了