
文章目录
- 1 cookie
- 2 session
- 3 Cookie 和 Session 的区别
- 4 Token(JWT)
- 5 cookie的18个问题
- 5.1 1. cookie存储在哪里?
- 5.2 2. cookie是如何工作的?
- 5.3 3. cookie和session的区别?
- 5.4 4. 什么是session级别的cookie?
- 5.5 5. cookie可以被谁来操作?
- 5.6 6. cookie各属性详解
- 5.7 7. js和服务端对cookie的操作有什么不同?
- 5.8 8. js如何操作cookie
- 5.9 9. 服务端如何读写cookie
- 5.10 10. Cookie 大小和数量的限制
- 5.11 11. 查看浏览器是否打开 Cookie 功能
- 5.12 12. cookie 的共享策略是什么
- 5.13 13. 跨域请求(CORS)中的cookie
- 5.14 14. cookie的编码
- 5.15 15. cookie与web安全
- 5.16 16. 不同的浏览器共享cookie吗?
- 5.17 17. cookie和localStorage/sessionStorage的差异
- 5.18 18. 哪些信息适合放到cookie中
- 6 参考文献
很久之前, Web基本都是文档的浏览而已。既然是浏览, 作为服务器, 不需要记录在某一段时间里都浏览了什么文档, 每次请求都是一个新的HTTP协议,就是请求加响应。不用记录谁刚刚发了HTTP请求, 每次请求都是全新的。
随着交互式Web应用的兴起, 像在线购物网站,需要登录的网站等,马上面临一个问题,就是要管理回话或者叫状态管理,记住那些人登录过系统,哪些人往自己的购物车中放商品,也就是说我必须把每个人区分开。那时的办法是在请求的页面中插入一个token,并且在下一次请求中将这个token返回(至服务器)。这就需要在form中插入一个包含token的隐藏表单域,或着在URL的qurey字符串中传递该token。这两种办法都强调手工操作并且极易出错。
cookie就是存储在用户主机浏览器中的一小段文本文件。Cookies 是纯文本形式,它们不包含任何可招行代码。
cookie 是一个非常具体的东西,指的就是浏览器里面能永久存储的一种数据。跟服务器没啥关系,仅仅是浏览器实现的一种数据存储功能。
客户端请求服务器时,如果服务器需要记录该用户状态,服务器生成cookie,并通过response Headers向客户端浏览器发送Cookie,浏览器把cookie以KV形式存储到某个目录下的文本文件中,下一次请求同一网站时会把该cookie发送给服务器。由于cookie是存在客户端上的,所以浏览器加入了一些限制确保cookie不会被恶意使用,同时不会占据太多磁盘空间。所以每个域的cookie数量是有限制的。
客户端设置
document.cookie = "name=xiaoming; age=12 "
客户端可以设置cookie的一些属性: expires, domain, path, secure(只有在https协议的网页中, 客户端设置secure类型cookie才能生效), 但无法设置httpOnly选项
设置cookie => cookie被自动添加到request header中 => 服务端接收到cookie
服务端设置
不管你是请求一个资源文件(如html/js/css/图片), 还是发送一个ajax请求, 服务端都会返回response.而response header中有一项叫set-cookie, 是服务端专门用来设置cookie的;
- 一个set-cookie只能设置一个cookie, 当你想设置多个, 需要添加同样多的set-cookie
- 服务端可以设置cookie的所有选项: expires, domain, path, secure, HttpOnly
HTML5提供了两种本地存储的方式 sessionStorage 和 localStorage;
- HTTP 是无状态的协议(对于事务处理没有记忆能力,每次客户端和服务端会话完成时,服务端不会保存任何会话信息):每个请求都是完全独立的,服务端无法确认当前访问者的身份信息,无法分辨上一次的请求发送者和这一次的发送者是不是同一个人。所以服务器与浏览器为了进行会话跟踪(知道是谁在访问我),就必须主动的去维护一个状态,这个状态用于告知服务端前后两个请求是否来自同一浏览器。而这个状态需要通过 cookie 或者 session 去实现。
- cookie 存储在客户端: cookie 是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。
- cookie 是不可跨域的: 每个 cookie 都会绑定单一的域名,无法在别的域名下获取使用,一级域名和二级域名之间是允许共享使用的(靠的是 domain)
- 因为存储在客户端,容易被客户端篡改,使用前需要验证合法性,所以,不要存储敏感数据,比如用户密码,账户余额;使用 httpOnly 在一定程度上提高安全性
- 尽量减少 cookie 的体积,能存储的数据量不能超过 4kb;一个浏览器针对一个网站最多存 20 个Cookie,浏览器一般只允许存放 300 个Cookie
- 设置正确的 domain 和 path,减少数据传输
- cookie 无法跨域(每个 cookie 都会绑定单一的域名,无法在别的域名下获取使用)
- 移动端对 cookie 的支持不是很好,而 session 需要基于 cookie 实现,所以移动端常用的是 token
session
什么是session
session从字面上讲,就是会话。这个就类似你和一个人交谈,你怎么知道当时和你交谈的是张三而不是李四呢?对方肯定有某种特征(长相等)表明他是张三;
session也是类似的道理,服务器要知道当前请求发给自己的是谁。为了做这种区分,服务器就是要给每个客户端分配不同的”身份标识”,然后客户端每次向服务器发请求的时候,都带上这个”身份标识“,服务器就知道这个请求来自与谁了。
至于客户端怎么保存这个”身份标识“,可以有很多方式,对于浏览器客户端,大家都采用cookie的方式。
- session 是另一种记录服务器和客户端会话状态的机制,即告诉服务端前后两个请求是否来自同一个客户端(浏览器),知道谁在访问我。因为http本身是无状态协议,这样,无法确定你的本次请求和上次请求是不是你发送的。如果要进行类似论坛登陆相关的操作,就实现不了了。
- session 是基于 cookie 实现的,session 存储在服务器端,sessionId 会被存储到客户端的cookie 中。ps:还有一种是浏览器禁用了cookie或不支持cookie,这种可以通过URL重写的方式发到服务器;
- 用户向服务器发送用户名和密码
- 服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色, 登陆时间等;
- 服务器向用户返回此 Session 的唯一标识信息 SessionID , 写入用户的cookie
- 浏览器接收到服务器返回的 SessionID 信息后,会将此信息存入到 Cookie 中,同时 Cookie 记录此 SessionID 属于哪个域名 。
- 用户随后的每一次请求, 请求会自动判断此域名下是否存在 Cookie 信息,如果存在自动将 Cookie 信息也发送给服务端
- 服务端会从 Cookie 中获取 SessionID,再根据 SessionID 查找对应的 Session 信息,由此得知用户的身份, 如果没有找到说明用户没有登录或者登录失效,如果找到 Session 证明用户已经登录可执行后面操作。
使用 session 时需要考虑的问题
- 将 session 存储在服务器里面,当用户同时在线量比较多时,这些 session 会占据较多的内存,需要在服务端定期的去清理过期的 session
当网站采用集群部署的时候,会遇到多台 web 服务器之间如何做 session 共享的问题。因为 session 是由单个服务器创建的,但是处理用户请求的服务器不一定是那个创建 session 的服务器,那么该服务器就无法拿到之前已经放入到 session 中的登录凭证之类的信息了。解决方案:写客户端cookie的方式、服务器之间session数据同步、用mysql数据库共享session数据。
当多个应用要共享 session 时,除了以上问题,还会遇到跨域问题,因为不同的应用可能部署的主机不一样,需要在各个应用做好 cookie 跨域的处理。
sessionId 是存储在 cookie 中的,假如浏览器禁止 cookie 或不支持 cookie 怎么办? 一般会把 sessionId 跟在 url 参数后面即重写 url,所以 session 不一定非得需要靠 cookie 实现
移动端对 cookie 的支持不是很好,而 session 需要基于 cookie 实现,所以移动端常用的是 token
安全性:Session 是存储在服务器端的,Cookie 是存储在客户端的。所以 Session 相比 Cookie 安全
存取值的类型不同:Cookie 只支持存字符串数据,想要设置其他类型的数据,需要将其转换成字符串,Session 可以存任意数据类型。
有效期不同: Cookie 可设置为长时间保持,比如我们经常使用的默认登录功能,Session 一般失效时间较短,客户端关闭(默认情况下)或者 Session 超时都会失效。
存储大小不同: 单个 Cookie 保存的数据不能超过 4K,Session 可存储数据远高于 Cookie,但是当访问量过多,会占用过多的服务器资源。
Token(JWT)
请求方输入自己的用户名,密码,然后 server 据此生成 token,客户端拿到 token 后会保存到本地(token存储在浏览器端),之后向 server 请求时在请求头带上此 token 即可(server有校验机制,检验token合法性,同时server通过token中携带的uid确定是谁在访问它)。
过程
- 用户通过用户名和密码发送请求
- 程序验证
- 程序返回一个签名的token给客户端
- 客户端储存token, 并且每次用每次发送请求
- 服务端验证Token并返回数据
这个方式的技术其实很早就已经有很多实现了,而且还有现成的标准可用,这个标准就是JWT;
结构
- header:指定了签名算法
- payload:可以指定用户 id,过期时间等非敏感数据
- Signature: 签名,server 根据 header 知道它该用哪种签名算法,再用密钥根据此签名算法对 head + payload 生成签名,这样一个 token 就生成了。
使用方式
客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage。此后,客户端每次与服务端通信,都要带上这个JWT。你可以把它放在Cookie里面自动发送,但是这样不能跨域,所以更好的做法是放在HTTP请求的头信息 Authorization 字段里面。
Authorization: Bearer <token>
另一种做法是, 跨域的时候, JWT就放在POST请求的数据体里。
优点
- 只要 server 保证密钥不泄露,那么生成的 token 就是安全的,因为如果伪造 token 的话在签名验证环节是无法通过的,就此即可判定 token 非法。节省了服务器空间。
- 支持跨域访问,cookie不支持, Cookie 跨站是不能共享的,这样的话如果你要实现多应用(多系统)的单点登录(SSO)。
- token是无状态的,可以在多个服务器间共享。
缺点
- token 一旦由 server 生成,它就是有效的,直到过期,无法让 token 失效,存储在客户端本地,容易被获取。
长度过长,超过cookie上限。
使用 token 时需要考虑的问题
如果你认为用数据库来存储 token 会导致查询时间太长,可以选择放在内存当中。比如 redis 很适合你对 token 查询的需求。
token 完全由应用管理,所以它可以避开同源策略
token 可以避免 CSRF 攻击(因为不需要 cookie 了)
移动端对 cookie 的支持不是很好,而 session 需要基于 cookie 实现,所以移动端常用的是 token
使用 JWT 时需要考虑的问题
因为 JWT 并不依赖 Cookie 的,所以你可以使用任何域名提供你的 API 服务而不需要担心跨域资源共享问题(CORS)
JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。
JWT 不加密的情况下,不能将秘密数据写入 JWT。
JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。
JWT 最大的优势是服务器不再需要存储 Session,使得服务器认证鉴权业务可以方便扩展。但这也是 JWT 最大的缺点:由于服务器不需要存储 Session 状态,因此使用过程中无法废弃某个 Token 或者更改 Token 的权限。也就是说一旦 JWT 签发了,到期之前就会始终有效,除非服务器部署额外的逻辑。
JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。
JWT 适合一次性的命令认证,颁发一个有效期极短的 JWT,即使暴露了危险也很小,由于每次操作都会生成新的 JWT,因此也没必要保存 JWT,真正实现无状态。
为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。
cookie一般是被浏览器以txt的形式存储在电脑硬盘中,供该浏览器进行读、写操作。
request
当浏览器发起一个请求时,浏览器会自动检查是否有相应的cookie,如果有则将cookie添加到Request Headers的Cookie字段中(cookie字段是很多name=value以分号分隔的字符串)。
response
当服务端需要设置cookie时,在http请求的Response Headers中添加Set-Cookie字段,浏览器接收到之后会自动解析识别,将cookie种下。
- 存储位置不同: cookie数据存放在客户的浏览器上,session数据放在服务器上。
- 存储大小不同: 一般单个cookie保存的数据不能超过4k, 单个域名最多保存30个cookie(不同浏览器有差异);session则无大小和数量限制。
session级别的cookie只针对当前会话存在,会话终止则cookie消失。
当cookie没有设置expires的时候,该cookie只会在网页会话期间存在,当浏览器退出的时候,该cookie就会消失。
浏览器中的表现为Expires/Max-Age的内容为Session。
服务端和js都可以读/写cookie。
- Name: cookie名
Value: cookie值。
Domain: cookie的域名。如果设成.example.com,那么子域名a.example.com和b.example.com,都可以使用.example.com的cookie;反之则不可以。
Path: 允许读取cookie的url路径,一般设置为/。
Expires: cookie过期时间。不设置,则为Session会话期,页面退出时cookie失效。
HttpOnly: 设置为true时,只有http能读取。js只能读取未设置HttpOnly的cookie。
Secure: 标记为Secure的cookie,只有https的请求可以携带。
SameSite: 限制第三方url是否可以携带cookie。有3个值:Strict/Lax(默认)/None。(chrome51新增属性,chrome80+强制执行)
- Strict: 仅允许发送同站点请求的的cookie
- Lax: 允许部分第三方请求携带cookie,即导航到目标网址的get请求。包括超链接 ,预加载和get表单
三种形式发送cookie
* None: 任意发送cookie,设置为None,(需要同时设置Secure,也就是说网站必须采用https)
- Priority:优先级,chrome的提案(firefox不支持),定义了三种优先级,Low/Medium/High,当cookie数量超出时,低优先级的cookie会被优先清除。
cookie有一个属性是HttpOnly,HttpOnly被设置时,表明该cookie只能被http请求读取,不能被js读取,具体的表现是:document.cookie读取到的内容不包含设置了HttpOnly的cookie。
js操作读/写cookie的api是document.cookie。
console.log(document.cookie);
// cna=8fDUF573wEQCAWoLI8izIL6X; xlly_s=1; t=4a2bcb7ef27d32dad6872f9124694e19; _tb_token_=e3e11308ee6fe; hng=CN%7Czh-CN%7CCNY%7C156; thw=cn; v=0;
读取后的cookie是一个字符串,包含了所有cookie的name和value(用分号分隔),需要我们自行将cookie解析出来。
document.cookie = 'uid=123;expires=Mon Jan 04 2022 17:42:40 GMT;path=/;secure;'
document.cookie = 'uid=123;expires=Mon Jan 04 2022 17:42:40 GMT;path=/caikuai;domain=edu.360.cn;secure;samesite=lax'
一次只能写一个cookie,想要写多个cookie需要操作多次。
1. expires默认为session级别。
2. path默认为当前url的路径。
3. domain默认为当前访问域名。
4. samesite默认为Lax。
5. secure默认为false(即http)。
只需要将一个已经存在的cookie名字过期时间设置为过去的时间即可。
document.cookie = 'uid=dkfywqkrhkwehf23;expires=' + new Date(0) + ';path=/;secure;'
重新赋值就好,旧值会覆盖新值。
注意:需要保证path和domain这两个值不变,否则会添加一个新的cookie。
Q:如果要设置多个cookie怎么办?
A:document.cookie一次只能设置一个cookie,需要操作多次document.cookie。
第2节“cookie是如何工作的”告诉我们,服务端是可以读和写cookie的,从图中我们可以看到,写cookie时,一条Set-Cookie写入一条cookie。读cookie时,所有的cookie信息都被放进了cookie字段中。
以express使用为例:
res.setHeader('Set-Cookie', ['uid=123;maxAge: 900000; httpOnly: true']);
// 或者
res.cookie("uid",'123',{maxAge: 900000, httpOnly: true});
console.log(req.getHeader('Cookie')); // 拿到所有cookie
// 或者
console.log(req.cookie.name);
不同的浏览器对Cookie的大小和数量的限制不一样,一般,单个域名下设置的Cookie不应超过30个,且每个Cookie的大小不应超过4kb,超过以后,Cookie将会被忽略,不会被设置。
window.navigator.cookieEnabled // true
domain和path共同决定了cookie可以被哪些url访问。
访问一个url时,如果url的host与domain一致或者是domain的子域名,并且url的路径与path部分匹配,那么cookie才可以被读取。
假如cookie如上图所示,那么一个url与可读取的cookie对应关系为:
url | uid1 | uid2 | uid3 |
---|---|---|---|
https://edu.360.cn/caikuai/ | √ | √ | √ |
https://edu.360.cn/ | √ | √ | x |
https://360.cn/article/123.html | x | √ | x |
首先cookie的SameSite需要设置为None。
其次对于将Access-Control-Allow-Credentials设置为true的接口(表示允许发送cookie),需要我们在发送ajax请求时,将withCredentials属性设为true。
document.cookie = 'uid=123;expires=Mon Jan 04 2022 17:42:40 GMT;path=/caikuai;domain=edu.360.cn;secure;samesite=lax'
从document.cookie的赋值中我们可以看到,存在等于号、冒号、分号、空格、逗号、斜杠等特殊字符,因此当cookie的key和value中存在这几种符号时,需要对其进行编码,一般会用encodeURIComponent/decodeURIComponent进行操作。
Q: 为什么不使用escape和encodeURI进行编码呢?
A: 因为相比这两个api,encodeURIComponent可以将更多的字符进行编码,比如”/”。
XSS漏洞的原理是,由于未对用户提交的表单数据或者url参数等数据做处理就显示在了页面上,导致用户提交的内容在页面上被做为html解析执行。
常规方案:对特殊字符进行处理,如”<“和”>”等进行转义。
cookie的应对方案:对于用户利用script脚本来采集cookie信息,我们可以将重要的cookie信息设置为HttpOnly来避免cookie被js采集。
CSRF,中文名叫跨站请求伪造,原理是,用户登陆了A网站,然后因为某些原因访问了B网站(比如跳转等),B网站直接发送一个A网站的请求进行一些危险操作,由于A网站处于登陆状态,就发生了CSRF攻击(核心就是利用了cookie信息可以被跨站携带)!
常规方案:采用验证码或token等。
cookie的应对方案:由于CSRF攻击核心就是利用了cookie信息可以被跨站携带,那么我们可以对核心cookie的SameSite设置为Strict或Lax来避免。
不同的浏览器互相不通信,彼此是独立的cookie。因此,我们在一个浏览器上登陆了某个网站,换到另一个浏览器上的时候需要重新登陆。
特性 | Cookie | localStorage | sessionStorage |
---|---|---|---|
操作者 | 服务器和js | js | js |
数据的生命期 | 可设置失效时间 | 除非被清除,否则永久保存 | 仅在当前会话下有效,关闭页面或浏览器后被清除 |
存放数据大小 | 4K左右 | 一般为5M | 一般为5M |
与服务器端通信 | 每次都会携带在HTTP头中每次都会携带在HTTP头中 | 不与服务器通信 | 不与服务器通信 |
易用性 | 原生的Cookie接口不友好,需要封装 | 接口易用 | 接口易用 |
cookie的增多无疑会加重网络请求的开销,而且每次请求都会将cookie完整的带上,因此对于那些“每次请求都必须要携带的信息(如身份信息、A/B分桶信息等)”,才适合放进cookie中,其他类型的数据建议放进localStorage中。