早期互联网只是用于简单的页面浏览,并没有交互,服务器也无法知道不同的请求是否来自同一个浏览器,不知道某用户上一次做了什么。每次请求都是相互完全独立的,这也是 HTTP 协议无状态特征的表现。这种缺陷显然无法满足交互式 Web 发展的需求,Cookie 作为一种解决这一问题的方案,被当时最强大的网景浏览器公司提出。
一、Cookie叙述
Cookie 可以理解成浏览器的身份证。不同站点会根据实际情况,发放一个唯一的身份证或不发放。当再次访问相同站点时,按照约定要带上这个身份证来享受部分特权。如果身份证丢了,那就要重新登记办理。
Cookie 信息是由客户端浏览器自身维护的。不同的浏览器有不同的客户端本地存储方式,Chrome 和 Firefox 使用 SQLite 存储,IE 使用的是文本格式。Cookie 里面重要的 key&value 都是被浏览器加密存放的,只有通过给定的 API 方式才能获取存入的原始数据。默认情况下,Cookie 信息会随着浏览器进程的结束而从内存中销毁,如果由于某些需求,服务器端设置了 Cookie 的存活时间,那么这个 Cookie 就会以某种形式被存储在磁盘上,在有效存活期内不会被清理,可以被重复使用并更新其生命周期。
1.1 观察Cookie在HTTP数据包中的交互
这里以 http://www.website.com/bbs/
站点为例,说明 Cookie 在 HTTP 协议包里是如何传输的。
第一次请求 bbs 首页 /bbs/ 时,在请求数据包的 header 部分是没有 Cookie 信息的。这时,因为某些功能需要,站点会要求在浏览器本地存储 Cookies,如下面第一次交互请求登陆页面,本地浏览器会存储三个变量: phpbb3_lhc4d_u
, phpbb3_lhc4d_k
, phpbb3_lhc4d_sid
。在这里要重点关注 phpbb3_lhc4d_sid
这个变量,它存储的是服务器端 SessionID 的值(也可以称呼它为会话标识符)。
GET /bbs/ HTTP/1.1
Host: www.website.com.cn
Upgrade-Insecure-Requests: 1
User-Agent: Mosilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Referer: http://www.website.com.cn/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Connection: close
HTTP/1.1 200 OK
Date: Mon, 21 Oct 2019 03:04:44 GMT
Server: Apache
X-Powered-By: PHP/5.5.38-1~dotdeb+7.1
Set-Cookie: phpbb3_lhc4d_u=1; expires=Tue, 20-Oct-2020 03:04:44 GMT; path=/; domain=www.website.com.cn; HttpOnly
Set-Cookie: phpbb3_lhc4d_k=; expires=Tue, 20-Oct-2020 03:04:44 GMT; path=/; domain=www.website.com.cn; HttpOnly
Set-Cookie: phpbb3_lhc4d_sid=8fedbe0e849ab04df7a698b54d011b16; expires=Tue, 20-Oct-2020 03:04:44 GMT; path=/; domain=www.website.com.cn; HttpOnly
Cache-Control: private, no-cache="set-cookie"
Expires: Mon, 21 Oct 2019 03:04:44 GMT
Referer-Policy: same-origin
X-Frame-Options: sameorigin
Vary: Accept-Encoding
Content-Length: 9110
Connection: close
Content-Type: text/html; charset=UTF-8
刷新页面,再次请求同一个页面,可以发现浏览器会自动把之前存储在本地的对应站点 Cookies 全部提交过去。这里提一下,每一个 Cookie 都是有大小限制的,大约在 4k 左右。
GET /bbs/ HTTP/1.1
Host: www.website.com.cn
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mosilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Referer: http://www.website.com.cn/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: phpbb3_lhc4d_u=1; phpbb3_ihc4d_k=; phpbb3_lhc4d_sid=8fedbe0e849ab04df7a698b54d011b16
Connection: close
HTTP/1.1 200 OK
Date: Mon, 21 Oct 2019 03:05:16 GMT
Server: Apache
X-Powered-By: PHP/5.5.38-1~dotdeb+7.1
Cache-Control: private, no-cache="set-cookie"
Expires: Mon, 21 Oct 2019 03:05:16 GMT
Referer-Policy: same-origin
X-Frame-Options: sameorigin
Vary: Accept-Encoding
Content-Length: 8435
Connection: close
Content-Type: text/html; charset=UTF-8
输入正确的账号信息进入 bbs 内部后,会发现服务器更新了存储在本地 Cookie 中的 phpbb3_lhc4d_sid
信息,它是你能留在 bbs 内部板块的通行证,只要访问的时候带上这个 Cookies 就可以畅通无阻。
POST /bbs/ucp/php?mode=login HTTP/1.1
Host: www.website.com.cn
Content-Length: 94
Cache-Control: max-age=0
Origin: http://www.website.com.cn
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mosilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Referer: http://www.website.com.cn/bbs/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: phpbb3_lhc4d_u=1; phpbb3_ihc4d_k=; phpbb3_lhc4d_sid=8fedbe0e849ab04df7a698b54d011b16
Connection: close
username=***********&password=**************************&login=%E7%99%BB%E5%BD%95&redirect=.%2Findex.php%3F
HTTP/1.1 302 Found
Date: Mon, 21 Oct 2019 03:05:48 GMT
Server: Apache
X-Powered-By: PHP/5.5.38-1~dotdeb+7.1
Set-Cookie: phpbb3_lhc4d_u=77; expires=Tue, 20-Oct-2020 03:05:48 GMT; path=/; domain=www.website.com.cn; HttpOnly
Set-Cookie: phpbb3_lhc4d_k=; expires=Tue, 20-Oct-2020 03:05:48 GMT; path=/; domain=www.website.com.cn; HttpOnly
Set-Cookie: phpbb3_lhc4d_sid=bdabd760e3a87aa6b0dfb517c8c7d90a; expires=Tue, 20-Oct-2020 03:05:48 GMT; path=/; domain=www.website.com.cn; HttpOnly
Location: http://www.website.com.cn/bbs/index.php?&sid=bdabd760e3a87aa6b0dfb517c8c7d90a
Cache-Control: max-age=86400
Expires: Tue, 22 Oct 2019 03:05:48 GMT
Referer-Policy: same-origin
X-Frame-Options: sameorigin
Vary: Accept-Encoding
Content-Length: 0
Connection: close
Content-Type: text/html
GET /bbs/index.php HTTP/1.1
Host: www.website.com.cn
Upgrade-Insecure-Requests: 1
User-Agent: Mosilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Referer: http://www.website.com.cn/bbs/index.php?&sid=bdabd760e3a87aa6b0dfb517c8c7d90a
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: phpbb3_lhc4d_u=1; phpbb3_ihc4d_k=; phpbb3_lhc4d_sid=bdabd760e3a87aa6b0dfb517c8c7d90a
Connection: close
HTTP/1.1 200 OK
Date: Mon, 21 Oct 2019 03:06:16 GMT
Server: Apache
X-Powered-By: PHP/5.5.38-1~dotdeb+7.1
Cache-Control: private, no-cache="set-cookie"
Expires: Mon, 21 Oct 2019 03:06:16 GMT
Referer-Policy: same-origin
X-Frame-Options: sameorigin
Vary: Accept-Encoding
Content-Length: 37722
Connection: close
Content-Type: text/html; charset=UTF-8
分析上述 HTTP 数据包的交互:
- Cookie 是使用 HTTP 的头部来传递和交换信息的;
- Set-Cookie 是服务器端给浏览器下发指令的关键字,可以带上一些控制属性;
- Cookie 是浏览器端发送给服务器端消息字段,它只能是 name=value 格式,无法带上其他属性。
二、Cookie有哪些属性
在 RFC6265 中规定,通常 Set-Cookie 响应头部包含头部名称为「Set-Cookie:尾随的 Cookie」。尾随的 Cookie 除了 name, value 这两个必备属性外,还有几个其他的控制属性可选。相关描述摘录如下:
属性 | 描述 |
---|---|
必须属性 | 变量名称:值 |
可选属性 | 描述摘录 |
expires 过期属性 | 如果这个属性的值不能被转换为日期,客户端会忽略该属性。 当同一个 Cookie 两次请求的 expires 值不相同时,新的可能会替换旧的。 |
max-age 最大缓存时间属性 | 相对过期时间,以秒为单位。 如果该属性的值不是数字,客户端将不做处理。(max-age 优先级大于 expires) |
domain 域属性 | 如果没有设置 Cookie 的 domain 值,该属性的默认值就是创建 Cookie 的网页所在的服务器域名。 |
path 路径属性 | Cookie 的适用范围为路径设置所限制。 如果服务器忽略路径属性,用户客户端会将请求 uri 路径元素的名录当做缺省值。 |
secure 安全属性 | 它指定了在网络上如何传输 Cookie 值。 默认情况下,Cookie 是不安全的,也就是说,它们是通过一个普通的、不安全的 HTTP 链接传输的。 但是如果将 Cookie 标记为安全的,那么它将只在浏览器和服务器通过 HTTPS 或其他安全协议链接时才被传输。 这个属性只能保证 Cookie 是保密的。 |
HttpOnly 属性 | HttpOnly 属性限制 Cookie 的 HTTP 请求的适用范围,只能通过 HTTP 访问,不能通过 document.cookie 获取设定为 HttpOnly 的键值,防止 XSS 读取 Cookie。 Cookie 可以同时拥有 HttpOnly 以及安全属性。 |
三、Cookie的应用场景
3.1 HTTP会话状态保持
单纯的Cookie会话管理——
相对安全的Cookie会话机制——
在功能实现上,两者都没有问题,但在实际使用中会发现 Cookie 本身大小数量的限制和信息本地保存会带来一定的安全问题(如可能会暴露给坏人,或被坏人重放)。一般使用中不推荐把敏感信息存放于 Cookie 中,而是采用存储会话标识符(SessionID)到 Cookie 里的方式,服务器端可以通过获取这个会话标识符(SessionID)关联当前状态信息。当然了,使用会话标识符并非没有风险,相对于前一种方式已是个好的改善。
打个比方,假设浏览器对应自然人,Cookie 对应身份证,而 Web 服务器端对应的是户籍管理处,那么,Web 服务器(户籍管理处)需要负责给浏览器(自然人)发放 Cookie(身份证)。后期的会话鉴别就是通过保存在客户端浏览器的 Cookie(身份证姓名和号码)里的「会话标识符」实现的,所有的敏感信息都存储在服务器端(户籍管理处),而非交给第三方的浏览器来保管。
3.2 基于Cookie的SSO单点登录
同域和非同域下SSO单点登录
在同域下实现 Cookies 单点登录相对而言简单一些。将 Cookie 的 domain 属性配置好父域名,如 .website.com.cn
,那么这个 Cookie 就可以被类似 a.website.com.cn
,b.website.com.cn
,sso.website.com.cn
,website.com.cn
共享使用。有一点需要特别注意,浏览器请求子域会带上父域的 Cookie,反之则不会。按照约定,我们把需要共享的 Cookie 信息写入到 website.com.cn
这个域名下即可。
以上讨论的是同域情况下的 SSO 单点登录过程。那么跨域情况下又是怎么个原理呢?继续来看流程图,相比上述同域情况会多了跳来跳去的动作。
3.3 跟踪分析用户行为
「大数据」颠覆了某些旧的束缚,有能力去分析全部数据,可得出准确的大方向,而不再仅凭随机采样数据做分析。在互联网行业,「大数据」表现在采集和分析网络用户行为数据,并推送需求上。说到网络用户行为数据的采集,就不能不谈谈 Cookie 在此所扮演的另类角色。
客户端访问个性化设置
网站的各项配置参数可以存储在浏览器本地 Cookies 中,当客户端浏览器再次访问此站点时,直接通过读取 Cookies 信息即可完成相关的个性化配置。当然,局限性也很明显,Cookies 到期失效或换一台电脑,效果就没有了。这是针对临时用户而言的。有会员注册功能的站点,一般将配置保存在 Web 服务器本地数据库,这是持久存在的。
搜索引擎&定向广告推送
在搜索引擎或购物站点搜索产品信息后,再浏览其他网页时,经常在其广告区域显示曾经搜索过的产品或相关信息。哪天小伙伴借你电脑临时一用,打开游览器,你的隐私可能就暴露了,很是讨厌,而这是 Cookie 的功劳。
网络广告公司信息推广
还有一种可能,你打开的某站点很有可能还内嵌了一种叫做网页臭虫的图片。该图片透明且只有一个像素大小,我们称呼它为 Pixel Code,其作用是向所有访问过此页面的客户端写入定制的 Cookie (通过不同站点收集用户爱好习惯)。当你访问与网络广告推广有合作的购物站点时,这些 Cookie 信息就会被读取并被有针对性地推荐广告。整个流程和上面的搜索引擎&定向广告类似,在此不再作图赘述了。
四、Cookie的安全问题探讨
作为一个和 HTTP 协议打交道多年的安全从业者,还是忍不住想讨论下这个问题——
我们知道 Cookie 是保存在用户本地的,由各自的浏览器自行维护。拿 Chrome 浏览器来说,用户可以直接在浏览器内输入 chrome://settings/siteData
来访问它的 Cookies 存储信息,这种方式是通过浏览器 API 获取,可以完整地看到原始 value 信息。如果用户想通过其他手段查看,还可以通过 chrome://version/
发现数据存储的位置,仔细查看后,不难发现,%LOCALAPPDATA%\Google\Chrome\UserData\Default\Cookies
就是它的 Cookies 存储文件。这是一个 SQLite 轻型数据库,通过工具打开查询后可发现,domain 是 baidu 的都可以被搜索出来,如下图。这里可以看到,name=value 的 value 部分被浏览器进行了一次加密存储处理。
聊完 Chrome 的本地存储,再说说 Cookie 本身的特点。Cookie 通常可能记录了用户账号 ID、密码、会话标识符 SessionID,这些信息可能是加密,也可能是未加密的。加密存储只能说明其在安全上做了些功课,但被截获(抓包、XSS)后,是否加密并无太大意义。坏人无需看懂那串密语,只需要把这些信息照葫芦画瓢丢给服务器,就可以实现越权操作了。而这些安全性问题,可以阅读相关词条(见Ref1)了解学习。 (易树国 | 天存信息) Ref
- ‘cookie 储存在用户本地终端上的数据’ - 百科词条
- ‘HTTP State Management Mechanism’ - RFC6265