如何设置cookie

107 阅读7分钟

如何设置cookie

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

所以,cookie的设置有两种模式:

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

服务器端设置cookie

服务器可以通过设置响应头,来告诉浏览器如何设置cookie 响应头按照下面的格式设置:

set-cookie:cookie1
set-cookie:cookie2
set-cookie:cookie3
...

通过这种模式,就可以在一次响应中设置多个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的域。如果不设置,浏览器会自动将其设置为当前的请求域,比如,浏览器请求的地址是 www.yuanjin.tech ,服务器响应了一个 set-cookie: a=1,浏览器会将该cookie的domain设置为请求的域 www.yuanjin.tech

    • 这里值得注意的是,如果服务器响应了一个无效的域,浏览器是不认的

    • 什么是无效的域?就是响应的域连根域都不一样。例如,浏览器请求的域是 yuanjin.tech ,服务器响应的cookie是 set-cookie: a=1; domain=baidu.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-age为1000,浏览器在添加cookie时,会自动设置它的expire为当前时间加上1000秒,作为过期时间。

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

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

    • 关于如何通过JS获取,后续会讲解
    • 关于什么是XSS,不在本文讨论范围 下面来一个例子,客户端通过 post 请求服务器 yuanjin.tech/login, 并在消息体中给予了账号和密码,服务器验证登录成功后,在响应头中加入了以下内容:
okie: token=123456; path=/; max-age=3600; httponly

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

key: token  
value: 123456  
domain: yuanjin.tech  
path: /  
expire: 2020-04-17 18:55:00 #假设当前时间是2020-04-17 17:55:00  
secure: false #任何请求都可以附带这个 cookie,只要满足其他要求  
httponly: true #不允许JS获取该 cookie

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

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

现在,还剩下最后一个问题,就是如何删除浏览器的一个 cookie 呢?

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

删除cookie其实就是修改cookie

cookie:token=;domain=yuanjin.tech;path=/;max-age=-1

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

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

示例

ter.post(
  "/login",
  synchandler(async (req, res) => {
    const result = await adminServ.login(req.body.loginId, req.body.loginPwd);
    if (result) {
      // 登录成功
      res.header("set-cookie", `token=${result.id}; path=/; domain=localhost; max-age=3600`);
    }
    return result;
  }
));

客户端修改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中的信息是否正确,如果不正确,不予以操作,如果正确,完成正常的业务流程