MERN博客部署后添加评论报401 Unauthorized错误求助
本地开发环境及Postman测试均正常,后端API部署在Render、前端部署在Vercel后,前端调用添加评论的POST接口时返回401 Unauthorized错误,无法正常添加评论,问题定位在认证环节。
相关代码
前端PostDetails组件代码
import { useNavigate, useParams } from "react-router-dom" import Comment from "../components/Comment" import Footer from "../components/Footer" import Navbar from "../components/Navbar" import {BiEdit} from 'react-icons/bi' import {MdDelete} from 'react-icons/md' import axios from "axios" import { URL,IF } from "../url" import { useContext, useEffect, useState } from "react" import { UserContext } from "../context/UserContext" const PostDetails = () => { const postId=useParams().id const [post,setPost]=useState({}) const {user}=useContext(UserContext) const [comments,setComments]=useState([]) const [comment,setComment]=useState("") const navigate=useNavigate() const fetchPost=async()=>{ try{ const res= await axios.get(URL+"/api/posts/"+postId) setPost(res.data) } catch(err){ console.log(err) } } useEffect(()=>{ fetchPost() },[postId]) const handleDeletePost=async ()=>{ try{ const res=await axios.delete(URL+"/api/posts/"+postId,{withCredentials:true}) console.log(res.data) navigate("/") } catch(err){ console.log(err) } } const fetchPostComments=async()=>{ try{ const res=await axios.get(URL+"/api/comments/post/"+postId,{withCredentials:true}) setComments(res.data) } catch(err){ console.log(err) } } useEffect(()=>{ fetchPostComments() },[postId]) const postComment=async(e)=>{ e.preventDefault() try{ const res=await axios.post(URL+"/api/comments/create", {comment:comment,author:user.username,postId:postId,userId:user._id}, {withCredentials:true}) console.log(res.data) fetchPostComments() setComment("") window.location.reload(true) } catch(err){ console.log(err) } } return ( <div> <Navbar/> <br/> <br/> <br/> <div className="px-8 md:px-[200px] mt-8"> <div className="flex items-center justify-between"> <h1 className="text-2xl font-bold text-black md:text-3xl">{post.title}</h1> <div className="flex items-center justify-center space-x-2"> <p className="cursor-pointer" onClick={()=>navigate("/edit/"+postId)}><BiEdit/></p> <p className="cursor-pointer" onClick={handleDeletePost}><MdDelete/></p> </div> </div> <div className="flex items-center justify-between mt-2 md:mt-4"> <p>@{post.username}</p> <div className="flex space-x-2"> <p>{new Date(post.updatedAt).toString().slice(0,10)}</p> <p>{new Date(post.updatedAt).toString().slice(16,24)}</p> </div> </div> <img src={IF+post.photo} className="w-full mx-auto mt-8" alt=""/> <p className="mx-auto mt-8">{post.desc}</p> <div className="flex items-center mt-8 space-x-4 font-semibold"> <p>Categories:</p> <div className="flex items-center justify-center space-x-2"> {post.categories?.map((c,i)=>( <> <div key={i} className="px-3 py-1 bg-gray-300 rounded-lg">{c}</div> </> ))} </div> </div> <div className="flex flex-col mt-4"> <h3 className="mt-6 mb-4 font-semibold">Comments:</h3> {comments?.map((c)=>( <Comment key={c._id} c={c} post={post} /> ))} </div> <div className="flex flex-col mt-4 md:flex-row"> <input onChange={(e)=>setComment(e.target.value)} type="text" placeholder="write a comment" className="md:w-[90%] outline-none px-4 mt-4 md:mt-0"/> <button onClick={postComment} className="bg-black text-sm text-white px-2 py-2 md:w-[20%] mt-4 md:mt-0">Add Comment</button> <br/> </div> </div> <Footer/> </div> ) } export default PostDetails
后端评论接口代码
const express=require('express') const router = express.Router(); const Comment=require('../models/Comment'); const verifyToken = require('../verifyToken'); //CREATE router.post("/create",verifyToken,async(req,res)=>{ try{ const newComment=new Comment(req.body) const savedComment=await newComment.save() res.status(200).json(savedComment) } catch(err){ res.status(200).json(err) } }) //UPDATE router.put("/:id",verifyToken,async (req,res)=>{ try{ const updatedComment=await Comment.findByIdAndUpdate(req.params.id,{$set:req.body},{new:true}) res.status(200).json(updatedComment) } catch(err){ res.status(500).json(err) } }) //DELETE router.delete("/:id",verifyToken,async (req,res)=>{ try{ await Comment.findByIdAndDelete(req.params.id) res.status(200).json("Comment has been deleted!") } catch(err){ res.status(500).json(err) } }) //GET POST COMMENT router.get("/post/:postId",async (req,res)=>{ try{ const comments=await Comment.find({postId:req.params.postId}) res.status(200).json(comments) } catch(err){ res.status(500).json(err) } }) module.exports=router
Comment模型代码
const mongoose=require('mongoose') const CommentSchema=new mongoose.Schema({ comment:{ type:String, required:true, }, author:{ type:String, required:true, }, postId:{ type:String, required:true, }, userId:{ type:String, required:true } },{timestamps:true}) module.exports=mongoose.model("Comment",CommentSchema)
服务器主文件代码
const express=require('express') const app=express() const mongoose=require('mongoose') const dotenv=require('dotenv') const cors=require('cors') const path=require("path") const multer=require('multer') const cookieParser=require('cookie-parser') const authRoute=require('./routes/auth') const userRoute=require('./routes/users') const postRoute=require('./routes/posts') const commentRoute = require("./routes/comment") const ottRoute=require('./routes/ott') const releaseRoute=require('./routes/release') const responseRoute=require('./routes/response'); const ratingRoute=require('./routes/rating') //database const connectDB=async()=>{ try{ await mongoose.connect(process.env.MONGO_URL) console.log("database is connected successfully!") } catch(err){ console.log(err) } } //middlewares dotenv.config() app.use(express.json()) app.use("/images",express.static(path.join(__dirname,"/images"))) app.use(cors({ origin: "http://localhost:5173", credentials: true })); app.use(cookieParser()) app.use("/api/auth",authRoute) app.use("/api/users",userRoute) app.use("/api/posts",postRoute) app.use("/api/comments", commentRoute) app.use("/api/otts",ottRoute); app.use("/api/releases",releaseRoute) app.use("/api/responses",responseRoute) app.use("/api/rating",ratingRoute) //image upload const storage=multer.diskStorage({ destination:(req,file,fn)=>{ fn(null,"images") }, filename:(req,file,fn)=>{ fn(null,req.body.img) } }) const upload=multer({storage:storage}) app.post("/api/upload",upload.single("file"),(req,res)=>{ res.status(200).json("Image has been uploaded successfully!") }) app.listen(process.env.PORT,()=>{ connectDB() console.log("app is running on port "+process.env.PORT) })
verifyToken中间件代码
const jwt=require('jsonwebtoken') const verifyToken=(req,res,next)=>{ const token=req.cookies.token console.log(token) if(!token){ return res.status(401).json("You are not authenticated!") } jwt.verify(token,process.env.SECRET,async (err,data)=>{ if(err){ return res.status(403).json("Token is not valid!") } req.userId=data._id console.log("passed") next() }) } module.exports=verifyToken
问题修复方案
1. 修正CORS配置
后端当前CORS只允许本地域名,需要添加Vercel前端域名:
// 服务器主文件中替换原CORS配置 const allowedOrigins = [ "http://localhost:5173", "https://your-vercel-domain.vercel.app" // 替换为你的实际Vercel前端域名 ]; app.use(cors({ origin: (origin, callback) => { // 允许无origin的请求(如Postman) if (!origin || allowedOrigins.includes(origin)) { callback(null, true); } else { callback(new Error("Not allowed by CORS")); } }, credentials: true }));
或者使用环境变量动态配置(推荐生产环境):
// 在Render控制台添加环境变量FRONTEND_URL,值为你的Vercel域名 app.use(cors({ origin: process.env.FRONTEND_URL, credentials: true }));
2. 完善Cookie跨域属性
登录接口设置Cookie时,需添加sameSite: 'None'和secure: true(线上HTTPS环境必须):
// 找到登录接口中设置token的代码,修改为: res.cookie("token", token, { httpOnly: true, secure: process.env.NODE_ENV === "production", // 生产环境启用HTTPS安全属性 sameSite: process.env.NODE_ENV === "production" ? "None" : "Lax", maxAge: 7 * 24 * 60 * 60 * 1000 // 7天有效期 });
3. 优化Axios全局配置
前端全局开启withCredentials,避免每个请求重复设置:
// 在前端入口文件(如src/main.jsx)添加 import axios from 'axios'; import { URL } from './url'; axios.defaults.baseURL = URL; axios.defaults.withCredentials = true;
4. 日志排查验证
在verifyToken中间件添加详细日志,查看Render控制台日志确认token是否正常传递:
const verifyToken=(req,res,next)=>{ const token=req.cookies.token console.log("[VerifyToken] Received token:", token); if(!token){ console.log("[VerifyToken] No token found in cookies"); return res.status(401).json("You are not authenticated!") } jwt.verify(token,process.env.SECRET,(err,data)=>{ if(err){ console.log("[VerifyToken] Token verification failed:", err.message); return res.status(403).json("Token is not valid!") } req.userId=data._id console.log("[VerifyToken] Authenticated user ID:", req.userId); next() }) }
内容的提问来源于stack exchange,提问作者dlhq__
相关产品推荐
相关产品推荐

