前言
由于浏览器无状态的特性,cookie
技术应运而生,cookie
是一个会话级的存储,大小 4KB
左右,用于浏览器将服务器设置的信息重新带给服务器进行验证,不支持跨域,在浏览器清空缓存或超过有效期后失效,不能存放敏感信息,session
是专门用于存储最初设置给浏览器 cookie
数据的地方,我们本篇就来讨论一下 cookie
和 session
在 NodeJS 中的使用方式。
cookie 的基本使用
1、NodeJS 原生操作 cookie
下面是 cookie
在 Node 原生中的读取和写入方法。
原生中操作 cookie
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
复制代码
| const http = require("http");
// 创建服务
http.createServer((req, res) => {
if (req.url === "/read") {
// 读取 cookie
console.log(req.headers.cookie);
res.end(req.headers.cookie);
} else if (req.url === "/write") {
// 设置 cookie
res.setHeader("Set-Cookie", [
"name=panda; domain=panda.com; path=/write; httpOnly=true",
`age=28; Expires=${new Date(Date.now() + 1000 * 10).toGMTString()}`,
`address=${encodeURIComponent("回龙观")}; max-age=10`
]);
res.end("Write ok");
} else {
res.end("Not Found");
}
}).listen(3000);
复制代码
|
上面代码创建了一个 http
服务器,可以通过读取 cookie
请求头的值来获取浏览器发来的 cookie
,服务器可以通过给浏览器设置响应头 Set-Cookie
实现对浏览器 cookie
的设置,多个 cookie
参数为数组,在数组内可以规定每一条 cookie
的规则,中间使用一个分号和一个空格隔开。
-
domain
用来设置允许访问 cookie
的域; -
path
用来设置允许访问 cookie
的路径; -
httpOnly
用来设置是否允许浏览器中修改 cookie
,如果通过浏览器修改设置过 httpOnly=true
的 cookie
,则会增加一条同名 cookie
,原来的 cookie
不会被修改; -
Expires
用来设置过期时间,绝对时间,值为一个 GMT
或 UTC
格式的时间; -
max-age
同样用来设置过期时间,相对时间,值为一个正整数,单位 s
。
cookie
默认不支持存储中文,如果存储中文需先使用 encodeURIComponent
方法进行转译,将转译后的结果存入 cookie
,在浏览器获取 cookie
需使用 decodeURIComponent
方法转回中文。
2、Koa 中操作 cookie
Koa
是当下流行的 NodeJS 框架,是对原生 Node 的一个轻量的封装,但是内部实现了快捷操作 cookie
的方法,下面是原生中对 cookie
的操作在 Koa
中的写法。
Koa 中操作 cookie
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
复制代码
| const Koa = require("koa");
const Router = require("koa-router");
// 创建服务和路由
const app = new Koa();
const router = new Router();
// 签名需要设置 key
app.keys = ["shen"];
router.get("/read", (ctx, next) => {
// 获取 cookie
let name = ctx.cookies.get(name) || "No name";
let name = ctx.cookies.get(age) || "No age";
ctx.body = `${name}-${age}`;
});
router.get("/write", (ctx, next) => {
// 设置 cookie
ctx.cookies.set("name", "panda", { domain: "panda.com" });
ctx.cookies.set("age", 28, { maxAge: 10 * 1000, signed: true });
});
// 使用路由
app.use(router.routes());
app.listen(3000);
复制代码
|
在 Koa
中将获取和设置 cookie
的方法都挂在了 ctx
上下文对象的 cookies
属性上,分别为 get
和 set
。
cookies.get
的参数为获取 cookie
的键名,返回值为键对应的值,cookies.set
的第一个参数同样为 cookie
的键名,第二个参数为键对应的值,第三个参数为一个对象,用来配置该条 cookie
的规则,如 domain
、path
和过期时间等,这里 maxAge
值为毫秒数。
注意:Koa
中设置的 cookie
默认不允许浏览器端通过 document.cookie
获取,但是服务器也可以被欺骗,比如使用 postman
发送一个带 Cookie
请求头的请求,服务器可以通过设置签名来预防,即添加 signed
选项并将值设置为 true
。
3、Koa 操作 cookie 方法的原理
cookies
对象都是挂在 ctx
上来实现的,使用过 Koa
都知道如果要操作 ctx
就会用到中间件的思想,我们这就看看这两个方法使用原生封装的过程。
Koa 中 ctx.cookies 对象 get 和 set 方法的原理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
复制代码
| const Koa = require("koa");
const querystring = require("querystring");
const app = new Koa();
app.use(async (ctx, next) => {
// 获取 cookie
const get = key => {
let cookies = ctx.get("cookie") || "";
return querystring.parse(result, "; ")[key];
};
// 设置 cookie,存储所有的 cookie,等于 setHeader 中的第二个参数
let cookies = [];
const set = (key, val, options = {}) => {
// 用于构造单条 cookie 和权限等设置的数组,默认存放这条 cookie 的键和值
let single = [`${key}=${encodeURIComponent(val)}`];
// 下面是配置
if (options.domain) {
arr.push(`domain=${options.domain}`);
}
if (options.maxAge) {
arr.push(`Max-Age=${options.maxAge}`);
}
if (options.path) {
arr.push(`path=${options.path}`);
}
if (options.httpOnly) {
arr.push(`HttpOnly=true`);
}
// 将配置组合到 single 中后转为字符串存入 cookies
cookies.push(single.join("; "));
// 设置给浏览器
ctx.set("Set-Cookie", cookies);
}
// 将获取和设置 cookie 的方法挂在 cookies 对象上
ctx.cookies = { get, set };
await next();
});
复制代码
|
在 get
方法内部获取 cookie
请求头的值并根据传入的 key
获取值,set
方法内,将传入的键值和选项拼接成符合 cookie
的字符串,通过 Set-Cookie
响应头设置给浏览器。
session 的基本使用
1、NodeJS 原生使用 session
正常 session
是存放在数据库中的,我们这里为了方便就用一个名为 session
的对象来代替。
原生中使用 session
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
复制代码
| const http = require("http");
const uuid = require('uuid/v1'); // 生成随字符串
const querystring = require("querystring");
// 存放 session
const session = {};
// 创建服务
http.createServer((req, res) => {
if (req.url === "/user") {
// 取出 cookie 存储的用户 ID
let userId = querystring.parse(req.headers["cookie"], "; ")["study"];
if (userId) {
if (session[userId].studyCount === 0) res.end("您的学习次数已用完");
session[userId].studyCount--;
} else {
// 生成 userId
userId = uuid();
// 将用户信息存入 session
session[userId] = { studyCount: 30 };
// 设置 cookie
req.setHeader("Set-Cookie", [`study=${userId}`]);
}
// 响应信息
res.end(`
您的用户 ID 为 ${userId},
剩余学习次数为:${session[userId].studyCount}
`);
} else {
res.end("Not Found");
}
}).listen(3000);
复制代码
|
上面写的案例是一个网校的场景,一个新用户默认有 30
次学习机会,以后每次访问服务器学习次数减 1
,如果 studyCount
值为 0
,则提示学习次数用完,否则提示当前用户的 ID
和剩余学习次数,session
中存储的是每一个用户 ID
对应的剩余学习次数,这样就不会轻易的被修改学习剩余次数,因为服务器只认用户 ID
,再通过 ID
去更改对应的剩余次数(当然忽略了别人冒充这个 ID
的情况,只能减,不能加),这样就不会因为篡改 cookie
而篡改用户存在 session
中的数据,除非连整个数据库都拖走。
2、Koa 中使用 session
我们接下来使用 Koa
实现和上面一摸一样的场景,在 Koa
的社区中提供了专门操作 session
的中间件 koa-session
,使用前需安装。
Koa 中使用 session
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
复制代码
| const Koa = require("koa");
const Router = require("koa-router");
const session = requier("koa-session");
const uuid = require("uuid/v1");
// 创建服务和路由
const app = new Koa();
const router = new Router();
// cookie 的签名
app.keys = ["panda"];
// 使用 koa-session 中间件
app.use(session({
key: "shen",
maxAge: 10 * 1000
}, app));
router.get("/user", (ctx, next) => {
// 取出 cookie 存储的用户 ID
let userId = ctx.cookie("study");
if (ctx.session.userId) {
if (ctx.session[userId].studyCount === 0) res.end("您的学习次数已用完");
ctx.session[userId].studyCount--;
} else {
// 生成 userId
userId = uuid();
// 将用户信息存入 session
ctx.session[userId] = { studyCount: 30 };
// 设置 cookie
ctx.cookies.set("study", userId);
}
// 响应信息
ctx.body = `
您的用户 ID 为 ${userId},
剩余学习次数为:${session[userId].studyCount}
`;
});
// 使用路由
app.use(router.routes());
app.listen(3000);
复制代码
|
使用 Koa
的 koa-session
以后,不再需要我们创建 session
对象进行存储,并且 cookie-session
中间件帮我们封装了 API 可以直接操作 mongo
和 MySQL
数据库,上面代码中与用原生相比还增加了 cookie
和 session
的签名和过期时间,比原生写起来要方便很多。
总结
本篇内容更偏向于 cookie
和 session
在 NodeJS 中的使用,没有过多的叙述理论性的内容,cookie
和 session
是相互依存的,也就是说共同使用的,现在已经有 JWT 的方案来替代,因为相比较下有很多优点,但某些项目和特殊场景还在使用 cookie
和 session
,所以还是写了这一篇,如果对 JWT 感兴趣可以看 通过一个案例理解 JWT。