• 【Express】登录鉴权 JWT


    JWT(JSON Web Token)是一种用于实现身份验证和授权的开放标准。它是一种基于JSON的安全传输数据的方式,由三部分组成:头部、载荷和签名。

    前置知识

    一、JWT 的前世:传统身份信息验证面临的篡改、伪造问题

    • 传统方式下浏览器保存身份信息存在被篡改和伪造的风险
    • 浏览器可以轻易更改 local storage、cookie 等存储的身份信息
    • 服务器无法验证身份信息的真实性,容易造成安全问题

    二、JWT 的本质:使用签名机制解决安全问题

    • JWT 引入签名机制,服务器对身份信息进行签名,并与身份信息一起发送给浏览器
    • 浏览器保存身份信息和签名,下次请求时一起发送给服务器
    • 服务器使用相同的密钥对身份信息进行签名,并与收到的签名进行对比,验证身份信息的真实性

    三、JWT 的组成:由三个部分组成的字符串

    • JWT 由三个部分组成,用点号分隔
    • 第一个部分是 Base64 编码的头部信息,包含签名算法等信息
    • 第二个部分是 Base64 编码的 Payload,包含用户身份信息等数据
    • 第三个部分是 Base64 编码的签名,由服务器使用密钥对头部和 Payload 进行签名

    在这里插入图片描述

    在这里插入图片描述

    jwt里需要包含生效时间和过期时间,传回服务器之后由服务器判断是否过期。

    用户主动登出就直接由客户端删除token。

    JWT不是加密方法,它只是把业务中后端原本需要用Redis记录的一些数据换了一种方式保存和读取,作用是减少数据库查询。它只能防止别人修改其他人的数据,没有办法防止别人复制或者读取。

    JWT 的使用

    在这里插入图片描述

    使用jsonwebtoken模块,你可以在Node.js应用程序中轻松生成和验证JWT。以下是jsonwebtoken库的使用步骤:

    1. 安装jsonwebtoken模块:在命令行中运行npm install jsonwebtoken来安装jsonwebtoken模块。

    2. 导入jsonwebtoken模块:在你的Node.js应用程序中,使用require语句导入jsonwebtoken模块。

    const jwt = require('jsonwebtoken');
    
    • 1
    1. 生成JWT:使用jsonwebtoken的sign方法生成JWT。该方法接受三个参数:载荷(Payload)、密钥和可选的配置对象。载荷是一个包含有关用户/客户端的信息的对象。
    const payload = {
      userId: '123456789',
      username: 'example_user'
    };
    
    const secretKey = 'your_secret_key';
    
    const token = jwt.sign(payload, secretKey, { expiresIn: '1h' });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    上述代码将生成一个JWT,其中包含了userId和username信息,使用了一个密钥进行签名,并设置了过期时间为1小时。生成的JWT将作为一个字符串存储在token变量中。

    1. 验证JWT:使用jsonwebtoken的verify方法验证JWT的有效性。该方法接受三个参数:要验证的JWT、密钥和一个可选的回调函数。
    const token = 'your_generated_jwt';
    
    jwt.verify(token, secretKey, (err, decoded) => {
      if (err) {
        // JWT验证失败
        console.log('JWT verification failed.');
      } else {
        // JWT验证成功
        console.log('JWT verified successfully.');
        console.log(decoded); // 解码后的JWT负载
      }
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    上述代码将验证传入的JWT是否有效,并使用提供的密钥进行签名验证。如果JWT有效,verify方法回调函数中的decoded参数将包含解码后的JWT负载信息。如果JWT无效,则会在回调函数中得到一个错误。

    以下是 JWT 的一个简单的封装:

    //jsonwebtoken 封装
    
    const jsonwebtoken = require("jsonwebtoken")
    // 设置密钥
    const secret = "anydata"
    const JWT = {
    	// 生成 token
        generate(value,expires){
        	// value 数据,expires 过期时间
            return jsonwebtoken.sign(value,secret,{expiresIn:expires})
        },
        // 校验 token
        verify(token){
        	// 放在 try...catch... 中,防止报错
            try{
                return jsonwebtoken.verify(token,secret)
            }catch(error){
                return false
            }
        }
    }
    
    module.exports = JWT
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    const token = JWT.generate('xx', '10s')
    // eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjoieHgiLCJpYXQiOjE2OTc0Mzk3MTEsImV4cCI6MTY5NzQzOTcyMX0.vfcaK_tHfbD-58nqcDRopg516jngqWJhw6zr229zACM
    // header.数据.签名
    console.log(JWT.verify(token))
    
    • 1
    • 2
    • 3
    • 4
    // node 中间件校验
    app.use((req,res,next)=>{
      // 如果token有效 ,next() 
      // 如果token过期了, 返回401错误
      if(req.url==="/login"){
        next()
        return;
      }
      const token = req.headers["authorization"]?.split(" ")[1]
      if(token){
        var payload = JWT.verify(token)
        // console.log(payload)
        if(payload){
          const newToken = JWT.generate({
            _id:payload._id,
            username:payload.username
          },"1d")
          res.header("Authorization",newToken)
          next()
        }else{
          // errCode 和 errInfo 是 network 的 preview 中返回的数据
          res.status(401).send({errCode:"-1",errInfo:"token过期"})
        }
      }
    })
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
     //生成token
    const token = JWT.generate({
        _id: result[0]._id,
        username: result[0].username
    }, "1d")
    
    res.header("Authorization", token)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    token 作为用户的数据标识,在接口层面起到了接口权限控制的作用,也就是说后端有很多接口都需要通过查看当前请求头信息中是否含有 token 数据,来决定是否正常返回数据。

    //前端拦截
    import axios from 'axios'
    // Add a request interceptor
    axios.interceptors.request.use(function (config) {
        const token = localStorage.getItem("token")
        config.headers.Authorization = `Bearer ${token}`
        return config;
      }, function (error) {
        return Promise.reject(error);
      });
    
    // Add a response interceptor
    axios.interceptors.response.use(function (response) {
        const {authorization } = response.headers
        authorization && localStorage.setItem("token",authorization)
        return response;
      }, function (error) {
        const {status} = error.response
        if(status===401){
        	// 移除本地失效的 token 值,由于过期而失效
            localStorage.removeItem("token")
            window.location.href="/login"
        }
        return Promise.reject(error);
      });
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    业务逻辑:

    1. 后端下载、导入模块 npm i jsonwebtoken
    2. 封装生成token、验证token方法
    3. 登录接口中将前端提供的字段生成token并放到登录接口响应头中
    4. 前端将token存储(并在每个请求的请求头中加入token,请求拦截添加token,响应拦截存储token)
    5. 后端设置应用级中间件
      5.1. 排除登录接口、路由
      5.2. 获取前端传过来的请求头并判断状态
      5.2.1. 有token:重新设置token并放在响应头传给前端(刷新过期时间)
      5.2.2. 无token:返回401给前端
    6. 前端响应拦截:无401跳转登录页并置空本地token

    简单总结:

    配置 jwt util,controller 中使用 jwt:

    1. 生成 token
    2. 响应头携带 token (Authorization:token)返回给前端 => 之后前端请求后端在请求头携带 token,Bearer ${token}
    3. 前端 响应拦截器 在响应头中获取 token 并设置在 localStorage 中,请求拦截器从 localStorage 中获取 token 进行请求头携带(固定格式)
    4. 后端 路由拦截,进行路由验证和 token 的刷新
  • 相关阅读:
    鹏哥C语言36-37---循环/分支语句练习(折半查找算法)
    Labview CIE ColorTool Lib (源码vi)
    Java工具封装:Html、Css、Javascript文件内容压缩
    静电监控在半导体行业中的应用
    总结,复习,整合命令执行漏洞实现及其绕过(绕过方式大全)
    初识GraphQL
    因果系列文章(9)——反事实(下)
    maven笔记
    MFC Windows 程序设计[220]之扩展对话框(附源码)
    FlashDuty Changelog 2023-09-21 | 自定义字段和开发者中心
  • 原文地址:https://blog.csdn.net/XiugongHao/article/details/133859236