登录后JWT获取与验证异常:发布帖子提示jwt must be provided
问题:发布帖子时抛出「jwt must be provided」错误
我是编程初学者,正在用React JS、Node.js Express和MySQL开发一个支持注册、登录、发帖子的社交网络。注册和登录功能正常,但发布帖子时出现以下错误:
C:\Users\sever\code\REACT\2022-03-JS-Nantes-Dear\backend\node_modules\jsonwebtoken\verify.js:53 return done(new JsonWebTokenError('jwt must be provided')); ^ JsonWebTokenError: jwt must be provided
相关后端代码
PostController.js
const Joi = require("joi"); const dayjs = require("dayjs"); const jwt = require("jsonwebtoken"); const models = require("../models"); class PostController { static add = async (req, res) => { const { content, categoryId } = req.body; const { accessToken } = req.body; const user = jwt.verify(accessToken, process.env.JWT_AUTH_SECRET); const validationErrors = Joi.object({ content: Joi.string().max(255).required(), categoryId: Joi.number().required(), }).validate({ content, categoryId }).error; if (validationErrors) { res.status(422).send(validationErrors); return; } models.post .insert({ content, userId: user.id, categoryId: parseInt(categoryId, 10), createdAt: dayjs().format("YYYY-MM-DD HH:mm:ss"), likers: 0, signals: 0, }) .then(([result]) => { res.status(201).send({ ...result, id: result.insertId, }); }) .catch((err) => { console.error(err); res.status(500).send({ error: err.message, }); }); }; } module.exports = PostController;
PostManager.js
const AbstractManager = require("./AbstractManager"); class PostManager extends AbstractManager { static table = "post"; insert(post) { return this.connection.query( `insert into ${PostManager.table} (content, userId, categoryId, createdAt, likers, signals) values (?, ?, ?, ?, ?, ?)`, [ post.content, post.userId, post.categoryId, post.createdAt, post.likers, post.signals, ] ); } } module.exports = PostManager;
post.routes.js
const express = require("express"); const { PostController } = require("../controllers"); const routes = express.Router(); routes.get("/post", PostController.browse); routes.get("/post/:id", PostController.read); routes.put("/post/:id", PostController.edit); routes.post("/post/posttext", PostController.add); routes.delete("/post/:id", PostController.delete); module.exports = routes;
前端相关代码(PostText组件)
import { React, useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; import "./styles/PostText.css"; import axios from "axios"; import swal from "sweetalert"; import ButtonReturnGreen from "./ButtonReturnGreen"; import ButtonPublierPost from "./ButtonPublierPost"; function PostText() { const [content, setContent] = useState(""); const [categoryId, setCategoryId] = useState(""); const [categories, setCategories] = useState([]); const navigate = useNavigate(); useEffect(() => { axios .get(`${import.meta.env.VITE_BACKEND_URL}/category`) .then((res) => setCategories(res.data)) .catch((err) => { console.error(err); }); }, []); const handleSubmit = (e) => { e.preventDefault(); if (!content || !categoryId) { swal({ title: "Error!", text: "Merci de spécifier la categorie et de publier un post", icon: "error", confirmButtonText: "parfait", }); } else { axios .post( `${import.meta.env.VITE_BACKEND_URL}/post/posttext`, { content, categoryId }, { withCredentials: true } ) .then(() => navigate("/home", { replace: true })) .catch((err) => { console.warn(err); }); } }; return ( <div className="div-post-texte"> <div className="title-post-category"> <ButtonReturnGreen /> <h1>À quelle catégorie</h1> <h1>appartient ce post?</h1> </div> <div className="form-post-text"> <select className="option-category-post" name="category" id="share-select" value={categoryId} onChange={(e) => setCategoryId(e.target.value)} > <option value="0">--Choisir une catégorie--</option> {categories.map((category) => ( <option key={category.id} value={category.id}> {category.name} </option> ))} </select> </div> <div className="bloc-texte-post"> <h1 className="title-post-message"> Écrit ton message ici :</h1> <form className="form-post"> <label className="postform" htmlFor="post"> <textarea className="inputformpost" type="content" name="content" id="content" placeholder="Partagez un message, une question, une expérience, un doute... avec la communauté! La sexualité n'est plus un tabou! Ta publication est anonyme." value={content} onChange={(e) => setContent(e.target.value)} /> </label> </form> </div> <div className="button-publier-post"> <ButtonPublierPost handleSubmit={handleSubmit} /> </div> </div> ); } export default PostText;
问题原因与解决方案
原因
后端从请求body里获取accessToken,但前端发布帖子时根本没传递这个token,导致jwt.verify因缺少token抛出错误。此外,直接在请求体里传token不符合REST规范,标准做法是通过Authorization请求头传递。
解决方案
1. 前端:存储并传递JWT Token
- 登录成功后,把后端返回的
accessToken存入localStorage(或React Context/Redux,根据项目规模选择):// 登录接口成功回调示例 axios.post('/login', { email, password }) .then(res => { localStorage.setItem('accessToken', res.data.accessToken); navigate('/home'); }) - 修改发布帖子的请求,在请求头里带上token:
const handleSubmit = (e) => { e.preventDefault(); if (!content || !categoryId) { swal({ title: "Error!", text: "Merci de spécifier la categorie et de publier un post", icon: "error", confirmButtonText: "parfait", }); } else { // 从localStorage获取token const accessToken = localStorage.getItem('accessToken'); axios .post( `${import.meta.env.VITE_BACKEND_URL}/post/posttext`, { content, categoryId }, { withCredentials: true, headers: { // 标准Bearer认证格式 Authorization: `Bearer ${accessToken}` } } ) .then(() => navigate("/home", { replace: true })) .catch((err) => { console.warn(err); }); } };
2. 后端:用中间件统一验证JWT
- 创建JWT验证中间件(
middleware/auth.js):const jwt = require('jsonwebtoken'); module.exports = (req, res, next) => { try { // 从请求头提取token,分割"Bearer "前缀 const token = req.headers.authorization.split(' ')[1]; const decodedUser = jwt.verify(token, process.env.JWT_AUTH_SECRET); // 将解析后的用户信息挂载到req对象,供后续控制器使用 req.user = decodedUser; next(); } catch (error) { return res.status(401).json({ error: '无效或缺失的身份验证token' }); } }; - 修改路由,给发布帖子接口添加中间件保护:
const express = require("express"); const { PostController } = require("../controllers"); const authMiddleware = require('../middleware/auth'); // 引入中间件 const routes = express.Router(); routes.get("/post", PostController.browse); routes.get("/post/:id", PostController.read); routes.put("/post/:id", PostController.edit); // 添加中间件,验证通过才允许访问add方法 routes.post("/post/posttext", authMiddleware, PostController.add); routes.delete("/post/:id", PostController.delete); module.exports = routes; - 修改PostController,移除从body取token的代码,直接使用中间件解析后的用户信息:
static add = async (req, res) => { const { content, categoryId } = req.body; // 删掉以下两行: // const { accessToken } = req.body; // const user = jwt.verify(accessToken, process.env.JWT_AUTH_SECRET); // 直接用中间件挂载的req.user const user = req.user; // 后续验证和插入逻辑不变 const validationErrors = Joi.object({ content: Joi.string().max(255).required(), categoryId: Joi.number().required(), }).validate({ content, categoryId }).error; if (validationErrors) { res.status(422).send(validationErrors); return; } models.post .insert({ content, userId: user.id, categoryId: parseInt(categoryId, 10), createdAt: dayjs().format("YYYY-MM-DD HH:mm:ss"), likers: 0, signals: 0, }) .then(([result]) => { res.status(201).send({ ...result, id: result.insertId, }); }) .catch((err) => { console.error(err); res.status(500).send({ error: err.message, }); }); };
这样修改后,前端会正确传递token,后端通过中间件统一验证身份,就能解决「jwt must be provided」的错误,同时代码更符合规范、易于维护。
内容的提问来源于stack exchange,提问作者Séverine
相关产品推荐
相关产品推荐

