一个不大不小的问题


假设服务器有一个接口,通过这个接口可以添加一个管理员,

但是,不是任何人都有权利操作的

那么服务器是如何知道请求接口的人是有这种权限的呢?

答案是: 只有登录过的管理员才能做这种操作

可问题是,客户端和服务器之间使用的是http请求,http请求是无状态的,什么叫无状态,就是服务器不知道这一次请求的人,跟之前请求的人是不是同一个人

image.png

由于http协议的无状态,服务器会忘记之前的请求,他去法确定这一次请求的客户端,就是之前登录的那个客户端

你可以把服务器想象成一个有脸盲症的人,没办法记住之前和说话的人之前做过什么

于是,服务器想了一个办法

它按照如下的流程来认证客户端的身份

  1. 客户端登录以后,服务器会给客户端一个出入证(令牌token)
  2. 客户端每次请求,都必须要附带这个出入证(令牌token)

image.png

服务器发扬了认证不认人的优良传统,既可以轻松识别身份了

但是,用户不可能只在一个网站登录,于是客户端会受到各个网站的出入证,因此,客户端要求有一个类似卡包的的东西,具备下面的功能

  1. 能够存放多个出入证,这些出入证来自不同的网站,也可能是一个网站有多个出入证,分别用于出入不同的地方
  2. 能够自动出示出入证,客户端访问不同的网站的时,能够自动把对应的出入证附带请求发送出去
  3. 正确出示出入证,客户端不能将肯德基的出入证给麦当劳
  4. 管理出入证的有效期,客户端要能自动识别哪些已经过期的出入证,并它从卡包移除出去

能满足以上所有的要求,就是cookie

cookie类似一个卡包,专门存放各种出入证,并有一套机制自动管理这些证件;

卡包内的每一张卡片,称为一个cookie

cookie的组成


cookie是浏览器中特有的概念,他就像浏览器的专属卡包,管理者各个网站的身份信息;

每个cookie就相当于某个网站的一个卡片,它记录了下面的信息

  • key: 键,比如身份编号
  • value: 值,比如某人的身份编号是2176917G1JHHK114HJ1H4HJHJ1,这有点像卡片的条形码,当然,它可以是任何信息
  • domain: 域,表达这个cookie是输入哪个网站的,比如baidu.com,表示这个cookie是属于baidu.com这个网站的
  • path: 路径,表达这个cookie是输入该网站的哪个基路径的,就好比同样一家公司不同的部门会办法不同的出入证.比如news,表示cookie输入/new这个路径的.(后续详细解释)
  • secure: 是否用于安全传输(后续详细解析)
  • expire: 过期时间,表示cookie在什么时候过期

当浏览器发送一个请求的时候,它会瞄一眼自己的卡包,看看哪些卡片是可以捎带给服务器

如果一个cookie同时满足以下条件,则这个cookie会被附带到请求中

  • cookie没有过期
  • cookie中的域和这次请求的域是匹配的
    • 比如cookie中的域是baidu.com,则可以匹配的请求域是baidu.com,www.baidu.com,blog.baidu.com等等
    • 比如cookie中的域是www.baidu.com,则只能匹配www.baidu.com这样的请求域
    • cookie不在乎端口号,只要域匹配就好
  • cookie中的path和这次请求的path可匹配的
    • 比如cookie中的path是/news,则可以匹配的路径是/news,/news/detail,/news/a/b/c等,但是不能匹配blog
    • 如果cookie的path是/,可以想象能够匹配所有的路径
  • 验证安全传输
    • 如果cookie中的secure的属性是true,则请求协议必须是https,否则不会发送cookie
    • 如果cookie中的secure的属性是false,则请求可以是http,也可以是https
  • 如果一个cookie瞒住了以上所有的条件,浏览器会自动把它加到请求中

具体的加入办法是,浏览器会将符合条件的cookie,自动放到请求头中,例如,当我们访问百度的时候,它在请求头中附带了下面的cookie
image.png

看到打马赛克的部分了吗?这部分是通过请求头的cookie发送到服务器的,它的格式是键=值;键=值;键=值;...,每一个键值对就是一个符合条件的cookie

cookie中包含了重要的身份信息,用于不要把你的cookie泄露给别人!!!!,否则,他人就拿到了你的证件,有了证件,就具备了为所欲我的可能

如何设置cookie


由于cookie是保存到浏览器的,同时,很多证件又是服务器颁发的

所以,cookie设置有两种模式

  • 服务器相应: 这种模式是非常普遍的,当服务器决定给客户端办法一个证件时,它会在响应的消息中包含cookie,浏览器会自动把cookie保存在卡包中
  • 客户端自行设置: 这种模式少一些,不过也有发生,比如用户关闭了某个广告,选择了”以后不要在弹出”,此时需要把这种小信息直接通过浏览器的JS代码保存到cookie中.为后续请求服务器时,服务器会看到客户端不想要再次弹出广告的cookie,不是就不会再发送广告过来

服务器端设置cookie


服务器可以通过设置响应头,来告诉浏览器如何设置cookie

响应头按照下面的格式设置:

  1. set-cookie:cookie1
  2. set-cookie:cookie2
  3. set-cookie:cookie3
  4. ...

通过这种模式,就可以在一次响应中设置多个cookie了,具体设置多少个cookie,设置什么cookie,根据你的需要自行处理
其中每个cookie的格式如下

键=值;path=?;domain=?;expire=?;max-age=?;secure;httponly

每个cookie除了键值是必须要设置的,其他的属性都是可选的,而且孙旭顺序不限

当这样的响应头到达客户端,浏览器会自动将cookie保存到卡包中,如果卡包中已经存在一模一样的的卡片(其他key,path,domain相同),则会自动覆盖之前的设置.

下面,一次说明每个属性的值

  • path: 设置的cookie路径.如果不设置浏览器会将其自动设置为当前请求的路径;比如,浏览器请求的地址是/login,服务器相应了一个set-cookie:a=1;,浏览器会将该cookie的path设置为请求路径/login

  • domain: 设置cookie的域.如果不设置,浏览器会自动将其设置为当前请求的域,比如,浏览器的请求地址是http://www.baidu.com,服务器相应了一个set-cookie:a=1,,浏览器会将该cookie的domain设置为请求的域www.baidu.com

    • 这里值得注意的是,如果服务器响应了一个无效的域,浏览器是不认的
    • 什么是无效的域? 就是响应的域连根域都不一样.比如,浏览器请求的是baidu.com,服务器响应的cookie是set-cookie:a=1;domain=sina.com,这样浏览器是不认的
    • 如果浏览器连这样的情况都允许,就意味着张三的服务器,有权利给用户一个cookie,用于访问李四的服务器,这会造成很多的安全性问题
  • expire: 设置cookie的过期时间;这里必须是一个有效的GMT时间,即格林威治标准时间字符串比如,Fri,17 Apr 2020 09:35:59 GMT,表示格林威治时间的2020-04-17 09:35:59,即北京时间的2020-04-17 17:35:59.当客户端的时间达到这个时间点后,会自动销毁该cookie

  • max-age: 设置cookie的相对有效期;expire和max-age通常设置一个即可;比如设置max-age1000,浏览器在添加cookie时,会自动设置的它的expire为当前时间加1000秒,作为过期时间.

    • 如果不设置expire,又没有设置max-age,则表示会话结束后过期;
    • 对于部分浏览器而言,关闭浏览器意味着会话结束
  • secure: 设置cookie是否是安全链接;如果设置该值,表示该cookie后续只能随着https请求发送.如果不设置,则表示该cookie会随着所有的请求发送

  • httponly: 设置cookie是否仅用于传输;如果设置了该值,则表示该cookie仅能用于传输,不是允许客户端通过JS获取,这对防止脚本跨站攻击(XSS)会很有用.

    • 关于如何通过JS获取,后续讲解
    • 关于什么是XSS,不在本文讨论

下面来一个例子,客户端通过post请求服务器localhost:12306/api/student,并在消息中给予了账号和密码,服务器验证登录成功以后,在响应头中加入以下内容

set-cookie:token=123456;path=';max-age=3600;httponly

当该响应达到浏览器后,浏览器会创建下面的cookie:

  1. key:token
  2. value:123456
  3. path:/
  4. expire:2020-04-17 18:55:00 #假设当前时间是2020-04-17 17:55:00
  5. secure:fase # 任何请求都可以附带这个cookie,只要满足其他要求
  6. httponly:true #不允许js获取cookie

于是,随着浏览器后续对服务器的请求,只要满足要求,这个cookie就会被附带到请求头中传给服务器:

cookie:token=123456;其他cookie....

现在,还剩下最后一个问题,就是如何删除一个cookie

如果要删除浏览器的cookie,只需要让服务器的响应同一个域,同样的路径,同样的key,只是时间过期的cookie即可

所以,删除cookie其实就是修改cookie

下面的相应会让浏览器删除cookie

cookie:token=;domain=localhost;path=/;max-age=-1

浏览器按照要求修改了cookie以后,就会发现cookie已经过期了,于是自然就删除了

无论是修改,还是删除,都要注意cookie的域和路径,以因为完全有可能存在域或路径不同,但是key相同的cookie,因此无法仅通过key确定是哪个cookie

客户端设置cookie


既然cookie是存放在客户端你的,所以浏览器向JS公开了接口,让其可以设置cookie

document.cookie="键=值;path=?;domain=?;expire=?;max-age=?;secure"

可以看出,在客户端设置cookie,和服务器设置cookie的格式是一样的,只有下面的不同

  • 没有httponly. 因为httponly本来就是为了限制在浏览器端访问的,既然你是在客户端配置,自然失去了意义;
  • path的默认值. 在服务器端设置了cookie时候,如果没有写path,使用的是请求的path.然而在客户端设置cookie时,也许根本没有请求发生;因此,path在客户端设置时的默认值是当前网页的path
  • domain的默认值;和path同理,客户端设置时的默认值是当前页面的domain
  • 其他: 一样
  • 删除cookie: 和服务器一样,修改cookie的的过期时间即可

总价


以上,就是cookie的原理部分

如果把它应用于登录场景,就是如下流程

登录请求

  1. 浏览器发送请求到服务器,附带账号密码
  2. 服务器验证账号密码是否正确,如果不正确,相应错误;如果正确,在响应头中设置cookie,附带登录认证信息(至于登录认证信息是什么样的,如何设计,要考虑哪些问题,就是另外一个话题了,可以百度JWT)
  3. 客户端收到cookie,浏览器自动保存下来

后续请求

  1. 浏览器发送请求到服务器,希望添加一个管理员,并将cookie自动附带到请求中
  2. 服务器先获取cookie,验证cookie的信息是否正确,如果不正确,不予操作;如果正确,完整正常的业务流程’