饼干的结构解析
本篇 blog 介绍了 Cookie 的结构,以及 Cookie 的各个属性的作用与关联,之后可通过一个 flask 服务来简单实践。
前置环境
- python
- flask
- 一个浏览器
- 一个 http 请求工具,如 curl
cookie 属性
属性 | 作用 |
---|---|
Domain | 标记 cookie 的域,会对比请求中 cookie domain 属性与目标服务器的域名比较,一致或者为子域才会进行后续 path 匹配 |
Path | cookie 作用的 URL 路径 |
Expires | cookie 过期时间 |
Max-Age | cookie 过期时间,单位为秒 |
Secure | cookie 只能通过 https 传输,该属性只能在 https 站点设置 |
HttpOnly | cookie 只能通过 http 传输,不能通过 js 访问 |
SameSite | cookie 只能在同站点下使用,防止跨站攻击 |
Name | cookie 名称 |
Value | cookie 值如果值为Unicode字符,需要为字符编码。如果为二进制数据,则需要使用BASE64编码 |
Priority | cookie 优先级,值为 Low、Medium、High,当 cookie 超量后优先级低的可能不会被发送 |
生命周期
- Expires
格式为 http-date GMT 格式,如Fri, 01 Dec 2023 12:01:00 GMT
用于强制删除 cookie,可通过将 expires 设置为过去的时间来实现。 - Max-Age
为 cookie 过期时间,单位为秒
Expires 和 Max-Age 二者只能存在一个,如果同时存在,优先使用 Max-Age。
未设置过期时间的 cookie 称为 session-cookie,浏览器会在会话结束时删除该 cookie,会话何时结束则取决于浏览器的定义。
cookie 的作用范围
cookie 的作用范围由 Domain
和 Path
共同决定,浏览器会先检测请求域名和 Domain
是否匹配,如果匹配则再检测 Path
是否匹配。
- Domain
如当前响应中set-cookie
设置了Domain=nikunokoya.com
则访问子域名vps.nikunokoya.com
时会携带该 cookie,如果为设置Domain
则默认会将设置该 cookie 的服务器作为Domain
。 - Path
Path
为 cookie 的作用路径,如Path=/index/
则访问nikunokoya.com/index/a/b/c
时会携带该 cookie,如果不设置Path
则默认为/
,即所有路径都会携带该 cookie。
SameSite
用于判断该 cookie 是否应该与跨站请求一起发送,以防止跨站请求伪造攻击(CSRF),SameSite
有三种取值:
- Strict
最严格模式,会禁止跨域请求携带该 cookie,例如一些修改密码或购物的服务请求,需要使用该模式保证服务的处理符合安全预期,而不是从某个邮件链接跳转过来并携带 cookie 造成安全隐患。 - Lax
会允许一部分跨域请求携带该 cookie,比如从在一个网站浏览另一个网站的图片时并不会携带 cookie,而跳转到该网站是会携带 cookie。 chrome 浏览器在未设置SameSite
时默认为Lax
模式。 - None
允许所有跨站请求携带该 cookie,但是需要同时设置
Secure
属性,即只能通过 https 传输。
- Same-Origin
请求的协议、域名、端口号任意一个不同都会被认为是跨域请求。- 比如从
https://www.a.com:443
发起一个请求到https://www.b.a.com:443
,这个请求就是跨域请求。 https://www.a.com:443
与https://www.a.com:80
也是跨域请求。
- 比如从
- Same-Site
判断是否跨站的标准更加宽松,会根据 eTLD+1 是否相同并且协议相同来判断是否跨站。https://vps.nikunokoya.com:443
和https://www.nikunokoya.com:443
为同站,a.com
和b.com
为跨站。 - CSRF
比如用户在a.com
登录了账号,然后在不退出a.com
的情况下访问了b.com
,此时b.com
可以通过a.com
的 cookie 伪造成用户,向a.com
发起请求。 - eTLD
有效顶级域名,公共后缀列表可在 publicsuffix.org 查看。
前缀语义
cookie 前缀语义用于通知浏览器该 cookie 的使用场景,浏览器会根据前缀判断是否应该发送该 cookie。
__Host-
前缀
以 __Host-
开头的 cookie,表示该 cookie 必须与 Secure
和 Path=/
属性一起使用,并且不能设置 Domain
属性。该前缀检查最严格,cookie 不会发送给任何子域,只会发送给当前域名的不同 path
。
__Secure-
前缀
当 cookie name 以 __Secure-
开头时,表示该 cookie 必须与 Secure
属性一起使用。当需要向不同的子域名发送 cookie 时,可以使用该前缀,但是需要注意 Domain
属性的设置。
第三方 cookie
直接调用三方 cookie 服务
当需要跨域携带 cookie 时需要设置 third-party cookie,三方 cookie 通常用于个性化推荐,广告投放等场景,记录用户访问习惯,但是也会造成用户隐私泄露。
三方 cookie 通常需要调用第三方的 cookie 服务来设置。
服务端和浏览器允许跨域请求
当发送 fetch 请求时如果是跨域请求,浏览器会在请求头中添加 Origin
字段标记请求来源,后端服务中需要在响应 Header 中设置 Access-Control-Allow-Origin
字段来允许跨域请求,如果设置为 *
则表示允许所有跨域请求,如果设置为 null
则表示不允许跨域请求。
w.Header().Set("Access-Control-Allow-Origin", "http://localhost:3000")
同时在前段的 fetch 请求中需要设置 credentials
字段为 include
,表示允许跨域请求携带 cookie。
credentials=include
表示允许跨域请求携带 cookie,但是需要设置Access-Control-Allow-Origin
字段必须为当前请求的源,不能为*
。credentials=same-origin
同源时发送 cookie。credentials=omit
表示不允许跨域请求携带 cookie。
通过代理
请求先发送给代理插件(此时未发生浏览器跨域),再由代理服务发送跨域请求到目标服务,目标服务响应后再由代理服务返回给浏览器。
demo
目录结构
|
|
flask 服务
以下用 flask 起了一个简易的服务,当访问本地的 /get-cookie/
时会设置一个名为 id 的 cookie 并存储在浏览器中。
同时通过 CORS 设置允许跨域请求,当访问 /api/auth/
时会检测 cookie 中的 id 是否为 123,如果是则返回一个 json,否则返回一个错误信息。
|
|
启动服务:
|
|
curl -I http://127.0.0.1:5000/get-cookie/ --cookie cookies
|
|
cookie 传递作用域可在浏览器的开发者工具中验证,如 chrome 的开发者工具中的 Application
-> Storage
-> Cookies
。
html 和 js
|
|
发送 fetch 请求设置 cookie:
|
|
npx serve 启动另一个不同源的服务
在另一个目录下将下面 index.html 通过 npx serve
启动另一个服务 http://localhost:3000/
,在该服务下发送跨域请求到 flask 服务。
|
|
注意这里需要在 fetch 请求中配置 credentials:"include"
|
|