会话控制

1 会话控制介绍

HTTP 协议是一个无状态的协议,它无法区分多次请求是否发送自同一客户端。而我们在实际的使用中,却又大量的这种需求,我们需要通过会话的控制来解决该问题。常见的会话控制解决方案有 :

  • cookie
  • session
  • token

2 cookke

cookie本质是一个存储在浏览器的文本,随着http请求自动传递给服务器。cookie 通过请求头和响应头实现 cookie 的设置和传输:

  • 服务器以响应头的形式将如何设置 cookie 发送给浏览器。
  • 浏览器收到以后会设置 cookie 并保存。
  • 浏览器再次访问服务器时,会以请求头的形式将 cookie 发送。
  • 服务器就可以通过检查浏览器发送的 cookie 来识别出不同的浏览器。

填写账号和密码校验身份,校验通过后下发 cookie

image-20230327214116490 有了 cookie 之后,后续向服务器发送请求时,就会自动携带 cookie

image-20230327214554199

通过设置响应头 Set-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'
    ]
);

注意: 名字相同但是 PathDomain 不同的 cookie,被认为是不同的 cookie,可以同时存在。

// 删除 cookie,通过把有效期设置为负值
response.setHeader('Set-Cookie', 'grade=; Max-Age=-1');

在 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"});
document.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,通过把有效期设置为负值
document.cookie = 'name2=; Max-Age=-1';

注意:浏览器端 JS 设置 cookie 无法设置 HttpOnly 属性。

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。

1)各个浏览器对 cookie 的数量和大小都有不同的限制,这样就导致我们不能在 cookie 中保存过多的信息。一般数量不超过 50 个,单个大小不超过 4kb。

2)cookie 的内容是由服务器发送给浏览器,再由浏览器将 cookie 发回,如果 cookie 较大会导致发送速度非常慢,降低用户的体验。

3) cookie 内容存储在客户端浏览器,客户端可以对 cookie 内容进行修改,安全性低。

3 session

3.1 session 介绍

Session 是一个对象,存储特定用户会话所需的属性及配置信息。Session是保存在服务器端的数据,保存介质可以是文件、数据库或者内存。

session 运行原理:

1. 服务器中为每一次会话创建一个对象,然后每个对象都设置一个唯一的 ID。
2. 通过设置响应头让浏览器设置 cookie 用于保存该 ID。
3. 将会话中产生的数据统一保存到 session 对象中,这样我们就可以将用户的数据全都保存到服务器中,而不需要保存到客户端,客户端只需要保存一个 ID 即可。

3.2 session 操作流程

填写账号和密码校验身份,校验通过后创建 session 信息,然后将 session_id 的值通过响应头返回给浏览器。

有了cookie,下次发送请求时会自动携带cookie,服务器通过 cookie 中的 session_id 的值确定用 户的身份。

3.2 在 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');

3.3 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
    // store: 该选项可以设置 session 保存在内存、文件或其他,默认 session保存在内存
    cookie: {        // cookie 的属性设置(sessionID 要存在cookie中)
           httpOnly: true, 
           maxAge: 1000*30 
        ...
    }
}));

使用:

// 设置 session (添加或修改)
req.session.userName = "zhangsan";

// 读取 session
req.session;

// 删除 session 中的某个数据
delete req.session.属性名

// 删除所有的 session, 参数十个回调函数
req.session.destroy(() => {})

修改 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'
    }),
    ...
}));

1) 存储位置:

  • cookie 数据存储于客户端。

  • session 数据存储于服务端,一个 session 对象为一个用户浏览器服务。

2) 安全性:

  • cookie 数据是以明文的方式存放在客户端的,安全性较低,可以通过一个加密算法进行加密后存放。

  • session 数据存放于服务器中,所以安全性较好。

3) 网络传输量:

  • cookie 数据会放在请求头上传递给服务器。

  • session 本身存放于服务器,但是通过 cookie 传递 id,会有少量的传送流量。

4) 大小:

  • cookie 保存的数据不能超过4K,很多浏览器都限制一个站点最多保存50个 cookie。

  • session 保存数据理论上没有任何限制。

4 Token

4.1 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 等。

4.2 token 的工作流程

填写账号和密码校验身份,校验通过后响应 token,token 一般是在响应体中返回给客户端的

image-20230328135106885 后续发送请求时,需要手动将 token 添加在请求报文中(cookie是自动携带的),一般是放在请求头中

4.3 Token 的特点

1)无状态:服务器不需要在内存或数据库中存储任何数据,可以在不同的服务器之间共享,提高了系统的可扩展性和可靠性。

2)安全性高:Token 通常使用加密算法进行保护,以确保数据的安全性和保密性。

3)跨平台性强:Token 可以在不同的平台之间使用,如 Web 应用、移动应用、桌面应用等。

4)灵活性高:Token 可以包含用户身份信息和其他元数据,可以根据需要自定义,提高了系统的灵活性和可扩展性。在现代互联网应用中,Token 得到了广泛的应用,如网站登录、API 接口调用、移动应用认证等。

4.4 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:(过期时间)}
})

results matching ""

    No results matching ""