整理和测试花了很大时间,如果对你有帮助,可以点个关注哇,也有其他干货哦~
浏览器和服务器之间的传输使用的 HTTP 协议,而它是无状态的。也就是说,每个请求都是独立的,服务器并不知道2次请求是否是同一个人。
为了解决这个问题,服务器想了一个办法:
当客户端登录成功后,服务器会给客户端一个令牌凭证 token;客户端后续的请求都需要带着这个 token 在服务器做验证。
但用户不可能只在一个网站登录,于是客户端会收到各个网站的出入证 token。所以客户端需要一个 “卡包” 来实现以下功能:
满足上述要求的就是 cookie,每一个 token 就是一个 cookie。
每个网站的 cookie 大小不超过 4kb。
每一个 cookie 都记录了以下信息:(除了 key 和 value,其他非必填+顺序无关)
key:键,比如表示身份编号的字符串 token
value:值,比如 123abc,它可以是任何字符串。
domain:主机(域),表示这个 cookie 是属于哪个网站的,比如 www.csdn.net
。【默认值:当前主机,也就是 location.host
】MDN参考
path:路径,表示这个 cookie 是属于该网站的哪个路径。【默认值:实测发现是 cookie 所处目录的上级目录。比如页面是 http://localhost:3001/a/api/login
,则 path 为 /a/api
】
secure:是否使用安全传输。MDN参考
httpOnly:表示该 cookie 仅能用于传输,而客户端通过 document.cookie
获取的是空字符串,这对防止跨站脚本攻击(XSS)会很有用。
XSS:比如当前页面打开 iframe,iframe 可以获取父级的 cookie。设置 httponly 可以不允许 js 获取来防止跨站脚本攻击。
expires:过期时间,表示该 cookie 在什么时候过期。MDN 参考
max-age:有效期。【默认值:如果 expires 和 max-age 都不设置,则为 session,也就是会话结束后过期,大多浏览器关闭(注意不是标签页关闭)意味着会话结束。】
expires 和 max-age 一般只设置一个即可。
需要同时满足以下4个条件:
Fri, 22 Dec 2023 17:09:13 GMT
。到期后浏览器会自动删除。new Date().toGMTString() // Fri, 22 Dec 2023 17:09:13 GMT
// 对比常见的时间格式:
new Date() // Sat Dec 23 2023 01:09:13 GMT+0800 (中国标准时间)
max-age=1000
,相当于设置 expires=当前时间 + 1000s
csdn.net
,则可匹配的请求域有:csdn.net
、www.csdn.net
、blogs.csdn.net
等。www.csdn.net
,则只能匹配 www.csdn.net
这样的请求域。https://search.jd.com/Search?keyword=xxx
网页来说:【翻译:通过 Set-Cookie 标头设置 cookie 的尝试被阻止,因为其域对于当前域无效】
/
表示匹配所有。如果是 /docs
:
/docs
,/docs/
,/docs/Web/
,/docs/Web/HTTP
/
,/docsets
,/fr/docs
浏览器会将符合条件的 cookie,自动添加到请求头 Cookie 中。下图可以看到有3个满足的 cookie,以 ;
分隔。
cookie 是保存在浏览器端的,有2种设置模式:
set-cookie: 123abc
,浏览器会自动保存在 “卡包” 中。查看方式:控制台–>Application–> Storage–>Cookies可在一次响应中设置多个 cookie。格式如下:
键=值; path=?; domain=?; expires=?; max-age=?; secure; httponly
举例:
// 服务端
const Koa = require("koa");
const Router = require("koa-router");
const { bodyParser } = require("@koa/bodyparser");
const app = new Koa();
const router = new Router();
router.post("/api/login", (ctx) => {
const { name, pwd } = ctx.request.body;
if (name === "下雪天的夏风" && pwd === "123") {
ctx.set("set-cookie", 'token=aaa; domain=localhost; max-age=3600;secure; httponly');
ctx.body = "登录成功";
} else {
ctx.body = {
code: 500,
msg: "用户名或密码错误",
};
}
});
router.get("/api/home", (ctx) => {
ctx.body = "home";
});
app.use(bodyParser()).use(router.routes());
app.listen(3000);
<!-- 前端 -->
<form action="http://localhost:3000/api/login" method="post" target="_blank">
<input type="text" name="name" />
<input type="password" name="pwd" />
<button>提交</button>
</form>
form 表单发送请求登录成功后,会自动跳转到页面 http://localhost:3000/api/login
,可以看到 cookie 已经设置了:
path
的默认值是 cookie 所处目录的上级目录。new Date() // Sat Dec 23 2023 01:27:53 GMT+0800 (中国标准时间)
new Date().toISOString() // 2023-12-22T17:27:53.738Z
new Date().toGMTString() // Fri, 22 Dec 2023 17:27:53 GMT
再次访问 http://localhost:3000/api/home
时,会发现请求头中自动带上了 cookie:
格式和在服务端相同,只是 httponly
字段无效。因为该字段本来就是限制在客户端访问的,客户端设置它没有意义。
document.cookie = 'token=aaa; domain=localhost;secure;httponly'
可以修改 cookie 的过期时间即可:max-age=-1
。浏览器会自动删除。
可以让服务器响应一个同样的 domain、同样的 path、同样的 key,只是时间过期的 cookie 即可。
以上面的例子来说,设置如下:
ctx.set("set-cookie", 'token=aaa; domain=localhost; max-age=-1');
或客户端删除:
document.cookie = 'token=aaa; domain=localhost; max-age=-1'
注意:无论是修改还是删除,都需要注意 domain 和 path,因为可能存在 domain 和 path 不同但 key 相同的 cookie。
登录 / 注册请求:
后续请求,浏览器会自动将符合的 cookie 附带到请求中;服务器验证 cookie 后,允许其他操作完成业务流程。
以上。