Node.js中使用JWT、localStorage、Cookie校验用户信息

目录
文章目录隐藏
  1. 一. 概念
  2. 二. JWT 的生成与使用
  3. 三. 应用场景
  4. 结语

本文分别站在了客户端(React.js)与服务端(Node.js)的角度,总结了整个用户校验过程各自的操作。在 Node.js 中使用 JWT、localStorage 和 Cookie 校验用户信息是一种常见的做法。JWT(JSON Web Token)是一种基于 JSON 的开放标准,用于在各方之间安全地传输信息。它由头部、载荷和签名组成,其中载荷包含有关用户的信息和其他数据。在服务端,首先需要验证 JWT 的合法性和有效性,通过验证签名是否正确以及验证过期时间来确保 Token 的有效性。校验过程包括解析 JWT、比较签名、检查过期时间等步骤。如果验证通过,可以从 JWT 中获取用户信息,并将用户信息放入请求对象中,供后续操作使用。

一. 概念

1.1 localStorage 和 Cookie

都是存储数据的方式

  • localStorage:储存在客户端(浏览器)本地
  • Cookie:存储在服务端,安全性更高。(是一个 HTTP 请求标头,由服务器通过 Set-Cookie 设置,存储到客户端的 HTTP cookie

1.2 Token/JWT 和 SessionId

都是用户信息标识

  • Token:一个通用术语,是代表用户身份的字符串。它通常由服务器在用户成功登录后生成,并在用户进行后续请求时发送给服务器以验证其身份。
  • JWT(JSON Web Token):一种特殊的 Token。由三部分组成的字符串:Header(令牌类型和签名算法)、Payload(用户信息)、Signature 组成
  • SessionId:用来识别和追踪用户会话的一串唯一的字符

本文主要讲 JWT

二. JWT 的生成与使用

安装 JWT 库,官网链接:点击这里

2.1 安装 JWT 库

npm i jsonwebtoken

2.2 登录时生成 JWT

const jwt = require('jsonwebtoken');

const login = async (req, res) => {
   // ...登录成功后
   
   const token = jwt.sign(
      { userId: <userId>, username: <username> }, // 填入想存储的用户信息
      process.env.JWT_SECRET,                     // 秘钥,可以为随机一个字符串
      {
         expiresIn: "7d",                         // 其他选项,如过期时间
      }
   );
   
   // ...
};

接着就是选择存储方式:

  1. 将 token 返回到客户端让客户端存储在 localStorage;
  2. 将 token 存储在服务端 Cookie。

2.3 调用其他请求时验证 Token

// 验证的中间件

const authToken = async (req, res, next) => {
  
  // ... 根据存储方式拿到 token
  const token = "your_token"
  
   try {
      const decoded = jwt.verify(token, process.env.JWT_SECRET); // 传入 token 和秘钥
      // 拿到解出来的 { userId, username }
      // ... 进一步从数据库中判断这个用户信息是否存在
      // 将信息挂载 req.user 中供后续接口使用
      req.user = { userId, username, ... };
      next();
   } catch (error) {
       res.status(401).json({msg:"用户验证失败"})
   }
};

三. 应用场景

  1. JWT & localStorage
  2. JWT & Cookie

3.1 存储在 localStorage

  1. 服务端:将 token 返回给客户端
    const login = async (req, res) => {
       // ...登录成功后
       // ...生成完 token
       const token = "your_token"
       
       // 将 token 返回给客户端
       res.status(StatusCodes.OK).json({
           msg: '登录成功',
           token,
        });
    };
    
  2. 客户端:将 token 存储到 localStorage,并在后续请求中将 token 发送给服务端。为了方便管理,这里简单封装了下 aixos:
    import toast from 'react-hot-toast';
    
    // 创建 axios 实例,把本地的 token 放在 header 中:
    const axiosInstance = axios.create({
         baseURL: '/api/v1',
         timeout: 3000,
         headers: { 'Authorization': 'Bearer ' + localStorage.getItem('token') }, // 每个请求都自动携带 token
     });
     
     // 是否显示成功的提示或者失败的提示
     const defaultConfig = {
         showError: true,
         showSuccess: false
     }
     const request = (url: string, config= {}) => {
         const _config = {
             ...defaultConfig,
             ...config
         }
         const { data, params } = _config
         const method = _config.method || 'get'
    
         return axiosInstance.request({
             url,
             method,
             data: data || {},
             params: params || {},
         }).then((res) => {
             const data = res.data;
             _config.success && _config.success(data);
             if (_config.showSuccess) toast.success(data.msg || '请求成功');
             return data as TResData<T>
         }).catch((err) => {
             if (err.response.status >= 500) {
                 toast.error('服务器发生错误,请稍后再试')
             }
             // 如果用户校验失败,重新返回登录页
             if (err.response.status === 401) {
                 toast.error('用户凭证出现问题,请重新登录')
                 location.href = '/login'  
             }
             // 其他错误
             let data = err.response.data
             _config.error && _config.error(data)
             if (_config.showError) toast.error(data.msg || '未知错误')
             return data
         })
     }
    

现在基于这个封装好的 request,写一下示例。

(1) 登录时存储 token

request('/login', {
      method: 'POST',
      data: {
         username,
         password,
      },
      showSuccess: true,
      success: (data) => {
         localStorage.setItem('token', data.token); // 登录成功后将 token 存储
         location.href = '/home'; // 跳转到主页 
      },
   });

结果如下:

登录时存储 token

(2) 其他请求:自动在 Header 上携带 token

request('/stats');

结果如下:

自动在 Header 上携带 token

(3)退出登录:清除 localstorage 的 token

request('/logout', {
  success: (data) => {
     localStorage.removeItem('token'); // 清除 tokn
     location.href = '/login'; // 跳转到登录页
  },
});

拿到客户端发过来的 token 进行验证:

// 用户验证中间件
const authToken = async (req, res, next) => {
    // 获取 token
   const authHeader = req.headers.authorization;

   if (!authHeader || !authHeader.startsWith('Bearer ')) {
      res.status(401).json({msg:"No token provided"})
   }

   const token = authHeader.split(' ')[1];
   
   // 验证 token
   try {
      const decoded = jwt.verify(token, process.env.JWT_SECRET);
      // 以 mongose 为例
      const user = await User.findById(decoded.userId).select('-password');
      req.user = { userId: user._id, username: user.username, email: user.email };
      next();
   } catch (error) {
       res.status(401).json({msg:"用户验证失败"})
   }
};

在其他请求中加上中间件:

app.use('/api/v1/jobs', authToken, jobsRoute);

3.2 存储在 Cookie

  1. (可选)服务端:安装 Cookie 解析库
    npm i cookie-parser
    // app.js
    
    const cookieParser = require('cookie-parser');
    app.use(cookieParser());
    // 或加密
    // app.use(cookieParser(process.env.COOKIE_SECRET, { signedCookies: true }));
    
  2. 服务端:将 token 存储在 Cookie 中
    const login = async (req, res) => {
       // ...登录成功后
       // ...生成完 token
       const token = "your_token"
       
       // 安装 cookie-parser 后可以这样写
       const oneDay = 1000 * 60 * 60 * 24;
       res.cookie('token', token, {
          httpOnly: true,
          expires: new Date(Date.now() + oneDay),
          secure: process.env.NODE_ENV === 'production',
          signed: true,
       });
       
       // 它实际上进行操作是:
       /**
          let cookieString = `token=${token}; Expires=${oneDay}; HttpOnly`;
          if (process.env.NODE_ENV === 'production') {
             cookieString += '; Secure';
          }
          res.setHeader('Set-Cookie', cookieString);
       */
       
         res.status(StatusCodes.OK).json({
           msg: '登录成功'
        });
    };
    

    结果如下:将 token 存储在 Cookie 中

  3. 客户端:不需要存储 token,也不需要在请求头携带 token 了,只需要根据服务端返回的 status code 来判断是否跳转回登录页
    // 依旧是使用上面封装好的 request
    
    const axiosInstance = axios.create({
        baseURL: '/api/v1',
        timeout: 1000,
        // headers: { 'Authorization': 'Bearer ' + localStorage.getItem('token') },
    });
    
    
    // ...
    .catch(()=>{
         if (err.response.status === 401) {
            toast.error('用户凭证出现问题,请重新登录')
            location.href = '/login'
        }
    })
    
  4. 服务端:对于其他请求,拿到 Cookie 的 token 进行验证其他请求的请求头部的 Cookie 将会多一个 token 信息:请求的请求头部的 Cookie 将会多一个 token 信息
    // 用户验证中间件
    const authToken = async (req, res, next) => {
       const token = req.cookie.token;  
       // 等同于:req.headers.cookie.split('=')[1]
       // 如果上面的 signed 为 true, 则  const token = req.signedCookies.token;
    
       if (!token) {
          res.status(401).json({msg:"No token provided"})
       }
    
       try {
          const decoded = jwt.verify(token, process.env.JWT_SECRET); // 传入 token 和秘钥
          // 拿到解出来的 { userId, username }
          // 将信息挂载 req.user 中供后续接口使用
          req.user = { userId, username, ... };
          next();
       } catch (error) {
           res.status(401).json({msg:"用户验证失败"})
       }
    };
    

    使用中间件:

    app.use('/api/v1/groups', authToken, groupsRoute);
    
  5. 服务端:对于退出登录,还需要清除 Cookie 的 token
    const logout = async (req, res) => {
       res.clearCookie('token')
       res.status(StatusCodes.OK).json({
          msg:'成功退出'
       })
    }

结语

综上所述,Node.js 中使用 JWT、localStorage 和 Cookie 校验用户信息是一种可行的方法。服务端通过校验 JWT 的合法性和有效性,将用户信息放入请求中;客户端通过将 JWT 存储在 localStorage 或 Cookie 中,实现用户信息的传递和校验。这种方法灵活且安全,可应用于各类 Node.js 应用中。

「点点赞赏,手留余香」

1

给作者打赏,鼓励TA抓紧创作!

微信微信 支付宝支付宝

还没有人赞赏,快来当第一个赞赏的人吧!

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系maynote@foxmail.com处理
码云笔记 » Node.js中使用JWT、localStorage、Cookie校验用户信息

发表回复