会话控制
会话控制介绍
HTTP 协议是一个无状态的协议,它无法区分多次请求是否发送自同一客户端,而我们在实际的使用中,却又大量需要这种需求,我们需要通过会话的控制来解决该问题。
常见的会话控制解决方案有 :
- cookie
- session
- token
cookie
填写账号和密码校验身份,校验通过后下发 cookie
有了 cookie 之后,后续向服务器发送请求时,就会自动携带 cookie
cookie 实现原理
cookie本质是一个存储在浏览器的文本,随着http请求自动传递给服务器。
- 服务器以响应头的形式将如何设置 cookie 发送给浏览器。
- 浏览器收到以后会设置 cookie 并保存。
- 浏览器再次访问服务器时,会以请求头的形式将 cookie 发送。
- 服务器就可以通过检查浏览器发送的 cookie 来识别出不同的浏览器。
express中间件操作 cookie
在 express 中,通过配置 cookie-parser
中间件,可以将 cookie
解析为一个对象,并为 request
对象添加了一些操作 cookie 属性方法。
安装:
npm install cookie-parser
引入:
const cookieParser = require("cookie-parser");
挂载中间件:
app.use(cookieParser());
使用:
// 设置 cookie (添加或修改) 注意:cookie的属性设置使用小驼峰
res.cookie("userName","laoli",{path: '/getCookie'});
res.cookie("age",18,{maxAge:20*1000,domain:"learn.fuming.site"});
// 读取cookie
req.cookies;
// 删除 cookie
res.clearCookie("userName"); // 注意:默认只删除 path 是 / 的cookie
res.clearCookie("age",{path:"/login"});
// 导入模块
const express = require('express');
const cookieParser = require('cookie-parser');
// 创建服务
const app = express();
// 给应用设置处理cookie的中间件
app.use(cookieParser());
// 路由
app.get('/set', (req, res) => {
// 设置 cookie
res.cookie('username', 'admin');
res.cookie('userid', '932232323');
res.cookie('address', '上海', {maxAge: 3600*1000});
res.cookie('parerts', [10,20,30,40,50]);
// 设置 响应
res.send('cookie 设置成功~~');
});
// 路由
app.get('/get', (req, res) => {
console.log(req.cookies);
res.send(`
cookie 查看成功~~<br>
用户名: ${req.cookies.username}<br>
地址: ${req.cookies.address}
`);
})
// 路由
app.get('/delete', (req, res) => {
res.clearCookie('username');
res.send('cookie删除成功!');
})
// 启动服务
app.listen(8080, () => {
console.log('http server is running on :8080');
});
node操作cookie
通过设置响应头 Set-Cookie
字段来告知浏览器进行 cookie 的添加、修改、删除等操作;通过读取请求头中的 cookie
字段可以读取到 cookie 信息。
读取 cookie
// cookie 信息会在请求头里,返回一个字符串。
request.headers.cookie;
// 可以使用第三方模块 cookie,来把 cookie 字符串处理成对象方便读取。
const cookie = require('cookie');
cookie.parse(request.headers.cookie);
设置(添加或修改)cookie
// 设置 cookie
response.setHeader('Set-Cookie', 'username=anni');
// 同时设置多个 cookie
response.setHeader('Set-cookie', ['age=100', 'address=Shanghai', 'grade=A']);
// 设置 cookie 的同时指定属性
response.setHeader('Set-Cookie', 'username=mingge;path=/a;max-age=3600');
// 同时设置多个 cookie 并指定属性
response.setHeader(
'Set-Cookie',
[
'username=tom; Max-Age=3600',
'addrress=Shanghai; Max-Age=7200; Path=/images; HttpOnly; Secure',
'grade=B; Max-Age=7200; Secure'
]
);
注意: 名字相同但是
Path
和Domain
不同的 cookie,被认为是不同的 cookie,可以同时存在。
删除 cookie
// 删除 cookie,通过把有效期设置为负值
response.setHeader('Set-Cookie', 'grade=; Max-Age=-1');
浏览器JS 中操作 cookie
读取 cookie
document.cookie; // 返回字符串
设置 cookie
// 设置 cookie,给 document.cookie 赋值即可
document.cookie = 'pwd=123123';
// 设置多个 cookie,给 document.cookie 多次赋值,就会设置多个 cookie
document.cookie = 'name1=Jack';
document.cookie = 'name2=Jim';
// 设置 cookie 的同时指定 cookie 的属性
document.cookie = 'name1=Jack; Max-Age=3600; Path=/page';
document.cookie = 'name2=Jams; Max-Age=7200';
删除 cookie
// 删除 cookie,通过把有效期设置为负值
document.cookie = 'name2=; Max-Age=-1';
**注意:**浏览器端 JS 设置 cookie 无法设置 HttpOnly 属性。
cookie 的属性
Domain:可以访问该 cookie 的域名。例如,如果设置为 .fuming.site
,则所有以 fuming.site
结尾的域名都可以访问该 cookie。
Expries: cookie 的最长有效时间。
Max-Age:设置 cookie 失效的时间,单位为毫秒,用来代替原来 Expires,通过它可以计算出其有效时间。Max-Age 如果为正数,则该 cookie 在 Max-Age 秒之后失效。如果为负数,则关闭浏览器时 cookie 即失效,浏览器也不会以任何形式保存该 cookie。
Path:设置 cookie 的使用路径。如果设置为 /index
,则只有路径为 /index
的页面可以访问该 cookie;如果设置为 /
,则本域名下的所有页面都可以访问该 cookie。
HttpOnly:若此属性为 true,则只有在 HTTP 头中会带有此 cookie 的信息,而不能在浏览器端通过 document.cookie
来访问此 cookie。
Secure:设置 cookie 是否仅使用安全协议传输,安全协议有 HTTPS 和 SSL 等,在网络上传输数据之前先将数据加密,该属性默认为 false。
cookie 的缺点
1)各个浏览器对 cookie 的数量和大小都有不同的限制,这样就导致我们不能在 cookie 中保存过多的信息。一般数量不超过 50 个,单个大小不超过 4kb。
2)cookie 如何设置是由服务器发送给浏览器,再由浏览器将 cookie内容发回,如果 cookie 较大会导致发送速度非常慢,降低用户的体验。
3)cookie 内容存储在客户端浏览器,客户端可以对 cookie 内容进行修改,安全性低。
session
session 实现原理
Session 是一个对象,存储特定用户会话所需的属性及配置信息。Session是保存在服务器端的数据,保存介质可以是文件、数据库或者内存。
session 运行原理:
1. 服务器中为每一次会话创建一个对象,然后每个对象都设置一个唯一的 ID。
2. 通过设置响应头让浏览器设置 cookie 用于保存该 ID。
3. 将会话中产生的数据统一保存到 session 对象中,这样我们就可以将用户的数据全都保存到服务器中,而不需要保存到客户端,客户端只需要保存一个 ID 即可。
session 操作流程
填写账号和密码校验身份,校验通过后创建 session 信息
,然后将 session_id
的值通过响应头返回给浏览器。
有了cookie,下次发送请求时会自动携带cookie,服务器通过 cookie
中的 session_id
的值确定用 户的身份
在 Node 中使用 session
设置 session:
/**
* 主要步骤:
* 1. 创建唯一 ID,使用该 ID 作为文件名
* 2. 将 session 数据写入文件
* 3. 将该 ID 保存在 cookie 中
*/
// 需要用到的第三方模块
const uuid = require('uuid'); // 用于创建 sessionId
const cookie = require('cookie'); // 用来解析 cookie 字符串
// 1. 如果请求头中有 sessionId 的 cookie 信息,就从中获取,否则创建新的 sessionId
if (request.headers.cookie && cookie.parse(request.headers.cookie).sessionid) {
var sessionId = cookie.parse(request.headers.cookie).sessionid;
} else {
var sessionId = uuid.v4();
}
// 2. 拼接存储 session 的文件名路径
let sessionFile = path.join(__dirname, 'sess', sessionId);
// 3. 判断是否存在对应的 session 文件,从文件中读取数据或创建一个新的空对象表示 session 数据
fs.access(sessionFile, (err) => {
if (err) {
// 如果没有相应的 session 存储文件
var sessionData = {};
} else {
// 如果存在相应的session 存储文件,从文件中读取数据
var sessionData = JSON.parse(fs.readFileSync(sessionFile));
}
// 4. 对 session 数据进行设置
sessionData['username'] = 'anni';
sessionData['address'] = '上海';
sessionData['content'] = [100,200,300,400,400,500];
// 5. session 数据转为 json 格式的字符串存如文件中
fs.writeFile(sessionFile, JSON.stringify(sessionData), (err) => {
// 6. 设置 cookie,用于存储 sessionID
response.setHeader('Set-Cookie', `sessionid=${sessionId}`);
response.end('session set success');
});
});
读取 session :
// 1. 读取 cookie 中的 session ID
const sessId = cookie.parse(req.headers.cookie).sessid;
// 2. 读取文件中的 session 数据并解析为对象
const data = JSON.parse(fs.readFileSync(path.join(__dirname, 'sess', sessId)));
销毁 session:
// 1. 读取 cookie 中的 session ID
const sessId = cookie.parse(req.headers.cookie).sessid;
// 2. 删除文件
fs.unlinkSync(path.join(__dirname, 'sess', sessId));
// 3. 设置 cookie 失效
res.set('Set-Cookie', 'sessid='+sessId+';Max-Age=-1');
express 中 使用中间件处理 session
在 express 中,通过配置 express-session
中间件,可以将 cookie
解析为一个对象,并为 request
对象添加了一些操作 cookie 属性方法。
安装:
npm install express-session
引入:
var session = require("express-session");
挂载中间件:
app.use(session({
name: 'sess', // 设置cookie的name,默认值是:connect.sid
secret: 'atguigu', // 参与加密的字符串(又称签名)
saveUninitialized: false, //是否为每次请求都设置一个 cookie 用来存储 session 的 id
resave: false ,// 强制保存 session 即使它并没有变化, 默认为 true,建议设置成 false。
// store: 该选项可以设置 session 保存在内存、文件或其他,默认 session保存在内存
cookie: {
httpOnly: true, // 开启后前端无法通过 JS 操作
maxAge: 1000*30 // 这一条 是控制 sessionID 的过期时间的!!!
}
}));
使用:
// 设置 session (添加或修改)
req.session.userName = "zhangsan";
app.get("/setSession",function (req,res){
req.session.userName = "zhangsan";
res.send("设置成功");
})
// 读取 session
req.session;
app.get("/getSession",function (req,res){
console.log(req.session.userName)
res.send("获取成功")
})
// 删除 session 中的某个数据
delete req.session.属性名
// 删除所有的 session, 参数是个回调函数
req.session.destroy(() => {})
app.get("/delSession",function (req,res){
req.session.destroy(function (){
res.send("删除成功");
})
})
修改 session 存储的位置:
默认 session 会存储在服务器的内存中,下面的案例将 session 的存储位置修改为文件,该案例中需要用到第三方模块 session-file-store
,在使用前请进行安装。
const session = require('express-session');
const FileStore = require('session-file-store')(session);
// 配置 session 中间件
app.use(session({
...
store: new FileStore(),
...
}));
也可以将 session 存储到 MongoDB 数据库中,该案例中需要用到第三方模块 connect-mongodb-session
,在使用前请进行安装。
const session = require('express-session');
const MongoDBStore = require('connect-mongodb-session')(session);
// 配置 session 中间件
app.use(session({
...
store: new MongoDBStore({
uri: 'mongodb://127.0.0.1:27017/project2',
collection: 'sessions'
}),
...
}));
cookie 和 session 的区别
1)存储位置:
- cookie 存储于客户端浏览器。
- session 存储于服务器端(存储介质可以是文件、内存、数据库等),一个 session 对象为一个用户浏览器服务。
2)安全性:
- cookie 是以明文的方式存放在客户端的,安全性较低,可以通过一个加密算法进行加密后存放。
- session 存放于服务器中,所以安全性较好。
3) 网络传输量:
- cookie ,浏览器每次发起请求,都会将该网站的所有cookie放进请求头。
- session 本身存放于服务器,浏览器 cookie 中只存储 ID,只有少量的传送流量。
4)大小:
- cookie 保存的数据不能超过4K,很多浏览器都限制一个站点最多保存50个cookie。
- session 保存数据理论上没有任何限制。
Token
Token 介绍
Token 是一种用于身份验证和授权的机制,token 通常是一个字符串或数字,用于标识用户身份和权限。在用户登录成功后,服务器会生成一个 Token,并将其发送给客户端。客户端在后续的请求中会将该 Token 发送回服务器,服务器使用 Token 来验证用户身份和授权访问相应的资源或服务。与 Cookie 、Session 不同的是,Token 不属于 HTTP 标准,完全由前后端协商而定。
常见的 Token 类型包括 JSON Web Token(JWT)、OAuth Token、Access Token 等。其中,JWT 是一种基于 JSON 的开放标准,定义了一种紧凑且自包含的方式,用于在各个系统之间安全地传递信息。OAuth Token 是一种用于授权的 Token,用于授权第三方应用程序访问用户的资源。Access Token 是一种用于访问受保护资源的 Token,通常用于 OAuth 授权流程中。
Token 是一种用于身份验证和授权的机制,可以在不同的应用程序之间使用,如 Web 应用、移动应用、桌面应用、微信小程序等。
Token 在客户端浏览器使用的时候,存储位置由开发者决定,可以是 Cookie、localStoreage、sessionStorage、IndexedDB 等。
token 的工作流程
填写账号和密码校验身份,校验通过后响应 token,token 一般是在响应体中返回给客户端的
后续发送请求时,需要手动
将 token 添加在请求报文中(cookie是自动携带的),一般是放在请求头中
Token 的特点
1)无状态:服务器不需要在内存或数据库中存储任何数据,可以在不同的服务器之间共享,提高了系统的可扩展性和可靠性。
2)安全性高:Token 通常使用加密算法进行保护,以确保数据的安全性和保密性。
3)跨平台性强:Token 可以在不同的平台之间使用,如 Web 应用、移动应用、桌面应用等。
4)灵活性高:Token 可以包含用户身份信息和其他元数据,可以根据需要自定义,提高了系统的灵活性和可扩展性。在现代互联网应用中,Token 得到了广泛的应用,如网站登录、API 接口调用、移动应用认证等。
JWT
JWT(JSON Web Token )是目前最流行的跨域认证解决方案,可用于基于 token
的身份验证,JWT 使 token 的生成与校验更规范。扩展阅读: https://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html
我们可以使用 jsonwebtoken 包
来操作 Token
//导入 jsonwebtokan
const jwt = require('jsonwebtoken');
//创建 token
// jwt.sign(数据, 加密字符串, 配置对象)
let token = jwt.sign({
username: 'zhangsan'
}, 'atguigu', {
expiresIn: 60 //单位是 秒
})
//解析 token
// jwt.verify(token,加密字符串,回调函数)
jwt.verify(token, 'atguigu', (err, data) => {
if(err){
console.log('校验失败~~');
return
}
console.log(data);// { username: '张三', iat: (创建时间), exp:(过期时间)}
})
token
token是session的升级版
token也是在后端存储,发给前端,前端自己存储下来。前端可以运行到app,小程序上,不一定存储到浏览器上。
JSON Web Token(JWT),也称为 token, 是一种开放标准,用于在客户端和服务器之间传递安全可靠的信息。它通过加密算法将用户的身份信息打包成一个 token,然后在客户端和服务器之间进行传输和验证。
JWT 通常包含三部分:头部(header)、载荷(payload)和签名(signature),其中头部和载荷使用 Base64 编码,签名使用加密算法进行加密。在前端开发中,通常使用 JWT 来进行身份验证和授权,以保护用户的隐私和安全。
后端
1. 使用 用户名 进行加密
2. token 存在有效期
前端使用 token 流程:
1. 用户在登录页面输入用户名和密码,然后点击登录按钮。
2. 前端发送登录请求到服务器,服务器验证用户身份,并生成一个 token。
3. 服务器将生成的 token 返回给前端。
4. 前端将 token 存储到本地存储(localStorage)中,以便后续使用。
5. 用户在进行需要身份验证的操作时,前端从本地存储中获取 token。
6. 前端将 token 添加到请求头中,然后发送请求到服务器。
7. 服务器验证 token 的有效性,并根据 token 中的信息判断用户是否有权限进行该操作。
8. 服务器返回响应结果给前端。
记账本注册登录功能
1. 用户路由
① 注册页面
GET /users/reg
② 执行注册
POST /users/reg
③ 登录页面
GET /users/login
③ 执行登录
POST /users/login
④ 退出登录
GET /users/logout
2. 注册用户
数据库创建 users 集合,创建对应的 schema、model
将用户信息添加到集合中
用户名是唯一的,否则无法注册成功
密码经过md5加密之后存储在数据库
3. 登录
根据提交的用户名和密码从数据库查找,如果找到说明存在用户,登录成功,如果找不到,登录失败
登录成功之后,将用户信息存储在 session 中
4. 全局登录验证
有些页面只有登录之后才能访问,如果没有登录,跳转到登录页
哪些必须登录之后才允许访问
GET /account
GET /account/create
POST /account/create 在account路由前面加中间件如果有session信息执行next()
GET /account/delete/:id 没有的话重定向。
哪些不需登录就可以访问
GET /users/reg
POST /users/reg
GET /users/login
POST /users/login
GET /users/logout
如何验证是否登录? 看能否获取到session
将用户信息显示在账单首页上
5. 退出登录
销毁session
6. 账单
账单集合,每个文档添加一个属性,记录用户id
添加账单的时候,将用户id添加进去
查询账单查询只查询该用户的账单