深入浅出了解Cookie和Session

1,077 阅读3分钟

前言

在Web开发中经常会用到Cookie和Session,用来存储一些用户信息从而达到鉴权的目的。但在使用中都是一些比较简单的存取,具体的Cookie和Session内部参数和原理基本都没了解的。以我个人的经历,无论是工作中还是在面试时,Cookie和Session都是一个高频的问题,由于对其没有深入的了解,所以也在这方面栽了不少跟头,所以我准备用一篇文章总结一下Cookie和Session。由于本人是个phper,所以下面的代码都以PHP为例。

产生的原因

在早期的web开发中都是一些门户网站,只展示静态网页,没有人为交互的,后来随着互联网的发展,出现了交互式的Web系统,如微博,有登录、搜索、点赞、评论,关注、转发这些功能,由于http是一个无状态的传输协议,服务器和用户之间的交互,是不知道哪个用户访问的它,用户的行为是无法被记录的,所以那些操作是串联不起来的。为了解决这个问题,就有了鉴权服务的存在,常见的有cookie、session和token。
token是开发代码里随机的库里唯一的一串字符串,一般在用户登录之后生成,然后将这个token存到用户表里,跟用户绑定,返给客户端,客户端存起来,以后的每次请求都带着这个token,这样服务器就知道了每次的请求用户是谁,最初的token就是这么用的,存在很多问题,如token不变,没有过期时间,后来为了解决这些问题出现了jwt token,由于token不是本文的重点,就不细说了。

Cookie

Cookie是什么

Cookie常有一种误解,说Cookie是缓存。Cookie的本质其实是一小段的文本信息,是一种字典类型的key、value键值对,存在用户的客户端,是一种浏览器管理状态的文件,并不能给服务器减轻什么压力,带着它反而增加带宽。

Cookie的分类

1、会话Cookie:保存在内存,当浏览器的会话结束自动结束。

2、持久化Cookie:保存在硬盘,只有当失效时间到期,才会自动消失。

cookie的属性

先看一张图,以百度为例 image.png 从图中我们可以看到,当我们访问百度的时候,会自动生成很多cookie,格式为: {Name:值, Value: 值, Domain:域名, Path:路径, Expries/Max-age:失效时间, Size: 大小, HttpOnly, Secure, SameSite, SameParty, Priority}

1、Name

名字,同一域下不能重复

2、Value

值,没什么可说的,但有一点需要注意,value的值是不允许有分号、逗号、空格等一些特殊符号的,在保证没有这些的时候是可以直接存的,不能保证的时候最好对value进行编码。

3、Domain

域,生成cookie的时候如果没有指定则是当前域名,如果我的域名为www.cookie.com,则下面的代码执行完,Domain就为www.cookie.com

<?php
setcookie('zhugeliang', 'www.cookie.com');

image.png 生成cookie的时候Domain是可以指定的,如:

setcookie('zhugeliang', 'www.cookie.com', 0, '', '.cookie.com');

image.png 需要注意的是domain只能为当前域名或者一级域名,设置成其他域名是不生效的。如果当前域名为www.cookie.com Domain设置成m.cookie.com是无法设置成功的,设置的cookie也不会成功

setcookie('zhugeliang', 'san', 0, '', 'm.cookie.com');

image.png Domain设置成一级域名和二级域名的区别:

例如有两个二级域名项目www.cookie.comm.cookie.com,如果不指定Domain,则生成的cookie Domain为各自的域名,两个项目访问的时候也只会带着当前域名下的cookie。

image.png

image.png 当我们在www.cookie.com下设置将Domain设置成一级域名时,在m.cookie.com访问也可以获取到的。

setcookie('domain1', 'test', 0, '', '.cookie.com');

image.png

image.png 这也是多个域名下实现cookie共享的方法。

4、Path

生效的路径,默认值为'/',匹配的是web路由,例如在http://www.cookie.com/index/index下生成一个cookie指定路径为'/index'

setcookie('pathTest', 'test', 0, '/index');

image.pnghttp://www.cookie.com/detail/index下访问是获取不到的

image.png 由于生效路径是根据路由匹配生效范围,所以在http://www.cookie.com/index/detail下一样是可以获取到的

image.png

5、Expries/Max-age

cookie的有效期,Expires属性,一般浏览器的cookie都是默认储存的,当关闭浏览器结束这个会话的时候,这个cookie也就会被删除,就是会话型cookie。
如果想要cookie存在一段时间,就需要设置Expires属性为未来的一个时间节点,Expires这个是代表当前时间的,这个属性Max-Age,代表持久化cookie

# max-age写法
setcookie('name', 'value', 1356663605);

// Expries写法
// Old header:
// Set-Cookie: name=value; expires=Fri, 28-Dec-2022 03:00:05 GMT

// New Header:
// Set-Cookie: name=value; Expires=Fri, 28-Dec-2022 03:00:05 GMT; Max-Age=5

Expries/Max-age值的不同,代表的cookie的类型也不同,值为session时,就是为会话型cookie,浏览器关闭直接消失,时间格式的为持久化cookie,时间了自动删除。 会话型cookie

setcookie('zhugeliang', 'www.cookie.com');

image.png 持久化cookie

setcookie('zhugeliang', 'www.cookie.com', time() + 1800);

image.png

6、Size

Cookie的大小

7、HttpOnly

如果cookie中设置了HttpOnly=true属性,那么通过js脚本将无法读取到cookie信息,这样能有效的防止XSS攻击,增加cookie的安全性。这个属性也是一个高频面试题。

8、Secure

该Cookie是否仅被使用安全协议传输,默认为false。安全协议有HTTPS和SSL等,在网络上传输数据之前先将数据加密。如果网站是http的,该参数设置为true,cookie是设置不成功的。

9、SameSite

作用是防止跨站请求伪造(CSRF)攻击和保护用户隐私。它有三个属性值

  • Strict 完全禁止第三方 Cookie,跨站点时,任何情况下都不会发送 Cookie。
  • Lax 允许部分第三方请求携带 Cookie。
  • None 无论是否跨站都会发送 Cookie。

Chrome的SameSite默认值是Lax,而Safari的默认值是Strict。

详细参考知乎:cookie samesite解析

10、SameParty

参考 详解 Cookie 新增的 SameParty 属性

11、Priority

优先级,chrome的提案,定义了三种优先级,Low/Medium/High,当cookie数量超出时,低优先级的cookie会被优先清除

参考Cookie 知识二则

Session

由于Cookie是存在客户端的,是能够变篡改的,所以为了安全起见就出现了另一种相对安全的鉴权session

Session是什么

Session是服务器用来保存用户操作的一系列会话信息,由Web容器进行管理,服务器使用一种类似于散列表的结构来保存信息。

Session id

当浏览器请求创建一个session时,服务器首先检查请求里是否已包含了一个session标识也就是session id,如果已包含则说明以前已经为此客户端创建过session,服务器就按照session id把这个session检索出来使用(检索不到,会新建一个),如果请求不包含session id,则服务器会创建一个新的session id,这个值是一个既不会重复,又不容易被找到规律以仿造的字符串,这个session id将被在本次响应中以cookie的方式返回给浏览器保存。

image.png 浏览器请求服务器,服务器生成完session将sessionId以cookie的形式返给浏览器,cookie的name为PHPSESSID,有多少个客户端,服务器上就存在多少sessionId,当然这个也不是绝对的,当生成session的时候更改了session.name,就会多出来一个。

Session与Cookie的关系

浏览器与服务器之间的session交互是通过session id的,session id的存储是依赖于cookie的,所以session中也会支持cookie的属性方法。

image.png

虽然session依赖于cookie,但并不是禁用了cookie,session就不能用,只需要与客户端约定好session id的交互流程就行。我之前做的一个APP,有个图形验证码的功能,服务端生成,APP展示,用户填完验证码,APP请求接口验证,客户端的开发就是参考了浏览器的接收和发送的方法,服务端就能正常的读写,当然也有很多方法,例如放到接口响应参数里,客户端请求的时候以参数形式再带回来,都是可以的。

集群架构Session共享

单机情况下,不存在Session共享的情况,但大多数公司每个项目都都是集群的方式,分布式情况下,如果不进行Session共享会出现请求落到不同机器要重复登录的情况。如下图:

image.png 用户第一次访问在服务器1,生成的session也在服务器1,第二次访问是服务器2,发现没有session,又跳到登录页登录。为了解决这个问题,就需要保证集群架构服务器之间的session一致性,session共享就出现了,主要有以下几种方式。

1、Session复制

image.png 当用户访问到服务器1时生成完session,服务器1自动将session信息同步给session2,保证session一致性。这种方式在用户量不大,服务器比较少的时候还是没什么问题的,但是如果用户量激增,服务器增多,缺点就凸显出来了,同步传输占用内网带宽,同步性能指数下降,session占用内存无法水平扩展。

2、Nginx负载均衡:

nginx负载均衡的几种方式:

1、轮询(默认):每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除。

2、weight权重:指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的情况。

3、ip_hash:每个请求按访问ip的hash结果分配服务器,固定访客每次访问都是同一个服务器。

4、fair(第三方):按后端服务器的响应时间来分配请求,响应时间短的优先分配。

5、url_hash(第三方):按访问url的hash结果来分配请求,使每个url定向到同一个后端服务器,后端服务器为缓存时比较有效。

只根据nginx负载均衡的方式实现解决服务器之间Session不一致的问题,显然ip_hash更适合,固定每个访客访问的都是同一台服务器,但这样显然限制了我们项目的扩展性,如果某台服务器挂了被自动剔除或者服务器不够添加几台,ip_hash算法就会重新分配服务器,这样就带来了新的问题,如果项目对数据的准确性要求比较高,如金融项目,显然违背了项目架构的高可用原则,任何一个架构师也不敢采用这种方式。

3、服务器集群session存储共享

我们都知道session是存在服务器的,如果集群每台服务器存取的session都在同一个一个地方,这样我们服务器的扩展就方便很多,只需要将每台服务器的session存储配置指向同一个地方就行。下面以PHP为例,列举几种session共享存储的几种方式。

PHP其实已经为我们提供了session共享存储方案,在php.ini里可以更改session的存储方式,只需要更改session.save_handler的值即可

1、文件(默认),在没改配置之前,默认是以文件存储的

[Session]
; Handler used to store/retrieve data.
; http://php.net/session.save-handler
session.save_handler = files
; The path can be defined as:
;
;     session.save_path = "N;/path"

session.save_handler = files,即使用读写文件的方式保存 SESSION 数据,而 SESSION 文件保存的路径由 session.save_path 指定,文件名以 sess_ 为前缀,后跟 SESSION ID,如:sess_q9ga90aoc8p2i6e3fuda8ft5bf。文件中的数据即是序列化之后的 SESSION 数据了。

image.png

如果访问量大,可能产生的 SESSION 文件会比较多,这时可以设置分级目录进行 SESSION 文件的保存,效率会提高很多,设置方法为:session.save_path="N;/save_path",N 为分级的级数,save_path 为开始目录。 当写入 SESSION 数据的时候,php 会获取到客户端的 SESSION_ID,然后根据这个 SESSION ID 到指定的 SESSION 文件保存目录中找到相应的 SESSION 文件,不存在则创建之,最后将数据序列化之后写入文件。读取 SESSION 数据是也是类似的操作流程,对读出来的数据需要进行解序列化,生成相应的 SESSION 变量。 save_path分级参数设置的时候需注意,实测PHP会报错,存放session的目录不会自动创建,直接报错

session.save_path = "3;/var/session"

我设置的是3层,目录的用户权限也已经更改,还是报错

image.png 后来发现php源码文件中ext/session/mod_files.sh文件,可以辅助生成目录,具体的实现没深入研究,感兴趣的可以动手操作一下。

文件存储我们可以更改存储的具体路径,达到存储共享的效果,只需要提供一台服务器专门存储session,然后将这台服务器的存储目录挂载到服务器集群里的所有服务器。

2、数据库存储 参考:PHP中将session存入数据库及取用 将session存到数据库,个人感觉意义不大

3、memcache

网站访问量很大的时候,session 的访问将势必影响网站的速度。因为文件的读取速度是很低的。 memcache 作为内存缓存服务器,通过哈希算法,把数据以key->value的形式进行读取,其速度是远远高于文件的读取。具体配置可参考如下:

session.save_handler = memcache
session.save_path = "tcp://Mem服务器1:端口号,tcp://Mem服务器2:端口号..."

保存的key为session_id 可连接memcache服务器,get查看

4、redis Redis是我个人感觉比较理想型的session共享存储方案,速度快,配置简单,可扩展性也比较强。

session.save_handler = redis //存储方式
session.save_path = “tcp://127.0.0.1:6379″ //redis如有密码则:tcp://127.0.0.1:6379?auth=password

常见问题

学完cookie和session,应该知道的几个常见问题

1、cookie和session的区别和联系

2、cookie禁用了session还能用吗?

3、二级域名相同的两个三级域名(如:www.cookie.comm.cookie.com)之间cookie和session如何共享?

4、不同的域名(如:www.tmall.comwww.taobao.com)之间cookie和session如何共享?

5、分布式集群架构,session如何共享?