
前言
由于浏览器无状态的特性,
cookie
技术应运而生,cookie
是一个会话级的存储,大小4KB
左右,用于浏览器将服务器设置的信息重新带给服务器进行验证,不支持跨域,在浏览器清空缓存或超过有效期后失效,不建议存放敏感信息,session
是专门用于存储最初设置给浏览器cookie
数据的地方,我们本篇就来讨论一下cookie
和session
在Node.js
中的使用方式。
cookie 的基本使用
Node.js 原生操作 cookie
下面是 cookie
在 Node 原生中的读取和写入方法。
/* 原生中操作 cookie */
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
方法转回中文。
Koa 中操作 cookie
Koa
是当下流行的 Node.js
框架,是对原生 Node
的一个轻量的封装,但是内部实现了快捷操作 cookie
的方法,下面是原生中对 cookie
的操作在 Koa
中的写法。
/* Koa 中操作 cookie */
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
const name = ctx.cookies.get(name) || 'No name';
const age = 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
。
Koa 操作 cookie 方法的原理
cookies
对象都是挂在 ctx
上来实现的,使用过 Koa
都知道如果要操作 ctx
就会用到中间件的思想,我们这就看看这两个方法使用原生封装的过程。
/* Koa 中 ctx.cookies 对象 get 和 set 方法的原理 */
const Koa = require('koa');
const querystring = require('querystring');
const app = new Koa();
app.use(async (ctx, next) => {
// 获取 cookie
const get = key => {
const cookies = ctx.get('cookie') || '';
return querystring.parse(result, '; ')[key];
};
// 设置 cookie,存储所有的 cookie,等于 setHeader 中的第二个参数
const cookies = [];
const set = (key, val, options = {}) => {
// 用于构造单条 cookie 和权限等设置的数组,默认存放这条 cookie 的键和值
const single = [key + '=' + encodeURIComponent(val)];
// 下面是配置
if (options.domain) {
single.push('domain=' + options.domain);
}
if (options.maxAge) {
single.push('Max-Age=' + options.maxAge);
}
if (options.path) {
single.push('path=' + options.path);
}
if (options.httpOnly) {
single.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 的基本使用
Node.js 原生使用 session
正常 session
是存放在数据库中的,我们这里为了方便就用一个名为 session
的对象来代替。
/* 原生中使用 session */
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 + ',\r\n剩余学习次数为:' + session[userId].studyCount);
} else {
res.end('Not Found');
}
}).listen(3000);
上面写的案例是一个网校的场景,一个新用户默认有 30
次学习机会,以后每次访问服务器学习次数减 1
,如果 studyCount
值为 0
,则提示学习次数用完,否则提示当前用户的 ID
和剩余学习次数,session
中存储的是每一个用户 ID
对应的剩余学习次数,这样就不会轻易的被修改学习剩余次数,因为服务器只认用户 ID
,再通过 ID
去更改对应的剩余次数(当然忽略了别人冒充这个 ID
的情况,只能减,不能加),这样就不会因为篡改 cookie
而篡改用户存在 session
中的数据,除非连整个数据库都拖走。
Koa 中使用 session
我们接下来使用 Koa
实现和上面一摸一样的场景,在 Koa
的社区中提供了专门操作 session
的中间件 koa-session
,使用前需安装。
/* Koa 中使用 session */
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.cookies.get('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 + ',\r\n剩余学习次数为:' + session[userId].studyCount
});
// 使用路由
app.use(router.routes());
app.listen(3000);
使用 Koa
的 koa-session
以后,不再需要我们创建 session
对象进行存储,并且 cookie-session
中间件帮我们封装了 API
可以直接操作 mongo
和 MySQL
数据库,上面代码中与用原生相比还增加了 cookie
和 session
的签名和过期时间,比原生写起来要方便很多。
总结
本篇内容更偏向于
cookie
和session
在Node.js
中的使用,没有过多的叙述理论性的内容,cookie
和session
是相互依存的,也就是说共同使用的,现在已经有 JWT 的方案来替代,因为相比较下有很多优点,但某些项目和特殊场景还在使用cookie
和session
。