摘要:本文了解了什么是会话控制,以及如何使用Cookie和Session管理会话并进行身份验证。
环境
Windows 10 企业版 LTSC 21H2
Node 18.14.0
NPM 9.3.1
NVM 1.1.12
1 简介
会话是服务器与客户端之间的一次交互过程,用于在多次请求中保持用户的状态。
会话控制是对会话进行控制,因为HTTP是一个无状态的协议,无法区分多次的请求是否来自同一客户端,所以需要通过会话控制解决该问题。
会话控制是实现用户认证、权限控制等核心功能的关键。
2 Cookie
2.1 简介
Cookie是服务器发送到客户端浏览器并保存在浏览器本地的数据,占用空间小,通过域名区分数据所属的服务器。
浏览器向服务器发送请求时,会自动将当前域名下可用的Cookie设置在请求头中传递给服务器。
2.2 使用
2.2.1 设置
语法:
js1
| 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。
示例:
js1 2 3 4 5 6 7 8 9 10 11
| let server = http.createServer((req, res) => { if (req.url == '/info/setCookie') { 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
示例:
js1 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) => { if (req.url == '/info/getCookie') { 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 删除
语法:
js1
| res.setHeader('Set-Cookie', 'name=; Max-Age=0');
|
示例:
js1 2 3 4 5 6 7 8 9 10 11
| let server = http.createServer((req, res) => { if (req.url == '/info/delCookie') { 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 设置
示例:
js1 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) => { if (req.url == '/info/setSession') { let sessionId = `${Date.now()}_${Math.random()}`; let sessionData = { service: 'info' }; sessions.set(sessionId, sessionData); 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 读取
示例:
js1 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) => { if (req.url == '/info/getSession') { let cookies = req.headers.cookie ? req.headers.cookie.split('; ') : []; 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 删除
示例:
js1 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) => { if (req.url == '/info/delSession') { let cookies = req.headers.cookie ? req.headers.cookie.split('; ') : []; 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。
示例:
js1 2 3 4 5 6 7 8 9 10 11 12 13
| const jwt = require('jsonwebtoken');
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进行验证,获得用户信息。
示例:
js1 2 3 4 5 6 7 8 9 10 11 12 13
| const jwt = require('jsonwebtoken');
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:用于签名的密钥。
条