HTTP 协议是无状态的,即服务器无法识别两条 http 请求是否由同一客户端发起,cookie 和 session 就是用来记录状态、确定请求的客户端身份。
cookie
概念
cookie 由服务端发起,对不同的用户发送不同的标识,客户端在之后的请求头中加入这个标识,服务端就可以通过这个标识来识别用户的身份,步骤如下:
- 服务端向客户端发送 cookie
- 使用 HTTP 协议规定的 set-cookie 头操作
- 规范规定的 cookie 格式为
name=value
- 浏览器保存 cookie
- 之后的请求中携带该 cookie
在项目中使用
cookie-parser 中间件方便了对 cookie 的解析,可以将 cookie 解析为 {name: value}
格式的对象
yarn add cookie-parser
引入项目
const cookieParser = require('cookie-parser');
app.use(cookieParser());
app.get('/', (req, res) => {
if (req.cookies.myCookie) {
console.log(req.cookies.myCookie); // 读取 cookie
res.end();
} else {
res.cookie('myCookie', 'foobar', {maxAge: 1000 * 60}); // 存储 cookie,1分钟
res.end();
}
})
cookieParser(secret, options)
有两个参数,第一个用来对 cookie 签名,可以是字符串或数组,如果是数组,解密时会一一尝试数组中的每一项作为加密字符串;第二个 options 是可选参数,包括:
- path: 指定 cookie 影响的路径
- expires: cookie 失效时间,UTC 时间格式
- maxAge: cookie 多久后失效,单位毫秒,不设置 expires 和 maxAge 时,会产生 session cookie, session cookie 在关闭浏览器时会被清除,一般用来保存 session 的 session_id
- secure: 当 secure 为 true 时,在 HTTPS 中有效,在 HTTP 中无效;否则在 HTTP 中有效,HTTPS 中无效
- httpOnly: 为 true 时,浏览器不允许脚本操作 document.cookie,避免 XSS 攻击拿到 cookie
- domain: 指定域名
signedCookies
对 cookie 进行签名,这样请求头里的 cookie 数据是加密后的,在服务端 cookie-parser 会自动解密
const cookieParser = require('cookie-parser');
app.use(cookieParser('my_cookie_secret')); // 设置签名字符串
app.get('/', (req, res) => {
if (req.signedCookies.myCookie) {
console.log(req.signedCookies.myCookie); // 读取 signedCookies,已解密
res.end();
} else {
// 存储 cookie,设置 signed 为 true 来加密 cookie
res.cookie('myCookie', 'foobar', {maxAge: 1000 * 60, signed: true});
res.end();
}
})
session
cookie 的使用很方便,但是有弊端:cookie 中的数据在客户端可以被修改和伪造,而且 cookie 中的数据字段太多会影响传输效率。为了解决这些问题,就产生了 session,session 中的数据存储在服务端
session 的运作通过 session_id 来进行,session_id 通常存放在客户端的 cookie 中,在 express 中默认是 connect.sid
字段,服务端会检查请求头中 cookie 所保存的 session_id 并与服务端的 session data 关联,进行数据的操作
也就是说,服务端会产生一个 1024 比特长的字符串,存储在 cookie 的 connect.sid
中,之后的请求会带有这个字符串,服务端就可以从 redis 等存储中拿到对应身份的数据
session 可以存放的位置有内存、cookie、redis、memecached 或数据库,通常会存储在 redis 和 memcached 缓存中
在 express 中使用 session 需要 express-session
中间价
yarn add express-session
在项目中使用
const session = require('express-session');
app.use(session({
secret: 'my_session_secret',
cookie: {maxAge: 1000 * 60}
}));
app.get('/', (req, res) => {
if (req.session.mySession) {
console.log(req.session.mySession); // 读取 session
res.end();
} else {
req.session.mySession = 'foobar'; // 写入 session
res.end();
}
})
session(options)
参数
- name: 设置 cookie 中保存 session 的字段名称,默认为
connect.sid
- store: session 的存储方式,默认存放在内存中,也可以存放在 redis 等缓存中
- secret: 签名字符串,用来计算 hash 值并存放在 cookie 中
- cookie: 设置存放 session_id 的 cookie 的相关选项,默认为
{path: '/', httpOnly: true, secure: false, maxAge: null}
- genid: 产生一个新的 session_id 时使用的函数,默认使用 uid2 这个 npm 包
- rolling: 每个请求都重新设置一个 cookie,默认 false
- resave: 即使 session 没有被修改,也保存 session 值,默认 true
- saveUninitialized: 强制未初始化的session保存到数据库
cookie session
cookie session 就是将 session 存储在 cookie 中,比如存储用户数据
const sessionData = {username: 'foo'};
对该数据与 secret 字符串做加密,将加密后的字符串存储到 cookie 的 session_data 字段中,之后的请求解密 session_data 即可
redis 中存储 session
在 redis 中存储 session,需要 redis
和 connect-redis
yarn add redis connect-redis
在项目中使用
const redis = require('redis');
const session = require('session');
let RedisStore = require('connect-redis')(session);
let redisClient = redis.createClient({
host: '127.0.0.1',
port: 6379,
db: 0,
password: ''
});
app.use(session({
secret: 'my_session_secret',
resave: false,
saveUnintialized: false,
store: new RedisStore({client: redisClient})
}))