本文分别站在了客户端(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", // 其他选项,如过期时间 } ); // ... };
接着就是选择存储方式:
- 将 token 返回到客户端让客户端存储在 localStorage;
- 将 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:"用户验证失败"}) } };
三. 应用场景
- JWT & localStorage
- JWT & Cookie
3.1 存储在 localStorage
- 服务端:将 token 返回给客户端
const login = async (req, res) => { // ...登录成功后 // ...生成完 token const token = "your_token" // 将 token 返回给客户端 res.status(StatusCodes.OK).json({ msg: '登录成功', token, }); };
- 客户端:将 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'; // 跳转到主页 }, });
结果如下: