前端也要掌握的jwt登录校验

1,342 阅读4分钟

🌝目的

讲述需求

个人项目涉及登录操作,那么我选择jwt作为校验规则。
优点和对比这种“废话”就放在最后说了。
不知道jwt?这里面有我之前面试前总结的一些知识。


✅代码

前言

环境:后端Express、前端任意框架都可

代码记录

安装(没错,它全名叫jsonwebtoken)

npm install jsonwebtoken

引入

const jwt = require('jsonwebtoken');

下发(先说下发再说校验和续期)

let randomCode = 0; // 保存验证码
let codeTimestamp; // 时间戳,这个要在发送验证码或者你的登录操作时候保存
/** 
  * @param {code} //参数
  * @method 校验验证码|下发jwt
  * @return {Authorization}
  */

router.post('/veifycode', async (req, res, next) => {
  const { code } = req.body
  // 比较用户输入的验证码与之前保存的随机验证码
  if (code === randomCode) {
    const nowTimestamp = Date.now();
    const timeDifference = nowTimestamp - codeTimestamp; //时间对比
    const validDuration = 300000;//验证码有效期5分钟||你可以根据自己选择设置
    if (timeDifference <= validDuration) { //在5分钟内的验证码才有效
      const secretKey = uuid; // 秘钥!!
      // '重点:上线环境一定要设置成环境变量,泄露的话 你就gg吧~'
      // 提供一个生成uuid的网址:https://www.uuid.online/
      const payload = { stuId: stuid }; //jwt的'体' 一般是用户的信息,我这里设置学号
      const options = { expiresIn: '2h' };// 过期时间
      jwt.sign(payload, secretKey, options, (err, token) => {
      // 重中之重:jwt的生成固定写法,注意参数写全
        if (err) {
          console.error('生成 JWT 出错:', err);
          return res.status(500).json({ code: 500, message: '生成 JWT 出错' });
        }
        // 成功生成 JWT,将 JWT 返回给客户端
        res.setHeader('Access-Control-Allow-Origin', '*');
        res.setHeader('Access-Control-Expose-Headers', 'Authorization');
        res.setHeader('Authorization', `Bearer ${token}`);
        // 我一般设置格式是 Authorization里bearer后边跟token,后边演示取的操作
        return res.status(200).json({ message: '验证码输入正确!' });//这时候已经下发成功了
      });
    } else {
      // 验证码已过期,返回错误信息给前端
      return res.status(401).send({ code: 401, message: '验证码已过期!' });
    }
  } else {
    return res.status(401).send({ code: 401, message: '验证码错误!' });
  }
})

注意:
image.png
框框中这一行如果不写,你的生产环境的请求里没有authorization,也就是headers里没有这个信息,jwt校验自然崩了。我排查了很久,查到是要写这个(作为前端,我也不知道原因。。。)
详细的可以看这篇文章

校验jwt和续期

因为如果用户一直有操作的情况下:
1、我们还要秉承之前的两个小时过期吗?--那用户岂不是玩着玩着突然过期了?
2、所以我们要进行 续期
我前端一般会在路由守卫里进行/verify/jwt的请求,每次请求查看jwt是否合格和过期

/** 
  * @param {headers.authorization}
  * @method 校验jwt|续期
  */

router.get('/verify/jwt', async (req, res, next) => {
  const token = req.headers.authorization;// 从请求头中取出
  if (!token) {
    return res.status(401).json({ message: 'No JWT token provided.' });
  }
  try {
    // 取出的格式为`Bearer ${****}`,就是刚才我说的设置风格
    //jwt.verify() 是jwt的校验固定写法,传入token信息和秘钥
    const decoded = jwt.verify(token.replace('Bearer ', ''), uuid);
    const isExpired = Date.now() >= decoded.exp * 1000;
    if (isExpired) {
      return res.status(401).json({ message: 'JWT令牌已过期' });
    }
    // 设置新的jwt发给前端
    const newToken = jwt.sign({ stuId: stuid }, uuid, { expiresIn: '2h' });
    res.setHeader('Authorization', `Bearer ${newToken}`);
    res.status(200).json({ message: 'JWT token is valid.' });
  } catch (err) {
    // 篡改或者其他操作,jwt校验失败
    return res.status(401).json({ message: '无效的JWT令牌' });
  }
});

前端的操作比较简单

你只需把1、请求头取出来 2、存进内存 3、每次请求带上.....
像极了大象塞进冰箱...
所以这就是网喷的前端狗都会?😅(我不信~)
好了不说了,两个拦截器演示:

http.instance.interceptors.request.use(config => {
  // 内存中拿出来,带上
    const jwt = localStorage.getItem('jwt')
    if (jwt) {
      config.headers!.Authorization = `Bearer ${jwt}`
    }
    *.....其他代码就省略了*
    return config
  })
http.instance.interceptors.response.use((response) => {
  // 取出来、存进去
  const jwt = (response.headers.authorization as string)?.split(' ')[1]
  jwt && localStorage.setItem('jwt', jwt)
  *.....其他代码就省略了*
  return response
}, (error: AxiosError) => {
  throw error
})

刚才说的路由拦截器(每次跳转前发请求校验jwt):

router.beforeEach(async (to, from) => {
  if (to.path === '/' || to.path === '/student/detail' || to.path.startsWith('/welcome') || to.path.startsWith('/login')) {
    return true //白名单不校验
  } else {
    try {
      const info = localStorage.getItem('info')
      if (!info) {
        return '/login?return_to=' + from.path
      }
      await http.get('/user/verify/jwt') //重点操作在这里~
      return true
    } catch (error) {
      return '/login?return_to=' + from.path
    }
  }
})

▶️严格模式

前端做了每次请求前的校验,那后端又不相信前端,所以......

我有两种方法

1、中间件:每个请求加多写几个字 ❌(这谁能容忍。。)

2、全局校验:请看代码 ✅(这个还可以)

// 白名单
const excludesPath = ['/api/work/mywork', '/api/user/addclassId', '/api/work']
app.use((req, res, next) => {
  // 按自己具体需要设置白名单吧! 除了白名单以内所有都要校验
  if (rexcludesPath.some(path => path === req._parsedOriginalUrl.pathname)) {
    next()
    return
  }
  const token = req.header('Authorization')?.replace('Bearer ', '');
  const secretKey = uuid;
  jwt.verify(token, secretKey, (err, decoded) => {
    if (err) {
      // jwt不合格的就返回指定参数和信息
      res.status(401).json({ message: '你没有权限!' })
    } else {
      next();
    }
  });
})

🎦结语

有无描述

JWT 的优点:

  1. 自包含性: JWT 包含了所需的所有信息,因此无需查找数据库或服务来验证用户。这使得它可以轻松地在各个服务之间传递和共享。
  2. 可扩展性: 你可以将任何信息添加到 JWT 的负载中,使其适应不同的使用场景。这使得 JWT 非常灵活。
  3. 跨平台和语言支持: JWT 在不同编程语言和平台之间都是可互操作的。这意味着你可以在前端、后端和移动应用程序中使用相同的 JWT。
  4. 无状态和分布式: 由于 JWT 是自包含的,服务器不需要在会话中存储任何状态。这在构建无状态和分布式系统时非常有用。
  5. 签名和加密: JWT 可以被签名来确保其内容未被篡改,并且可以被加密以保护敏感信息。这使得 JWT 在安全性方面具备一定的保障。
  6. 减少数据库查询: 由于 JWT 包含了一些基本信息,如用户的 ID 和角色,你不必在每个请求中查询数据库来获取用户信息。

JWT 的缺点:

  1. 潜在的安全性问题: 如果密钥丢失或被泄露,攻击者可能会伪造 JWT 或解密其内容。因此,密钥管理非常重要。
  2. Token 大小: JWT 的载荷可能会包含大量的信息,导致 Token 变得较大。这可能在网络传输中产生额外的开销。
  3. 不适合存储敏感信息: 尽管可以加密 JWT,但最好避免在其中存储敏感信息,因为它们可以在客户端被解码。
  4. 无法即时失效: 一旦 JWT 发出,除非在有效期内过期,否则无法立即使其失效。这可能会在某些情况下导致问题。
  5. 不支持注销: 一旦 JWT 发出,服务器无法立即使其失效。注销一个 JWT 需要在客户端存储黑名单或增加其他机制。

这个格式肯定是AI写的啦~ 慢慢食用各位大哥(写错轻喷哈,我是弱实习生)

有用,点个赞再走吧? 👀