饼干的结构解析
本篇 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"
| |
