抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

摘要:本文了解了什么是会话控制,以及如何使用Cookie和Session管理会话并进行身份验证。

环境

Windows 10 企业版 LTSC 21H2
Node 18.14.0
NPM 9.3.1
NVM 1.1.12

1 简介

会话是服务器与客户端之间的一次交互过程,用于在多次请求中保持用户的状态。

会话控制是对会话进行控制,因为HTTP是一个无状态的协议,无法区分多次的请求是否来自同一客户端,所以需要通过会话控制解决该问题。

会话控制是实现用户认证、权限控制等核心功能的关键。

2.1 简介

Cookie是服务器发送到客户端浏览器并保存在浏览器本地的数据,占用空间小,通过域名区分数据所属的服务器。

浏览器向服务器发送请求时,会自动将当前域名下可用的Cookie设置在请求头中传递给服务器。

2.2 使用

2.2.1 设置

语法:

js
1
res.setHeader('Set-Cookie', 'name=value; Max-Age=value; Path=value; HttpOnly=value; Secure=value; SameSite=value');

参数:

  • Max-Age:设置过期时间,单位是秒。
  • Path:设置作用路径。
  • HttpOnly:是否禁止JS脚本访问,只允许HTTP请求访问,设置为true可以避免XSS攻击。
  • Secure:是否只在HTTPS连接下发送。
  • SameSite:控制发送级别,发送防止CSRF攻击,可选值为Strict/Lax/None。

示例:

js
1
2
3
4
5
6
7
8
9
10
11
// 创建服务
let server = http.createServer((req, res) => {
// 设置Cookie请求
if (req.url == '/info/setCookie') {
// 设置Cookie
res.setHeader('Set-Cookie', 'service=info; Max-Age=60; Path=/info; HttpOnly; Secure; SameSite=Strict');
// 返回响应
res.writeHead(200, { 'Content-Type': 'text/html;charset=UTF-8' });
res.end('<h1>设置成功</h1>');
}
});

2.2.2 读取

语法:

js
1
req.headers.cookie

示例:

js
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
// 创建服务
let server = http.createServer((req, res) => {
// 读取Cookie请求
if (req.url == '/info/getCookie') {
// 读取Cookie
let cookies = req.headers.cookie ? req.headers.cookie.split('; ') : [];
let service = '';
cookies.forEach((cookie) => {
const [key, value] = cookie.split('=');
if (key == 'service') {
service = value;
}
});
// 返回响应
if (service) {
res.writeHead(200, { 'Content-Type': 'text/html;charset=UTF-8' });
res.end(`
<h1>读取成功</h1>
<p>服务名:${service}</p>
`);
} else {
res.writeHead(500, { 'Content-Type': 'text/html;charset=UTF-8' });
res.end('<h1>读取失败</h1>');
}
}
});

2.2.3 删除

语法:

js
1
res.setHeader('Set-Cookie', 'name=; Max-Age=0');

示例:

js
1
2
3
4
5
6
7
8
9
10
11
// 创建服务
let server = http.createServer((req, res) => {
// 删除Cookie请求
if (req.url == '/info/delCookie') {
// 删除Cookie
res.setHeader('Set-Cookie', 'service=; Max-Age=0');
// 返回响应
res.writeHead(200, { 'Content-Type': 'text/html;charset=UTF-8' });
res.end('<h1>删除成功</h1>');
}
});

3 Session

3.1 简介

Session是保存在服务器本地的数据,可以保存当前访问的用户的相关信息,用于实现会话控制和用户免登录功能。

3.2 流程

用户在登录网站以后,可以在服务端创建Session会话,并将用户信息保存在Session中,同时生成并保存Session对应的sessionId标识,然后将sessionId标识通过Cookie发送到客户端浏览器。

当用户通过浏览器访问网站时,会将Cookie里的sessionId发送给服务器,服务器通过sessionId可以唯一的确定用户的Session数据。

如果sessionId在浏览器的Cookie里没过期,并且能通过sessionId找到用户对应的Session数据。说明已经登录过了,跳过登录直接访问网站内容。

如果sessionId在浏览器的Cookie里过期了,或者不能通过sessionId找到用户对应的Session数据。说明登录时间超时了,或者没有登录过,这时就需要重新登录,保存用户信息到服务器的Session里,同时保存对应的sessionId标识,然后将sessionId标识通过Cookie发送到浏览器保存。

3.3 使用

3.3.1 设置

示例:

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 创建对象存储数据
const sessions = new Map();
// 创建服务
let server = http.createServer((req, res) => {
// 设置Session请求
if (req.url == '/info/setSession') {
// 设置Session
let sessionId = `${Date.now()}_${Math.random()}`;
let sessionData = { service: 'info' };
sessions.set(sessionId, sessionData);
// 设置Cookie
res.setHeader('Set-Cookie', `sessionId=${sessionId}; Max-Age=60; Path=/info; HttpOnly; Secure; SameSite=Strict`);
// 返回响应
res.writeHead(200, { 'Content-Type': 'text/html;charset=UTF-8' });
res.end('<h1>设置成功</h1>');
}
});

生成sessionId用于在Session中管理数据,并在Cookie中设置sessionId用于识别会话。

3.3.2 读取

示例:

js
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
// 创建对象存储数据
const sessions = new Map();
// 创建服务
let server = http.createServer((req, res) => {
// 读取Session请求
if (req.url == '/info/getSession') {
// 读取Cookie
let cookies = req.headers.cookie ? req.headers.cookie.split('; ') : [];
// 读取Session
let sessionId = '';
cookies.forEach(cookie => {
const [key, value] = cookie.split('=');
if (key == 'sessionId') {
sessionId = value;
};
});
// 返回响应
if (sessionId) {
let sessionData = sessions.get(sessionId);
if (sessionData) {
res.writeHead(200, { 'Content-Type': 'text/html;charset=UTF-8' });
res.end(`
<h1>读取成功</h1>
<p>服务名:${sessionData.service}</p>
`);
} else {
res.writeHead(500, { 'Content-Type': 'text/html;charset=UTF-8' });
res.end('<h1>读取失败</h1>');
}
} else {
res.writeHead(500, { 'Content-Type': 'text/html;charset=UTF-8' });
res.end('<h1>读取失败</h1>');
}
}
});

3.3.3 删除

示例:

js
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
// 创建对象存储数据
const sessions = new Map();
// 创建服务
let server = http.createServer((req, res) => {
// 删除Session请求
if (req.url == '/info/delSession') {
// 读取Cookie
let cookies = req.headers.cookie ? req.headers.cookie.split('; ') : [];
// 读取Session
let sessionId = '';
cookies.forEach(cookie => {
const [key, value] = cookie.split('=');
if (key == 'sessionId') {
sessionId = value;
};
});
// 返回响应
if (sessionId) {
sessions.delete(sessionId);
res.writeHead(200, { 'Content-Type': 'text/html;charset=UTF-8' });
res.end('<h1>删除成功</h1>');
} else {
res.writeHead(500, { 'Content-Type': 'text/html;charset=UTF-8' });
res.end('<h1>读取失败</h1>');
}
}
});

3.4 比较

使用Cookie和Session的区别:

名称 Cookie Session
存储位置 客户端浏览器 服务器
数据量 通常限制在4KB 可存储更多数据
生命周期 可设置过期时间 通常在会话结束或超时后销毁
安全性 容易被篡改 更安全,数据存储在服务器端
用途 用户偏好设置、会话标识符 用户认证、购物车数据
适用场景 简单的用户状态管理 复杂的用户会话管理

4 Token

4.1 简介

Token是由服务端创建并保存在客户端的数据,客户端存储Token并在请求时传递给服务器,用于验证用户身份和授权访问,是无状态的认证机制。

4.2 流程

用户登录后,服务器使用存储在服务器的秘钥对用户的相关信息进行加密,将加密后的密文作为Token返回给客户端。

客户端保存Token,并在后续请求的请求头中带上Token,服务器通过对Token解密进行验证来确认用户身份。

验证通过则允许访问资源,验证失败则拒绝访问资源。

4.3 类型

4.3.1 Session

Session是一种特殊的Token,在用户登录后,服务器生成一个唯一的会话ID,并将其存储在服务器端,同时将该ID发送给客户端。

4.3.2 JWT

JWT(JSON Web Token)是一种自包含的令牌,包含用户信息和签名,可以在客户端和服务器之间传递。

4.3.3 OAuth

OAuth是一种用于授权的令牌,通常用于第三方应用访问用户资源。

4.4 区别

比较Session和Token的区别:

名称 Session Token
状态保持 服务器需要维护每个用户的Session信息,因此是有状态的。 服务器通过验证Token的签名来确认用户身份,无需存储用户状态,因此是无状态的。
存储位置 服务器将用户状态存储在Session中,客户端通过Cookie存储sessionId,通过sessionId在服务器查询用户状态。 服务器不存储用户状态,客户端通过LocalStorage或Cookie存储Token,通过Token在服务器解析用户信息。
传输方式 通常通过Cookie将sessionId传递给服务器。 通常通过请求头将Token传递给服务器。
安全问题 面临的主要安全威胁是CSRF(跨站请求伪造)攻击,因为Cookie会自动附带在每个请求中。可以通过设置HttpOnly和Secure标志来增强安全性。 面临的主要安全威胁是XSS(跨站脚本攻击),因为Token通常存储在LocalStorage中,可能被恶意脚本读取。传输过程中使用HTTPS可以防止中间人攻击。
性能 服务器需要存储和管理Session数据,分布式系统需要将Session存储到分布式数据库中,增加了服务器的内存或存储负担。 服务器无需存储用户状态,减少了服务器负担,适合分布式系统,但每次请求都需要验证Token的签名,增加了计算开销。
适用场景 适合需要服务器维护用户状态的场景,特别是在用户量不大且对安全性要求较高的情况下。 适合分布式系统、单点登录(SSO)和需要高扩展性的场景

5 JWT

5.1 简介

JWT(JSON Web Token)是目前最流行的跨域认证解决方案,可用于基于Token的身份验证,使Token的生成与校验更规范。

5.2 使用

使用JWT通常会借助一些成熟的库来简化操作,比如jsonwebtoken库,使用npm install jsonwebtoken命令安装。

5.2.1 签发

对用户信息进行签名,获得JWT。

示例:

js
1
2
3
4
5
6
7
8
9
10
11
12
13
// 引入模块
const jwt = require('jsonwebtoken');
// 签发Token
function signToken(payload, secret, options) {
try {
return jwt.sign(payload, secret, options);
} catch (error) {
console.error('签发失败: ' + error.message);
return null;
}
}
let token = signToken({ username: '张三', sex: '男' }, 'salt', { expiresIn: 600 });
console.log(token);

参数:

  • payload:包含用户信息的对象。
  • secret:用于签名的密钥。
  • options:可选参数。
  • expiresIn:令牌过期时间,单位是秒。

5.2.2 验证

对JWT进行验证,获得用户信息。

示例:

js
1
2
3
4
5
6
7
8
9
10
11
12
13
// 引入模块
const jwt = require('jsonwebtoken');
// 验证Token
function verifyToken(token, secret) {
try {
return jwt.verify(token, secret);
} catch (error) {
console.error('验证失败: ', error.message);
return null;
}
}
let data = verifyToken('eJhbGciO.NzQ0ODA4.vlc8FbdY', 'salt');
console.log(data);

参数:

  • token:需要验证的JWT。
  • secret:用于签名的密钥。

评论