Node.js中使用JWT、localStorage、Cookie校验用户信息
本文分别站在了客户端(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'; // 跳转到主页 }, });
结果如下:
(2) 其他请求:自动在 Header 上携带 token
request('/stats');
结果如下:
(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
- (可选)服务端:安装 Cookie 解析库
npm i cookie-parser
// app.js const cookieParser = require('cookie-parser'); app.use(cookieParser()); // 或加密 // app.use(cookieParser(process.env.COOKIE_SECRET, { signedCookies: true }));
- 服务端:将 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,也不需要在请求头携带 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' } })
- 服务端:对于其他请求,拿到 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);
- 服务端:对于退出登录,还需要清除 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 应用中。
码云笔记 » Node.js中使用JWT、localStorage、Cookie校验用户信息
![](https://media.mybj123.com/wp-content/uploads/2024/02/1706962282-d2a91681b352c3a.png)