【node进阶】深入浅出前后端身份验证(下)---JWT

news2025/1/23 15:00:39

✅ 作者简介:一名普通本科大三的学生,致力于提高前端开发能力
✨ 个人主页:前端小白在前进的主页
🔥 系列专栏 : node.js学习专栏
⭐️ 个人社区 : 个人交流社区
🍀 学习格言: ☀️ 打不倒你的会使你更强!☀️
💯 刷题网站:这段时间有许多的小伙伴在问有没有什么好的刷题网站,博主在这里给大家推荐一款刷题网站:👉点击访问牛客网👈牛客网支持多种编程语言的学习,各大互联网大厂面试真题,从基础到拔高,快来体验一下吧!


在这里插入图片描述
🔥前言

上一篇文章中带领大家学习了session,session身份认证适应于服务端渲染,我们前后端分离项目中用的都是jwt,本篇文章会详细的介绍jwt

📃目录

  • JWT认证机制
    • Session 认证的局限性
    • 什么是 JWT
    • JWT 的工作原理
    • JWT 的组成部分
    • JWT 的三个部分各自代表的含义
    • JWT 的使用方式
  • Express 中使用 JWT
    • 安装 JWT 相关的包
    • 定义 secret 密钥
    • 在登录成功后生成 JWT 字符串
    • 将 JWT 字符串还原为 JSON 对象
    • 使用 req.auth 获取用户信息
    • 捕获解析 JWT 失败后产生的错误
  • 完整示例demo
  • 小结

JWT认证机制

Session 认证的局限性

Session 认证机制需要配合 Cookie 才能实现。由于 Cookie 默认不支持跨域访问,所以,当涉及到前端跨域请求后端接口的时候,需要做很多额外的配置,才能实现跨域 Session 认证。

同时Session存在一定的存储问题,例如:我们有一个集群,我们第一次选择在机器A中登录,第二次在机器B中登录,这样会导致我们的Session会复制来复制去的(如图所示)。
在这里插入图片描述

如果我们想解决这个问题,我们可以把Session挂载到一个单独的机器中去,但是这样的话又会导致一个问题:用户需要重新再登录一遍,这样的话就很烦(如图所示)
在这里插入图片描述
同时,Cookie存储的有效信息容易被CSRF(Cross-site request forgery)跨站请求伪造导致安全性的问题。

注意:

  • 当前端请求后端接口不存在跨域问题的时候,推荐使用 Session 身份认证机制
  • 当前端需要跨域请求后端接口的时候,不推荐使用 Session 身份认证机制,推荐使用 JWT 认证机制

什么是 JWT

JWT(英文全称:JSON Web Token)是目前最流行跨域认证解决方案

JWT 的工作原理

在这里插入图片描述
注意:
CSRF攻击的原因是浏览器会自动带上cookie,而不会带上token
以CSRF攻击为例:
cookie:用户点击了链接,cookie未失效,导致发起请求后后端以为是用户正常操作,于是进行扣款或者盗取网站操作;
token:用户点击链接,由于浏览器不会自动带上token(因为我们把token保存到了localStorage或者sessionStorage中了),所以即使发了请求,后端的token验证不会通过,所以不会进行扣款或者盗取网站操作;
用户的信息通过 Token 字符串的形式,保存在客户端浏览器中。服务器通过还原 Token 字符串的形式来认证用户的身份

JWT 的组成部分

JWT 通常由三部分组成,分别是 Header(头部)Payload(有效荷载)Signature(签名)
三者之间使用英文的“.”分隔,格式如下:

Header.Payload.Signature

JWT 字符串的示例:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNjY2NzA2NTE4LCJleHAiOjE2NjY3MDY1NDh9.Gtw5Hg0t83g11oZEoKPxi9UljxW02M3dse5mqT7iqlI

JWT 的三个部分各自代表的含义

JWT 的三个组成部分,从前到后分别是 Header、Payload、Signature。
其中:
Payload 部分才是真正的用户信息,它是用户信息经过加密之后生成的字符串。
Header 和 Signature 是安全性相关的部分,只是为了保证 Token 的安全性

JWT 的使用方式

客户端收到服务器返回的 JWT 之后,通常会将它储存在 localStoragesessionStorage 中。
此后,客户端每次与服务器通信,都要带上这个 JWT 的字符串,从而进行身份认证。推荐的做法是把 JWT 放在 HTTP 请求头的 Authorization 字段中,格式如下:

Authorization:Bearer token

Express 中使用 JWT

安装 JWT 相关的包

运行如下命令,安装如下两个 JWT 相关的包:

npm i jsonwebtoken express-jwt

其中:
jsonwebtoken 用于生成 JWT 字符串
express-jwt 用于将 JWT 字符串解析还原成 JSON 对象

定义 secret 密钥

为了保证 JWT 字符串的安全性,防止 JWT 字符串在网络传输过程中被别人破解,我们需要专门定义一个用于加密和解密的 secret 密钥
当生成 JWT 字符串的时候,需要使用 secret 密钥对用户的信息进行加密,最终得到加密好的 JWT 字符串
当把 JWT 字符串解析还原成 JSON 对象的时候,需要使用 secret 密钥进行解密

const jwt = require('jsonwebtoken')
const expressJWT = require('express-jwt')

// 密钥为任意字符串
const secretKey = 'Bruce'

在登录成功后生成 JWT 字符串

调用 jsonwebtoken 包提供的 sign() 方法,将用户的信息加密成 JWT 字符串,响应给客户端

app.post('/api/login', (req, res) => {
  ...
  res.send({
    status: 200,
    message: '登录成功',
    // jwt.sign() 生成 JWT 字符串
    // 参数:用户信息对象、加密密钥、配置对象-token有效期
    // 尽量不保存敏感信息,因此只有用户名,没有密码
    token: jwt.sign({username: userInfo.username}, secretKey, {expiresIn: '10h'})
  })
})

将 JWT 字符串还原为 JSON 对象

客户端访问有权限的接口时,需通过请求头的 Authorization 字段,将 Token 字符串发送到服务器进行身份认证
服务器可以通过 express-jwt 中间件将客户端发送过来的 Token 解析还原成 JSON 对象

// unless({ path: [/^\/api\//] }) 指定哪些接口无需访问权限
app.use(expressJWT({ secret: secretKey }).unless({ path: [/^\/api\//] }))

使用 req.auth 获取用户信息

当 express-jwt 中间件配置成功后,即可在那些有权限的接口中,使用 req.auth 对象,来访问从 JWT 字符串中解析出来的用户信息

// 这是一个有权限的 API 接口
app.get('/admin/getinfo', function (req, res) {
  // TODO_05:使用 req.auth 获取用户信息,并使用 data 属性将用户信息发送给客户端
  console.log(req.auth);
  res.send({
    status: 200,
    message: '获取用户信息成功!',
    data: req.auth // 要发送给客户端的用户信息
  })
})

捕获解析 JWT 失败后产生的错误

当使用 express-jwt 解析 Token 字符串时,如果客户端发送过来的 Token 字符串过期或不合法,会产生一个解析失败的错误,影响项目的正常运行
通过 Express 的错误中间件,捕获这个错误并进行相关的处理:

app.use((err, req, res, next) => {
  if (err.name === 'UnauthorizedError') {
    return res.send({ status: 401, message: 'Invalid token' })
  }
  res.send({ status: 500, message: 'Unknown error' })
})

完整示例demo

app.js文件:

// 导入 express 模块
const express = require('express')
// 创建 express 的服务器实例
const app = express()

// TODO_01:安装并导入 JWT 相关的两个包,分别是 jsonwebtoken 和 express-jwt
const jwt = require('jsonwebtoken')
const { expressjwt: expressJWT} = require('express-jwt')
// 允许跨域资源共享
const cors = require('cors')
app.use(cors())

// 解析 post 表单数据的中间件
const bodyParser = require('body-parser')
const { response } = require('express')
app.use(bodyParser.urlencoded({ extended: false }))

// TODO_02:定义 secret 密钥,建议将密钥命名为 secretKey
const secretKey = 'lzqlmy ^_^!'
// TODO_04:注册将 JWT 字符串解析还原成 JSON 对象的中间件
app.use(expressJWT({secret : secretKey,algorithms:['HS256']}).unless({path:[/^\/api\//]}))
// 登录接口
app.post('/api/login', function (req, res) {
  // 将 req.body 请求体中的数据,转存为 userinfo 常量
  const userinfo = req.body
  // 登录失败
  if (userinfo.username !== 'admin' || userinfo.password !== '000000') {
    return res.send({
      status: 400,
      message: '登录失败!'
    })
  }
  // 登录成功
  // TODO_03:在登录成功之后,调用 jwt.sign() 方法生成 JWT 字符串。并通过 token 属性发送给客户端
  //1.参数一:用户的信息对象  参数二:加密的密钥  参数三:配置对象,可以配置当前 token 的有效期
  //千万不要把密码加密到 token 字符串中
  const tokenStr = jwt.sign({username:userinfo.username},secretKey,{expiresIn:'30s'})
  res.send({
    status: 200,
    message: '登录成功!',
    token: tokenStr // 要发送给客户端的 token 字符串
  })
})

// 这是一个有权限的 API 接口
app.get('/admin/getinfo', function (req, res) {
  // TODO_05:使用 req.user 获取用户信息,并使用 data 属性将用户信息发送给客户端
  console.log(req.auth);
  res.send({
    status: 200,
    message: '获取用户信息成功!',
    data: req.auth // 要发送给客户端的用户信息
  })
})

// TODO_06:使用全局错误处理中间件,捕获解析 JWT 失败后产生的错误
app.use((err,req,res,next)=>{
  //这次错误是由token解析失败造成的 
  if(err.name === `UnauthorizedError`) {
    return res.send({
      status : 401,
      message:'token已过期'
    })
  }
  res.send({
    status : 500,
    message : '未知的错误!'
  })
})
// 调用 app.listen 方法,指定端口号并启动web服务器
app.listen(8888, function () {
  console.log('Express server running at http://127.0.0.1:8888')
})

postman中测试接口:
在这里插入图片描述
在登录接口中获取到token,得到token后进行复制
在这里插入图片描述
将token复制到获取信息的接口中来,在Headers中添加KEY值:Authorization,在VALUE中添加Bearer + token值。我们可以看到获取到了用户信息,当我们token过期的时候,错误中间件会提示token已过期

小结

前后端分离的主流时代中,jwt是必须要学会的东西,可能在开发中你只需要承担前端的工作,但是你需要了解jwt的工作原理,以及能够处理好后端传过来的token,这种能力必须是要有的!继续加油吧,少年!

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/408167.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【JavaScript】JS实用案例分享:选择器组件 | 简易计算器

🖥️ NodeJS专栏:Node.js从入门到精通 🖥️ 博主的前端之路(源创征文一等奖作品):前端之行,任重道远(来自大三学长的万字自述) 🖥️ TypeScript知识总结&…

Axure RP9使用指南

1、快捷键 选中图层移动到上一层 ctrl [ 选中图层移动到下一层 ctrl ] 选中图层移动到顶层 ctrl shift [ 选中图层移动到底层 ctrl shift ] 合并为一组 ctrl G 取消合并 ctrl shift G 锁定位置和尺寸 ctrl K 解锁 ctrl shift K 拖动页面可以按住空格再通过鼠标拖动…

CSS实现文字颜色渐变

三行代码即可实现: background: linear-gradient(to bottom, #ffff, #f8fdff, #acf5ff, #67eeff, #24d8ff);-webkit-background-clip: text;color: transparent; 一 先设置背景颜色渐变 background 渐变属性可选值: 1 linear-gradient 为线性渐变…

JavaScript的节点操作 —— 增加节点

目录 JavaScript中的节点操作分为:增、删、改、查(获取),四大类型。 我们直接利用案例来学习: HTML布局代码: JavaScript代码实现: 第一步:获取所有要用到的元素对象&#xff…

Vue3中toRef以及toRefs的基本使用

toRef以及toRefs 作用:创建一个ref对象,其value值指向另一个对象中的某个属性。 语法:const name toRef(obj,name) 应用:要将响应式对象中的某个属性单独提供给外部使用时 拓展:toRefs与toRef功能一致,但可以批量创建多个ref对…

vue报错 Error in render: “TypeError: Cannot read properties of undefined (reading ‘nickname‘)“

起因:我用axios请求数据(可以看见数据了,已经请求成功的),最后加载在模板上(页面模板上有内容),但是控制台出现了一个奇怪的错误 在created()钩子函数请求接口并报错数据&#xff0…

css 如何实现文本竖排、横排展示

writing-mode 该属性定义了文本在水平或垂直方向上如何排布 writing-mode:horizontal-tb | vertical-rl | vertical-lr | sideways-rl | sideways-lr接下来和我一起看看各个属性值的用法吧&#xff01;Go! <style>.root {border: 1px solid green;width: 400px;height: 4…

Vue内置组件的component标签

component标签&#xff1a;它的用途是可以动态绑定我们的组件,根据数据不同更换不同的组件. 有一个is属性&#xff0c;is的作用就是显示指定的组件 官方说明&#xff1a;渲染一个“元组件”为动态组件。依 is 的值&#xff0c;来决定哪个组件被渲染 使用方式也很简单&#xf…

nvue基础快速入门

一、uniapp连接手机 1、用USB让手机和电脑连接 2、点击3---8次手机版本信息打开开发者模式 3、在更多设置里面找到开发者选项 4、打开USB调试和允许手机可以USB安装&#xff0c;以及不要让手机只允许充电&#xff0c;要打开文件传输模式&#xff08;在手机消息栏里面会有提示…

前后端交互流程

1.前后端交互: 前后端交互&#xff0c;也可以理解为数据交互。前端需要获取&#xff08;GET&#xff09;的数据获取上传&#xff08;POST&#xff09;的数据&#xff0c;要通过 请求 来完成的&#xff0c;前端发送请求&#xff0c;后端接收到请求后&#xff0c;便进行对数据库…

若依移动端Ruoyi-App——引入uview2.0开发项目

1. 背景&#xff1a; 若依移动端Ruoyi-App只有个人中心&#xff0c;登录&#xff0c;其他模块都是建设中&#xff0c;因uniapp本身样式不够美观&#xff0c;所以本文基于ruoyi移动端引入uview2.0&#xff0c;实现基本功能。 RuoYi-App: &#x1f389; RuoYi APP 移动端框架&am…

element plus 的表单form使用详解

首先在你需要使用form组件的Vue文件里引入 import type { FormInstance } from element-plus 拿这一张图解释一下属性&#xff0c;第一项ref"ruleFormRef",&#xff0c;ruleFormRef就是我们定义并使用各个属性的必要条件&#xff0c;const ruleFormRef ref<Form…

vue 项目启动失败 ‘webpack-dev-server‘ 不是内部或外部命令,也不是可运行的程序

报错描述 我使用命令 npm run dev 启动项目 报了下面的错。 webpack-dev-server 不是内部或外部命令&#xff0c;也不是可运行的程序 > webpack-dev-server --inline --progress --config build/webpack.dev.conf.js webpack-dev-server 不是内部或外部命令&#xff0c;也不…

Vue3 框架使用报错以及解决办法

1、TypeError: Failed to fetch dynamically imported module: 引入组件时&#xff0c;没有添加.vue后缀 或者引入的组建没有被使用 2、SyntaxError: The requested module /node_modules/_schart.js3.0.4schart.js/lib/sChart.min.js?v0343bb8c does not provide an export…

前后端分离项目,如何解决跨域问题?

跨域问题是前后端分离项目中非常常见的一个问题&#xff0c;举例来说&#xff0c;编程猫&#xff08;codingmore&#xff09;学习网站的前端服务跑在 8080 端口下&#xff0c;后端服务跑在 9002 端口下&#xff0c;那么前端在请求后端接口的时候就会出现跨域问题。 403 Forbidd…

在uniapp中获取可视区域的高度(uni.getSystemInfo)

前言在实际开发中我们会遇到不确定高度的情况&#xff0c;那么在uniapp中我们如何获取区域的高度呐&#xff1f;一起来看看吧使用到的&#xff1a;uni-app提供了异步(uni.getSystemInfo)和同步(uni.getSystemInfoSync)的2个API获取系统信息uni.getSystemInfo(OBJECT)异步获取系…

Google Chrome装到D盘的方法

1. 下载chrome安装包 Google Chrome 网络浏览器 2. 创建自定义安装目录 D:\Program Files\Google\Chrome 3. 创建个人数据目录 D:\Program Files\Google\PersonData 4. 创建chrome浏览器的默认安装目录&#xff08;目录中的文件删除掉&#xff09; C:\Program Files\Google …

手把手教你如何Vue项目打包dist文件并Tomcat发布【超级详细】

???作者&#xff1a;bug菌 博客&#xff1a;CSDN、掘金等 ??公众号&#xff1a;猿圈奇妙屋 ??特别声明&#xff1a;原创不易&#xff0c;转载请附上原文出处链接和本文声明&#xff0c;谢谢配合。 ??版权声明&#xff1a;文章里可能部分文字或者图片来源于互联网或者百…

React中CodeMirror插件的使用及封装

目录 一、CodeMirror是什么 二、React中CodeMirror的基本使用介绍 &#xff08;一&#xff09;引入CodeMirror 1. 安装CodeMirror插件 2.引入 CodeMirror 插件 &#xff08;二&#xff09;引入文件配置 &#xff08;三&#xff09;关键属性解读 1.value 2.mode 3.the…

Vue3 从零开始 搭建 简单 干净 的 后台管理系统

前言&#xff1a; 记得自己大二时&#xff08;2017年&#xff09;&#xff0c;想搭建一个后台管理系统&#xff0c;当时头脑想的是用原生JS写的。我肯定干不出来&#xff0c;后来乖乖用了当时比较流行的layui&#xff0c;就算现在也真的难以做下去。 这几天&#xff0c;有了需…