前后端 token 的使用

news2024/11/17 17:26:41

嗷呜, 预感又是一篇长的水文, 但是内心好激动哦

适用于前端(了解 node express 框架的人看), 想要了解后端的人看

好了, 开始废话模式

前后端 token 的使用

最近在做一个后台管理项目, 但是我一个卑微的前端我去哪里找接口的, 没有, 只好我自己写

哎, 我能有什么坏心思呢, 没有

1. 了解 JWT

利用token进行用户身份验证的流程:

  • 客户端使用用户名和密码请求登录
  • 服务端收到请求,验证用户名和密码
  • 验证成功后,服务端会签发一个token,再把这个token返回给客户端
  • 客户端收到token后可以把它存储起来,比如放到cookie中
  • 客户端每次向服务端请求资源时需要携带服务端签发的token,可以在cookie或者header中携带
  • 服务端收到请求,然后去验证客户端请求里面带着的token,如果验证成功,就向客户端返回请求数据

*支持跨域访问:cookie是无法跨域的,而token由于没有用到cookie(前提是将token放到请求头中),所以跨域后不会存在信息丢失问题

无状态:token机制在服务端不需要存储session信息,因为token自身包含了所有登录用户的信息,所以可以减轻服务端压力

JWT就是上述流程当中token的一种具体实现方式,其全称是JSON Web Token,官网地址:https://jwt.io/

通俗地说,JWT的本质就是一个字符串,它是将用户信息保存到一个Json字符串中,然后进行编码后得到一个JWT token并且这个JWT token带有签名信息,接收后可以校验是否被篡改,所以可以用于在各方之间安全地将信息作为Json对象传输。

JWT 的验证证流程:

  • 首先,前端通过Web表单将自己的用户名和密码发送到后端的接口,这个过程一般是一个POST请求。建议的方式是通过SSL加密的传输(HTTPS),从而避免敏感信息被嗅探
  • 后端核对用户名和密码成功后,将包含用户信息的数据作为JWT的Payload,将其与JWT Header分别进行Base64编码拼接后签名,形成一个JWT Token,形成的JWT Token就是一个如同lll.zzz.xxx的字符串
  • 后端将JWT Token字符串作为登录成功的结果返回给前端。前端可以将返回的结果保存在浏览器中,退出登录时删除保存的JWT Token即可
  • 前端在每次请求时将JWT Token放入HTTP请求头中的Authorization属性中(解决XSS和XSRF问题)
  • 后端检查前端传过来的JWT Token,验证其有效性,比如检查签名是否正确、是否过期、token的接收方是否是自己等等
  • 验证通过后,后端解析出JWT Token中包含的用户信息,进行其他逻辑操作(一般是根据用户信息得到权限等),返回结果

用案例来了解, 唔, 我把 app.js 拆分成了下面的几个部分, 代码的顺序没有改变, 依次看就可以

/*
 * Start at 2022.4.30
 * Author: ximingx.
 * Github: https://github.com/ximingx
 * Csdn: https://ximingx.blog.csdn.net/
 */

// 导入 express 模块
// 创建 express 的服务器实例
const express = require('express')
const app = express()
const port = process.env.PORT || 2022;
// 跨域设置
const cors = require('cors')
app.use(cors())
// req.body 解析
app.use(express.json());
app.use(express.urlencoded({extended: false}));

1.1 安装并导入

首先需要我们去安装依赖

> yarn add jsonwebtoken express-jwt

然后导入

// 1. 安装并导入 JWT 相关的两个包, express-jwt版本推荐6.1.1, 最近他更新了, 可能会出现一些意外的问题
const jwt = require('jsonwebtoken')
const expressJWT = require('express-jwt')

1.2 定义你自己的 secret 密钥

// 2. 定义 secret 密钥,建议将密钥命名为 secretKey
const secretKey = 'ximingx';

1.3 JWT字符串解析

// 5. JWT字符串解析
// 用于接收到客户端传来的请求, 验证是否含有 token
// 注意:只要配置成功了 express-jwt 这个中间件,就可以把解析出来的用户信息,挂载到 `req.user` 属性上
app.use(expressJWT({secret: secretKey, algorithms: ['HS256']}).unless({path: ['/login', '/register']}))

1.4 登陆后生成 token

// 登录的路由, 在这里登录成功后, 返回 token 字符串
app.post('/login', (req, res) => {
    // 哎呀,这里就是一个简单的登录测试, 没搞的很复杂
    if (!(req.body.username == 'admin' && req.body.password == "root")) {
        return res.json({
            code: 1,
            msg: '用户名或密码错误'
        })
    }
    // 3. 用户登陆成功后, 需要生成token
    // 参数1:用户的信息对象
    // 参数2:加密的秘钥
    // 参数3:配置对象,可以配置当前 token 的有效期
    const tokenStr = jwt.sign({
        username: req.body.username
    }, secretKey, {expiresIn: '1h'})
    // 返回登陆成功后返回数据
    res.json({
        status: 200,
        message: '登录成功!',
        // 4. 并通过 token 属性发送给客户端
        token: tokenStr,
    })
})

1.5 验证

// 这里需要这样请求, 否则 token 会无效报错
// axios({
//     method: 'post',
//     url: 'http://localhost:2022/adimin',
//     headers: {
//         'Authorization': 'Bearer ' + token
//     }
// })

// 可以分别用带 token 和不带 token 验证一下

// 这是一个有权限的 API 接口
app.get('/admin', function (req, res) {
    // 6. 使用 req.user 获取用户信息,并使用 data 属性将用户信息发送给客户端
    console.log(req.user)
    // {
    //     "status": 200,
    //     "message": "获取用户信息成功!",
    //     "data": {
    //         "username": "admin",
    //         "iat": 1651242605, // 创建时间
    //         "exp": 1651246205 // token 有效期
    //     }
    // }
    res.send({
        status: 200,
        message: '获取用户信息成功!',
        data: req.user, // 要发送给客户端的用户信息
    })
})

这是不带 token 的请求

image-20220430114311749

当我们在登陆后, 获取 token

image-20220430114734717

再次进行测试, 带上 token 就可以啦

image-20220430115107833

1.6 捕获 token 过期异常

// 7:使用全局错误处理中间件,捕获解析 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(port, () => {
    console.log(`Server is running at http://localhost:${port}`);
});

2. 搭建一个简单的后台服务

于是我用了 nodeexpress 框架简单的做了一个服务器

下面是 app.js 文件, 看完上面的 jwt 应该就可以很好地理解了

/*
 * Start at 2022.
 * Author: ximingx.
 * Github: https://github.com/ximingx
 * Csdn: https://ximingx.blog.csdn.net/
 */
// 导入 express 模块
// 创建 express 的服务器实例
const express = require('express')
const app = express()
const port = process.env.PORT || 3000;
// 跨域设置
const cors = require('cors')
app.use(cors())
// req.body 解析
app.use(express.json());
app.use(express.urlencoded({extended: false}));
// 加载数据库
require('./db/index');
// 5. JWT字符串解析
const expressJWT = require('express-jwt')
// 2. 定义 secret 密钥,建议将密钥命名为 secretKey
const secretKey = 'ximingx';
// 注意:只要配置成功了 express-jwt 这个中间件,就可以把解析出来的用户信息,挂载到 `req.user` 属性上
app.use(expressJWT({secret: secretKey, algorithms: ['HS256']}).unless({path: ['/api/ximingx/v1/login']}));
// 自定义模块
const {writeLog} = require('./log/log');
// 路由
app.use('/api/ximingx/v1', require('./router/index'));
// 全局捕获错误
app.use((err, req, res, next) => {
    writeLog(err);
    // 这次错误是由 token 解析失败导致的
    if (err.name === 'UnauthorizedError') {
        return res.send({
            status: 401,
            message: '无效的token',
        })
    }
    return res.status(500).send('未知的错误!');
});
// 监听端口
app.listen(3000, () => {
    console.log(`server is running at http://localhost:${port}`);
});

这是我用于登录验证的路由

// 除了 /api/ximingx/v1/login 其他的路由都需要 token
app.use(expressJWT({secret: secretKey, algorithms: ['HS256']}).unless({path: ['/api/ximingx/v1/login']}));

app.use('/api/ximingx/v1', require('./router/index'));

router.js

const express = require('express');
const router = express.Router();
// 1. 安装并导入 JWT 相关的两个包, express-jwt版本推荐6.1.1, 最近他更新了, 可能会出现一些意外的问题
const jwt = require('jsonwebtoken')
// 2. 定义 secret 密钥,建议将密钥命名为 secretKey
const secretKey = 'ximingx';
// 自定义模块
const {UserLogin, findUser, createUser} = require('../db/user')
const {LoginRegex} = require('../utils/regex')
const {writeLog} = require('../log/log')

router.post('/login', (req, res) => {
    if (!LoginRegex(req.body)) {
        writeLog("Login 参数不支持")
        return res.json({
            title: "register",
            code: 400,
            data: {
                info: "ximingx",
                message: "参数不支持",
            }
        })
    }

    UserLogin(req.body).then(user => {
        if (user == null) {
            return res.json({
                title: "login",
                code: 400,
                data: {
                    info: "ximingx",
                    message: "用户名或者密码错误"
                }
            })
        }
        // 3. 用户登陆成功后, 需要生成token
        // 参数1:用户的信息对象
        // 参数2:加密的秘钥
        // 参数3:配置对象,可以配置当前 token 的有效期
        const tokenStr = jwt.sign({
            username: req.body.username
        }, secretKey, {expiresIn: '1h'})
        res.json({
            title: "login",
            code: 200,
            data: {
                com: "ximingx",
                message: req.body,
                // 4. 并通过 token 属性发送给客户端
                token: tokenStr,
            }
        })
    }).catch(err => {
        writeLog(err)
        return res.json({
            title: "login",
            code: 400,
            dara: {
                info: "ximingx",
                message: "用户名或者密码错误",
            }
        })
    })
});

3. 前端登录页面

image-20220430113258699

这里我们进行请求

axios({
url: ‘http://localhost:3000/api/ximingx/v1/login’,
method: ‘POST’,
data: {
username: this.username,
password: this.password,
}
})

在登录成功后进行存储

localStorage.setItem(‘token’, res.data.data.token);

inputInfo: function () {
      let password = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,18}$/;
      let username = /^[a-zA-Z0-9]{3,12}$/;
      if (!(username.test(this.username) && password.test(this.password))) {
        return ElNotification.error('用户名或者密码不合法!');
      }
      axios({
        url: 'http://localhost:3000/api/ximingx/v1/login',
        method: 'POST',
        data: {
          username: this.username,
          password: this.password,
        }
      }).then(res => {
        if (res.data.code == 200) {
          localStorage.setItem('token', res.data.data.token);
          ElNotification({
            title: 'Success',
            message: '登录成功 请稍等...',
            type: 'success',
          });
          this.$router.push({
            path: '/home',
          })
        } else {
          ElNotification.error({
            title: '用户名或者密码错误',
            message: `请重新输入`,
          })
        }
      }).catch(res => {
        ElNotification({
          title: 'Error',
          message: '用户名或者密码错误',
          type: 'error',
        });
      })
    },

4. 导航守卫

但是呢, 即使是没有 token, 我们在 url 里输入别的页面仍然会跳转

这里就需要我们在前端 router.js 里配置, 判读有无 token, 使别人不能随便的进行别的有权限的页面的访问

// to 从哪一个路径来, from 到哪一个路径去, next 是否继续执行, 表示放行
router.beforeEach((to, from, next) => {
  if (to.path === '/login') {
    return next()
  } else {
    const token = localStorage.getItem('token')
    if (!token) {
      return next('/login')
    }
    return next()
  }
})

但是还是有问题, 别人可以自己加 token 啊

image-20220430113920401

然后我们就可以快乐的跳转了

image-20220430114018625

所以呢, 最后还应该给导航守卫还应该添加验证

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

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

相关文章

【React】使用Next.js构建并部署个人博客

👉 TypeScript学习:TypeScript从入门到精通 👉 蓝桥杯真题解析:蓝桥杯Web国赛真题解析 👉 个人简介:一个又菜又爱玩的前端小白🍬 👉 你的一键三连是我更新的最大动力❤️&#xff01…

Server Tomcat v9.0 Server at localhost failed to start问题

Server Tomcat v9.0 Server at localhost failed to start问题解决办法。 在我们使用eclipse启动Tomcat时,有时会出现Server Tomcat v9.0 Server at localhost failed to start 的错误提示,导致无法成功启动,下面给出出现这种问题的简单解决…

Python开发自定义Web框架

文章目录开发自定义Web框架1.开发Web服务器主体程序2.开发Web框架主体程序3.使用模板来展示响应内容4.开发框架的路由列表功能5.采用装饰器的方式添加路由6.电影列表页面的开发案例开发自定义Web框架 接收web服务器的动态资源请求,给web服务器提供处理动态资源请求…

Web项目(Vue)部署到阿里云服务器【超详细】

超详细Vue项目部署篇!!! 小白的部署之路 前段时间白嫖了一年的阿里云服务器,想着手上有个项目,那就部署上去吧。找了很多教程,没有一篇是完整细致的,对于小白的我来说太难了,然后就…

最全面的SpringBoot教程(三)——SpringBoot Web开发

前言 本文为SpringBoot Web开发相关内容介绍,下边将对静态资源管理(包括:静态资源访问,静态资源前缀,webjar,首页支持),请求参数处理(包括:Rest风格&#xff…

【微信小程序】-- 自定义组件 - 父子组件之间的通信(三十八)

💌 所属专栏:【微信小程序开发教程】 😀 作  者:我是夜阑的狗🐶 🚀 个人简介:一个正在努力学技术的CV工程师,专注基础和实战分享 ,欢迎咨询! &…

也许是全网最全的 Angular 新手入门指南

文章目录Angular概述Angular程序架构Angular优势angular/cli脚手架文件加载顺序项目目录结构Angular模块NgModule 装饰器内置模块自定义模块模块的tipsAngular组件Component 元数据数据绑定脏值检测父子组件通讯投影组件Angular指令内置属性型指令内置结构型指令指令事件样式绑…

若依框架(前后端分离)打war包部署到linux

一、前端部署 1.找到ruoyi-ui目录。 2.安装依赖。 npm install 3.执行以下操作,解决 npm 下载速度慢的问题。 npm install --registryhttps://registry.npmmirror.com 4.修改vue.config.js,若后端采用的是默认8080端口,则不用修改,默认就是…

2023最新最全vscode插件精选

文章简介 本文介绍最新、最实用、最强大的 vscode 精选扩展。好用的扩展,犹如神兵利器,帮助程序员在代码的世界中,所向披靡,战无不胜! 作者介绍 随易出品,必属精品,只写有深度,有质…

vue 路由钩子

路由钩子分为三种 全局钩子: beforeEach、 afterEach、beforeResolve单个路由里面的钩子: beforeEnter组件路由:beforeRouteEnter、 beforeRouteUpdate、 beforeRouteLeave 它的三个参数: to: (Route路由对象) 即将要进入的目标…

【前端知识体系梳理(三)】Diff策略

​ 目录 🍉前言 🍉传统Diff算法 🍉React Diff 🍓🍓🍓1、tree diff 🍓🍓🍓2、component diff 🍓🍓🍓3、element diff &#x1…

前端页面项目——博客系统

目录 1.实现博客列表页 1.1 实现导航栏 1.2 实现中间版心 1.3 实现个人信息 1.4 实现博客列表 2. 实现博客正文页 3. 实现博客登陆页 4. 实现博客编辑 4.1 实现编辑区 4.2 引入编辑器 展示 1)登录页面 2)博客列表页 3)博客详情页 4&am…

【JavaScript】手撕前端面试题:手写Object.create | 手写Function.call | 手写Function.bind

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

PyQt5之进度条:QProgressBar

PyQt5之进度条:QProgressBar 在软件中,在处理特别冗长的任务时,如果没有相关的进度信息,这个等待的过程会比较考验用户的耐心,根据相关理论,进度条可以缓解用户在等待过程中的焦虑,所以&#x…

前端学习笔记(14)-Vue3组件传参

1.props&#xff08;父组件传递给子组件&#xff09;1.1 实现如果你没有使用 <script setup>&#xff0c;props 必须以 props 选项的方式声明&#xff0c;props 对象会作为 setup() 函数的第一个参数被传入&#xff1a;在子组件中&#xff1a;export default {props: {ti…

微信小程序头像昵称填写能力

1、基本介绍 微信小程序获取头像昵称的能力&#xff0c;最近又进行了一次调整&#xff0c;如果没有记错这是今年第三次调整了&#xff0c;每次调整每个开发者心中我相信都跟我一样&#xff0c;万马奔腾。。。今天写个demo体验下实际效果如何。 详细信息请见小程序用户头像昵称…

微信小程序实现PDF预览功能——pdf.js(含源码解析)

文章目录前言一、pdf.js 是什么&#xff1f;二、使用步骤1.下载库文件2.使用方式微信小程序端——使用 web-view 标签H5 端——使用 iframe 标签&#xff08;使用vue框架&#xff09;3.更改源码如何隐藏顶部工具栏如何让用户强制阅读一定时间如何获取pdf总页数如何获取pdf当前页…

【折腾电脑】Edge浏览器看B站视频卡顿最全解决办法合集

开头碎碎念&#xff1a;更新频率明显和疫情呈正相关&#xff0c;祝大家健健康康吃好喝好&#xff01; 使用Microsoft Edge浏览器观看B站视频&#xff0c;卡得无法忍受。 在网络上搜索相关问题&#xff0c;最早的一条是2016/04/17微软问题反馈的记录。任何原因的卡顿都是正常的&…

Vue样式穿透

Vue样式穿透 vue文件的style标签的scoped属性作用&#xff1a;PostCSS在元素标签上添加特殊属性值&#xff0c;在样式的选择器后面添加属性选择器&#xff0c;实现了组件样式的私有化&#xff0c;防止组件之间的样式污染&#xff08;比如相同类名的元素&#xff09;。 但在使…

【CSS】盒子模型内边距 ② ( 内边距复合写法 | 代码示例 )

文章目录一、内边距复合写法1、语法2、代码示例 - 设置 1 个值3、代码示例 - 设置 2 个值4、代码示例 - 设置 3 个值5、代码示例 - 设置 4 个值一、内边距复合写法 1、语法 盒子模型内边距 可以通过 padding-left 左内边距padding-right 右内边距padding-top 上内边距padding-…