nodejs -会话控制学习笔记

一、介绍

会话控制就是 对会话(根据用户)进行控制。

HTTP 是一种无状态的协议,它没有办法区分多次的请求是否来自于同一个客户端(客户),无法区分用户,而产品中又大量存在这样的需求,所以需要会话控制解决该问题。

常见的会话控制技术有三种:

  • cookie
  • session
  • token

二、cookie

1、是什么

cookie 是 HTTP 服务器发送到用户浏览器并保存在本地的一小块数据(不需要前端手动存储,后端设置cookie以后,响应头上会上,之后,前端发送任何请求,都会自动将 cookie 携带在请求头中)。

cookie 是保存在浏览器的一小块数据,是按照域名划分的。

1-2、特点

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

1-3、cookie 的运行流程

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

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

浏览器操作 cookie 相对较少,仅做了解

  1. 禁用所有 cookie
  2. 删除 cookie
  3. 查看 cookie

控制台 -》 应用(Application) -》Cookies

控制台 -》 网络(NetWork) -》 请求头

浏览器 -》 设置

js 复制代码
const express = require('express')
const app = express()
// 获取 cookie 时需要用到 cookie-parser
const cookieParser = require('cookie-parser')
app.use(cookieParser())

app.get('/', (req, res) => {
  res.send('home')
})

// 设置 cookie
app.get('/setCookie', (req, res) => {
  // 设置 cookie,会在浏览器关闭(退出后台)的时候销毁
  // 如果提前打开了两个 独立 的tab页,把 访问http://127.0.0.1:3000/的页面关掉以后
  // 另一个页面还是会存在 cookie
  res.cookie('name', 'zhangsan')

  // 设置 cookie 键值对和过期时间,maxAge 单位是毫秒,30 * 1000 = 30s
  // cookie 会在 30 秒之后销毁,常用于做 7天免登录
  // res.cookie('name','zhangsan',{maxAge: 30 * 1000})

  res.cookie('theme', 'blue')
  res.send('cookie')
})

// 删除 cookie
app.get('/removeCookie', (req, res) => {
  // 删除 cookie,参数是必填项
  res.clearCookie('theme')
  res.send('成功删除cookie')
})

// 获取 cookie
app.get('/getCookie', (req, res) => {
  // 获取cookie
  // res.send( req.cookies)
  res.send( req.cookies.name)
  // res.send( req.cookies.theme)
})

app.listen(3000, () => {
  console.log('服务器已经启动....')
})

三、session

1、是什么

session 是保存在 服务端的一块数据,保存当前访问用户的信息。

2、作用

实现会话控制,可以识别用户的身份,快速获取当前用户的相关信息。

3、运行流程

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

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

4、代码对 session 增删查

js 复制代码
const express = require('express')

// 下载 npm i express-session connect-mongo
// 引入 express-session、connect-mongo
const session = require('express-session')
const MongoStore = require('connect-mongo')


const app = express()
app.use(session({
  name: 'sid',    // 设置 cookie 的 name,默认值是:connect.sid
  secret: 'zhangsan',   // 参与加密的字符串(又称签名)
  saveUninitialized: false,   // 是否为每次请求都设置一个cookie用来存储session的id
  resave: true,   // 是否在每次请求时重新保存session,
  // 场景:为 true 时,假如一个网站的session的过期时间时 20 分钟,每次请求之后,会重置这个时间
  // 直到不做任何请求之后的 20 分钟,才会销毁session_id
  store: MongoStore.create({
    mongoUrl: 'mongodb://127.0.0.1:27017/bilibili'  // 数据库的连接配置
  }),
  cookie: {
    httpOnly: true,   // 开启后前端无法通过 js 操作(document.cookie)获取到 cookie
    maxAge: 1000 * 60 * 5 // sessionID 的过期时间 5 分钟
  }
}))

// 设置 session
app.get('/login', (req, res) => {
  if(req.query.username === 'admin' && req.query.password === 'admin') {
    // 设置 session 信息
    req.session.username = 'admin'
    req.session.uid = '258aefefdf'
    res.send('登录成功')
  } else {
    res.send('登录失败')
  }
})

// session 的读取
app.get('/cart', (req, res) => {
  // 判断用户是否登录--》检测session 是否存在用户数据
  if(req.session.username) {
    res.send(`购物车页面欢迎你 ${req.session.username}`)
  } else {
    res.send('你还没有登录')
  }
})

// session 销毁
app.get('/logout', (req, res) => {
  req.session.destroy(() => {
    res.send('退出成功')
  })
})

app.get('/', (req, res) => {
  res.send('首页')
})

app.listen(3000, () => {
  console.log('服务启成功')
})

cookie 和 session 的区别主要有以下几点:

  1. 存放的位置
    cookie:浏览器端
    session: 服务端
  2. 安全性
    cookie 是以明文的方式存放在客户端的,安全性相对较低
    session 存放在 服务端,所以安全性相对较好
  3. 网络传输量
    cookie 设置内容过多会增大报文体积,影响传输效率
    session 数据存储在服务器,只能通过 cookie 传递 id,所以不影响传输效率
  4. 存储显示
    浏览器限制单个 cookie 保存的数据不能超过 4k,且单个域名下的存储数量也有限制
    session 数据存储在 服务器 中,所以没有这个限制

五、token

1、是什么

token 时服务端生成并返回给 HTTP 客户端的一串加密字符串,token 中保存着用户信息

2、作用

实现会话控制,可以识别用户的身份,主要用于移动端 app

3、token 的工作流程

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

后续发送请求时,需要手动将 token 添加在请求报文中,一般是放在请求头中

4、特点

  1. 服务端压力更小
    数据存储在客户端
  2. 相对更安全
    数据加密
    可以避免 CSRF(跨站请求伪造)
  3. 扩展性更强
    服务可以共享
    增加服务节点更简单

5、JWT

JWT (JSON Web Token)是目前最流行的跨域认证解决方案,可用于 基于 token 的身份验证

JWT 使 token 的生成与校验更规范
npm i jsonwebtoken

js 复制代码
const jwt = require('jsonwebtoken')

// 创建 token
// let token = jwt.sign({
//   username: 'zhangsan'
// }, 'nodejs-token', {
//   expiresIn: 60 // 单位是 秒
// })

// token eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InpoYW5nc2FuIiwiaWF0IjoxNzIxODkyMjMzLCJleHAiOjE3MjE4OTIyOTN9.EpSHpeYuf7KOjptpTftrSZyAJ1234xvcgnvc6_PBdYQ
// console.log(token)

let t = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InpoYW5nc2FuIiwiaWF0IjoxNzIxODkyMjMzLCJleHAiOjE3MjE4OTIyOTN9.EpSHpeYuf7KOjptpTftrSZyAJ1234xvcgnvc6_PBdYQ'
// 校验 token
jwt.verify(t, 'nodejs-token', (err, data) => {
  if(err) {
    console.log('校验失败')
    return
  }
  console.log(data) // { username: 'zhangsan', iat: 1721892343, exp: 1721892403 }
})

流程:

  1. 登录成功后,服务端生成 token,并返回给前端
js 复制代码
const express = require('express')
const router = express.Router()
const md5 = require('md5')
const UserModel = require('../../models/UserModel')
const jwt = require('jsonwebtoken')


// 登录功能的路由
router.post('/login', (req, res) => {
  let { username, password } = req.body
  UserModel.findOne({username, password: md5(password)}).then((data) => {
    if(!data) {
      return res.json({
        code: '2001',
        msg: '账号或密码错误',
        data: null
      })
    }

    req.session.username = data.username
    req.session._id = data._id

    // res.render('success', {msg:'登录成功',url:'/account'})

    // 创建当前用户的 token
    // nodejs-token 为加密字符串
    let token = jwt.sign({
      username: data.username,
      _id: data._id
    }, 'nodejs-token', {
      expiresIn: 60 * 60 * 24 * 7   // token 过期时间为 1 周
    })
    res.json({
      code: '0000',
      msg: '登录成功',
      data: token
    })
  }).catch(() => {
    // res.render('success', {msg:'登录失败',url:'/login'})
    // res.status(500).send('登录失败,请稍后重试')
    res.json({
      code: '2002',
      msg: '登录失败,请稍后重试',
      data: null
    })
  })
})

module.exports = router
  1. 前端拿到 token 后,在每次请求时,都要将 token 写到请求头中
  2. 前端请求时,将token放入请求头后,后端处理请求:
    api/auth.js
js 复制代码
const express = require('express');
const router = express.Router();

const AccountModel = require('../../models/AccountModel')
const dayjs = require('dayjs')

// 配置中间件 =》校验 token
const CheckTokenMiddleware = require('../../middlewares/CheckTokenMiddleware')

router.post('/account', CheckTokenMiddleware,(req, res, next) => {
  AccountModel.create({
    ...req.body,
    time: dayjs(req.body.time).format()
  }).then((data) => {
    console.log(data)
    // res.render('success', {msg:'添加成功了',url:'/account'})

    res.json({
      code: '0000',
      msg: '创建成功',
      data: data
    })
  }).catch(() => {
    // res.status(500).send('删除失败了')

    res.json({
      code: '1002',
      msg: '创建失败',
      data: null
    })
  })
});

router.get('/account/remove/:id', CheckTokenMiddleware, (req, res) => {
  //获取 params 的 id 参数
  let id = req.params.id
  AccountModel.deleteOne({_id: id}).then((data) => {
    // console.log(data)
    // res.render('success', {msg:'删除成功了',url:'/account'})

    res.json({
      code: '0000',
      msg: '删除成功',
      data: {}
    })
  }).catch(() => {
    // res.status(500).send('删除失败了')

    res.json({
      code: '1003',
      msg: '删除失败',
      data: null
    })
  })
})
module.exports = router;

models/AccountModel.js

js 复制代码
const mongoose = require('mongoose')

const AccountSchema = new mongoose.Schema({
  title: {
    type: String,
    required: true
  },
  time: Date,
  type: {
    type: Number,
    default: -1
  },
  account: {
    type: Number,
    required: true
  },
  remarks: String
})
const AccountModel = mongoose.model('account', AccountSchema)
module.exports = AccountModel

middlewares/CheckTokenMiddleware.js

js 复制代码
const jwt = require('jsonwebtoken')

module.exports = (req, res, next) => {
  const token = req.get('token')
  jwt.verify(token, 'nodejs-token', (err, data) => {
    if(err) {
      return res.json({
        code: '2003',
        msg: 'token 校验失败',
        data: null
      })
    }
    
    next()
  })
}

六、扩展

1、本地域名

所谓本地域名就是 只能在本机使用的域名 ,一般在开发阶段使用

1-1、操作流程

编辑文件 C:\Windows\System32\drivers\etc\hosts

127.0.0.1 www.baidu.com

如果修改失败, 可以修改该文件的权限

1-2、原理

在地址栏输入 域名 后,浏览器会先进行 DNS (Domain Name System)查询,湖区该域名对应的 IP 地址

请求会发送到 DNS 服务器,可以 根据域名返回 IP 地址

可以通过 ipconfig /all查看本机的 DNS 服务器
hosts 文件也可以设置域名与 IP 的映射关系,在发送请求前,可以通过该文件获取域名的 IP 地址

相关推荐
守护者17011 分钟前
JAVA学习-练习试用Java实现“使用Arrays.toString方法将数组转换为字符串并打印出来”
java·学习
学会沉淀。20 分钟前
Docker学习
java·开发语言·学习
Rinai_R34 分钟前
计算机组成原理的学习笔记(7)-- 存储器·其二 容量扩展/多模块存储系统/外存/Cache/虚拟存储器
笔记·物联网·学习
吃着火锅x唱着歌34 分钟前
PHP7内核剖析 学习笔记 第四章 内存管理(1)
android·笔记·学习
ragnwang37 分钟前
C++ Eigen常见的高级用法 [学习笔记]
c++·笔记·学习
胡西风_foxww1 小时前
【es6复习笔记】rest参数(7)
前端·笔记·es6·参数·rest
Web阿成2 小时前
3.学习webpack配置 尝试打包ts文件
前端·学习·webpack·typescript
雷神乐乐3 小时前
Spring学习(一)——Sping-XML
java·学习·spring
李雨非-19期-河北工职大3 小时前
思考: 与人交际
学习