NodeJS Express.js JWT身份验证问题:用户令牌在本地存储中可用,但收到未验证的消息

myss37ts  于 2023-08-04  发布在  Node.js
关注(0)|答案(1)|浏览(152)

我想提交一份评论。但是在我的verifyToken和verifyUser中间件中,我没有得到值。相反,它说的是值是未定义的,尽管我在控制台中获得了令牌和用户名。
由于我没有在中间件中获得用户信息和令牌,它说我没有授权,并阻止我提交审查。如果我将verifyToken和verifyUser中间件从审查路径中删除,那么它可以正常工作。但如果我添加了它们,那么在终端中会显示:

req.user: undefined

Token: undefined

字符串
虽然我在前端本地存储中获得令牌和用户。
我需要帮助来克服这个问题。感谢是预付款。
中间件:

const jwt = require("jsonwebtoken");

const verifyToken = (req, res, next) => {
  console.log("req.user:", req.user);
  console.log("req.params.tourId:", req.params.tourId);
  const token = req.cookies.accessToken;
  console.log("Token received in verifyToken:", token);
  console.log("Token:", token);

  if (!token) {
    return res.status(401).json({
      success: false,
      message: "You are not authorized",
    });
  }

  try {
    const user = jwt.verify(token, process.env.JWT_SECRET);
    console.log("Decoded User:", user);
    req.user = user;
    next();
  } catch (error) {
    console.error("Token Verification Error:", error);
    res.status(401).json({
      success: false,
      message: "Token is Invalid",
    });
  }
};

const verifyUser = (req, res, next) => {
  console.log("req.user:", req.user);
  console.log("req.params.tourId:", req.params.tourId);

  if (
    req.user &&
    (req.user.id === req.params.tourId || req.user.role === "admin")
  ) {
    next();
  } else {
    return res.status(401).json({
      success: false,
      message: "You're not authenticated",
    });
  }
};

const verifyAdmin = (req, res, next) => {
  if (req.user.role === "admin") {
    next();
  } else {
    return res.status(401).json({
      success: false,
      message: "You're not authorized",
    });
  }
};

module.exports = { verifyToken, verifyAdmin, verifyUser };


路线:

const express = require("express");
const router = express.Router();
const { verifyUser, verifyToken } = require("../utils/verifyToken");

const { createReview } = require("../controller/reviewController");

router.post("/:tourId", verifyToken, verifyUser, createReview);

module.exports = router;


控制器:

const Review = require("../model/Review");
const Tour = require("../model/Tour");

const createReview = async (req, res) => {
  const tourId = req.params.tourId;
  const newReview = new Review({ ...req.body });

  try {
    const savedReview = await newReview.save();

    await Tour.findOneAndUpdate(
      { _id: tourId },
      { $push: { reviews: savedReview._id } }
    );

    res.status(200).json({
      success: true,
      message: "Review Submitted",
      data: savedReview,
    });
  } catch (error) {
    res.status(500).json({
      success: false,
      message: error.message,
    });
    console.log(error);
  }
};

module.exports = { createReview };


TourDetails.jsx:

import React, { useEffect, useRef, useState } from "react";
import "../styles/TourDetails.css";
import { Container, Row, Col, Form, ListGroup } from "reactstrap";
import { useParams } from "react-router-dom";
import calculateAvgRating from "../utils/averageRating";
import { AiFillStar, AiOutlineStar } from "react-icons/ai";
import { MdLocationPin } from "react-icons/md";
import { HiUserGroup } from "react-icons/hi";
import { FaDollarSign } from "react-icons/fa";
import { GrLocation } from "react-icons/gr";
import { RiPinDistanceFill } from "react-icons/ri";
import avatar from "../assets/tour-images/avatar.jpg";
import Booking from "../components/Booking/Booking";

import { useSelector, useDispatch } from "react-redux";
import { getSingleTour } from "../redux/action/tourAction";
import { server } from "../server";
import axios from "axios";
import { toast } from "react-toastify";

const TourDetails = () => {
  const { id } = useParams();
  const dispatch = useDispatch();
  const reviewsMsgRef = useRef("");
  const [userRating, setUserRating] = useState(0);
  const [user, setUser] = useState({});
  const [submittedReview, setSubmittedReview] = useState(null);
  const [isLoggedIn, setIsLoggedIn] = useState(
    !!localStorage.getItem("accessToken")
  );

  // Fetch single tour when the component mounts
  useEffect(() => {
    dispatch(getSingleTour(id));
    window.scrollTo(0, 0);
  }, [dispatch, id]);

  useEffect(() => {
    setIsLoggedIn(!!localStorage.getItem("accessToken"));
    const storedUser = JSON.parse(localStorage.getItem("user"));
    if (storedUser) {
      setUser(storedUser);
    }
    console.log(user);
  }, []);

  const { tour, isLoading, error } = useSelector((state) => state.tour);

  if (isLoading) {
    return <div>Loading...</div>;
  }

  if (error) {
    return <div>{error}</div>;
  }

  if (!tour) {
    return <div>Tour not found.</div>;
  }

  const {
    photo,
    title,
    desc,
    price,
    reviews,
    address,
    city,
    distance,
    maxGroupSize,
  } = tour.data;

  const { totalRating, avgRating } = calculateAvgRating(reviews);

  const options = { day: "numeric", month: "long", year: "numeric" };

  const handleStarClick = (rating) => {
    setUserRating(rating);
  };

  const isRatingSelected = (rating) => rating <= userRating;

  const handleSubmitReview = async (e) => {
    e.preventDefault();
    const reviewText = reviewsMsgRef.current.value;
    const accessToken = localStorage.getItem("accessToken");

    if (!accessToken) {
      toast.error("Please login first to submit a review.");
      return;
    }

    try {
      const reviewObj = {
        username: user.name,
        reviewText,
        rating: userRating,
      };

      const res = await axios.post(`${server}/reviews/${id}`, reviewObj, {
        withCredentials: true,
        headers: {
          Authorization: `Bearer ${accessToken}`,
        },
      });

      // Store the submitted review data in the component state
      setSubmittedReview(res.data.data);

      toast.success("Review submitted successfully");
      console.log("Review submitted successfully:", res.data);
    } catch (error) {
      toast.error("Error submitting review");
      console.log(error);
    }
  };

  return (
    <section>
      <Container>
        <Row>
          <Col lg="8">
            <div className="tour_content">
              <img src={photo} alt="photo" />
              <div className="tour_info">
                <h2>{title}</h2>
                <div className="d-flex align-items-center gap-5">
                  {avgRating > 0 ? (
                    <span className="tour_rating d-flex align-items-center gap-1">
                      <AiFillStar className="tour_icon" />
                      {avgRating}
                      <span>({reviews?.length})</span>
                    </span>
                  ) : (
                    <span className="tour_rating">Not Rated</span>
                  )}

                  <span>
                    <GrLocation className="tour_icon" />
                    {address}
                  </span>
                </div>
                <div className="tour_extra-details d-flex align-items center">
                  <span>
                    <MdLocationPin className="tour_icon" />
                    {city}
                  </span>
                  <span>
                    <RiPinDistanceFill className="tour_icon" />
                    {distance} k/m
                  </span>
                  <span>
                    <FaDollarSign className="tour_icon" />$ {price}/per person
                  </span>

                  <span>
                    <HiUserGroup className="tour_icon" />
                    {maxGroupSize} people
                  </span>
                </div>
                <h5>Description</h5>
                <p>{desc}</p>
              </div>

              <div className="tour_reviews mt-4">
                <h4>Reviews ({reviews?.length} reviews)</h4>

                <Form onSubmit={handleSubmitReview}>
                  <div className="rating_group d-flex align-items-center gap-3 mb-4">
                    {[1, 2, 3, 4, 5].map((rating) => (
                      <span
                        key={rating}
                        onClick={() => handleStarClick(rating)}
                      >
                        {isRatingSelected(rating) ? (
                          <AiFillStar />
                        ) : (
                          <AiOutlineStar />
                        )}
                      </span>
                    ))}
                  </div>

                  <div className="review_input d-flex align-items-center">
                    <input
                      type="text"
                      ref={reviewsMsgRef}
                      placeholder="Share your thoughts"
                      required
                    />
                    <button
                      className="btn primary_btn text-white"
                      type="submit"
                    >
                      Submit
                    </button>
                  </div>
                </Form>

                <ListGroup className="user_reviews">
                  {submittedReview && (
                    <div className="review_item">
                      <img src={avatar} alt="avatar" />
                      <div className="w-100">
                        <div className="d-flex align-items-center justify-content-between">
                          <div>
                            <h5>{submittedReview.username}</h5>
                            <p>
                              {new Date().toLocaleDateString("en-US", options)}
                            </p>
                          </div>
                          <span className="d-flex align-items-center">
                            <AiFillStar className="review_icon" />5
                          </span>
                        </div>
                        <h6>{submittedReview.reviewText}</h6>
                      </div>
                    </div>
                  )}

                  {reviews.map((review, index) => (
                    <div className="review_item" key={index}>
                      <img src={avatar} alt="avatar" />
                      <div className="w-100">
                        <div className="d-flex align-items-center justify-content-between">
                          <div>
                            <h5>{review.username}</h5>
                            <p>
                              {new Date("08-28-2023").toLocaleDateString(
                                "en-US",
                                options
                              )}
                            </p>
                          </div>
                          <span className="d-flex align-items-center">
                            <AiFillStar className="review_icon" />5
                          </span>
                        </div>
                        <h6>Amazing Tour</h6>
                      </div>
                    </div>
                  ))}
                </ListGroup>
              </div>
            </div>
          </Col>

          <Col lg="4">
            <Booking tour={tour} avgRating={avgRating} />
          </Col>
        </Row>
      </Container>
    </section>
  );
};

export default TourDetails;

2cmtqfgy

2cmtqfgy1#

编辑

看起来你在前端的Authorization头上发送访问令牌,但试图从verifyToken中间件函数中的cookie中读取。请记住,在您的axios请求配置对象中设置的withCredentials属性不会自动发送存储在localStorage对象中的数据,它只会发送为您当前浏览会话存储的相关cookie。只要在请求的Authorization头中发送了一个有效的会话jwt,下面的代码就可以帮助您通过验证。

const verifyToken = (req, res, next) => {
  const authHeader = req.get("Authorization")?.split(" ");

  if (!authHeader || authHeader[0] !== "Bearer" || !authHeader[1]) {
    return res.status(401).json({
      success: false,
      message: "You are not authorized",
    });
  }

  const token = authHeader[1];

  try {
    const user = jwt.verify(token, process.env.JWT_SECRET);
    req.user = user;        
    next();
  } catch (error) {
    console.error("Token Verification Error:", error);

    if(["JsonWebTokenError", "TokenExpiredError"].includes(error.name)) {
      return res.status(401).json({
        success: false,
        message: "Token is Invalid",
      });
    }

    res.status(500).json({
      success: false,
      message: "Internal Server Error",
    });
  }
};

字符串
原文回答:
我没有看到任何对verifyToken函数的调用,该函数应该设置req.user对象,所以看起来你需要替换:

router.post("/:tourId", verifyUser, createReview);


与:

router.post("/:tourId", verifyToken, verifyUser, createReview);


这将首先调用verifyToken,如果验证成功,它将设置req.user对象,然后调用下一个中间件函数,在本例中是verifyUser

相关问题