Axios 发布请求适用于 Postman,但不适用于浏览器(它返回“错误 401-未授权”)

Posted

技术标签:

【中文标题】Axios 发布请求适用于 Postman,但不适用于浏览器(它返回“错误 401-未授权”)【英文标题】:Axios post request works on Postman but not on browser (it returns "Error 401- Not Authorized") 【发布时间】:2021-10-15 08:34:03 【问题描述】:

我在使用 axios 发布请求时遇到问题。在浏览器中,如果我在控制台中选中“网络”,则会显示错误“401 - 未授权”。但是用 Postman 进行测试一切正常。 我已经尝试了几个在类似问题中公开的解决方案,但都没有成功。

这是我设置快递服务器的代码:

import express from "express";
import mongoose from "mongoose";
import cors from "cors";
import dotenv from "dotenv";
import authRouter from "./routes/auth.js";
import categoryRouter from "./routes/category.js";

//DOTENV CONFIG
dotenv.config();
const MONGODB_CONNECTION = process.env.MONGO_URI;

//INITIALIZE APP
const app = express();

//MIDDLEWARES
app.use(function (req, res, next) 
  res.header("Access-Control-Allow-Origin", "*");
  res.header(
    "Access-Control-Allow-Headers",
    "Origin, X-Requested-With, Content-Type, Accept"
  );
  next();
);

app.use(express.json());

app.use(cors());

//ROUTES
app.use("/api/auth", authRouter);
app.use("/api/categories", categoryRouter);

//PORT
const PORT = process.env.PORT || 5020;

//CONNECT MONGODB
const connectDB = () => 
  mongoose
    .connect(MONGODB_CONNECTION, 
      useNewUrlParser: true,
      useFindAndModify: true,
      useUnifiedTopology: true,
      useCreateIndex: true,
    )
    .then(() => console.log("MONGODB is connected"))
    .catch((err) => console.log("MONGODB connection error:", err));
;

connectDB();

//INITIALIZE SERVER
app.listen(PORT, () =>
  console.log(`Connection is established and running on port $PORT`)
);

这是与路由器设置相关的代码:

import express from "express";
import  getCategories, createCategory  from "../controllers/category.js";
import  authMiddleware, authAdminMiddleware  from "../middlewares/auth.js";

const router = express.Router();

router.route("/get-categories").get(getCategories);
router
  .route("/create-category")
  .post(authMiddleware, authAdminMiddleware, createCategory);

export default router;

用于身份验证的中间件:

import jwt from "jsonwebtoken";
import dotenv from "dotenv";
import  User  from "../models/User.js";

dotenv.config();
const JWT_SECRET = process.env.JWT_SECRET;

export const authMiddleware = async (req, res, next) => 
  try 
    let token;
    if (
      req.headers.authorization &&
      req.headers.authorization.startsWith("Bearer")
    ) 
      token = req.headers.authorization.split(" ")[1];
    

    if (!token) 
      return res.status(401).json( errorMessage: "Invalid Authentication" );
    

    const decoded = jwt.verify(token, JWT_SECRET);
    const user = await User.findById(decoded.user._id);

    if (!user) 
      return res.status(404).json( errorMessage: "No user found" );
    

    req.user = user;

    next();
   catch (error) 
    return res.status(500).json( errorMessage: "Not authorized" );
  
;

export const authAdminMiddleware = async (req, res, next) => 
  try 
    const user = await User.findOne(
      _id: req.user.id,
    );
    if (req.user.role === 0) 
      return res
        .status(400)
        .json( errorMessage: "Not Authorized. Admin private route." );
    
    next();
   catch (error) 
    return res
      .status(401)
      .json( errorMessage: "Not Authorized. Admin private route." );
  
;

POST 请求控制器:

import  Category  from "../models/Category.js";
import slugify from "slugify";

export const createCategory = async (req, res) => 
  const  name, parentId  = req.body;

  try 
    if (!name) 
      return res
        .status(400)
        .json( errorMessage: "Category name field is required." );
    

    const category = await Category.findOne( name );

    if (category) 
      return res.status(409).json( errorMessage: "Category already exist." );
    

    let id;

    if (parentId) 
      const findParentId = await Category.findOne( name: parentId );
      if (findParentId) 
        id = findParentId._id;
      
    

    const newCategory = new Category(
      name,
      slug: slugify(name),
      parentId: id,
    );
    newCategory.save((err, saved) => 
      if (err) 
        return res.status(400).json( errorMessage: err.message );
      
      if (saved) 
        return res
          .status(201)
          .json( successMessage: "Category created successfully!" );
      
    );
   catch (error) 
    res.status(500).json( errorMessage: error.message );
  
;

前端的 API 调用:

import axios from "axios";
import  getCookies  from "../helpers/storage&cookies/storage&cookies";

console.log(getCookies("token"));

const config = 
  headers: 
    "Content-Type": "application/json",
    Authorization: "Bearer " + getCookies("token"),
  ,
;

export const postCategory = async (data) => 
  const response = await axios.post(
    "http://localhost:5020/api/categories/create-category",
    data,
    config
  );
  return response;
;

具体的客户端发布请求:

import React,  useState  from "react";
import CreateCategoryModalUI from "./CreateCategoryModalUI";
import  postCategory  from "../../../api/category";

const CreateCategoryModal = ( categories ) => 
  const [createCategoryData, setCreateCategoryData] = useState(
    name: "",
    parentId: "",
    errorMessage: "",
    successMessage: "",
    loading: false,
  );

  const  name, parentId, successMessage, errorMessage, loading  =
    createCategoryData;

  const handleChange = (e) => 
    if (e.target.type == "text") 
      setCreateCategoryData(
        ...createCategoryData,
        name: e.target.value,
        successMessage: "",
        errorMessage: "",
      );
     else 
      setCreateCategoryData(
        ...createCategoryData,
        parentId: e.target.value,
        errorMessage: "",
        successMessage: "",
      );
    
  ;

  const handleSubmit = (e) => 
    e.preventDefault();

    if (!name) 
      setCreateCategoryData(
        ...createCategoryData,
        errorMessage: "Category name field is required.",
      );
     else 
      const data =  name, parentId ;
      console.log(data);
      setCreateCategoryData(
        ...createCategoryData,
        loading: true,
      );

      postCategory(data)
        .then((response) => 
          setCreateCategoryData(
            name: "",
            parentId: "",
            errorMessage: "",
            successMessage: response.data.successMessage,
            loading: false,
          );
        )
        .catch((error) => 
          if (error.response.status === 409) 
            setCreateCategoryData(
              ...createCategoryData,
              errorMessage: "Category already exists",
              loading: false,
            );
           else 
            setCreateCategoryData(
              ...createCategoryData,
              errorMessage: error.toString(),
              loading: false,
            );
          
        );
    
  ;

  return (
    <CreateCategoryModalUI
      createCategoryData=createCategoryData
      categories=categories
      handleChange=handleChange
      handleSubmit=handleSubmit
    />
  );
;

export default CreateCategoryModal;

我该如何解决这个问题? 非常感谢!

【问题讨论】:

使用您的浏览器开发工具 Network 面板检查请求。 Authorization 标头中是否有正确的不记名令牌?此外,如果您使用 cors 包,则无需手动设置 CORS 响应标头 是的,我已经检查过了:授权标头是正确的。此外,我还手动设置了 CORS 响应标头,因为对于另一个 POST 请求,我遇到了类似的问题,通过添加这两行代码莫名其妙地解决了。 【参考方案1】:

这是一个评论,但我没有足够的声誉来这样做,这就是我发布答案的原因。

您可以尝试在创建 Axios 实例时将 withCredentials 设置为 true,withCredentials 根据 Axios 存储库执行以下操作:

withCredentials表示是否跨站访问控制请求 应该使用凭据制作

官方文档没有添加任何进一步的解释...

axios.create( withCredentials: true, )

【讨论】:

OP 正在使用不需要withCredentials的不记名令牌 谢谢,我也尝试使用 withCredentials: true,但尽管将“Access-Control-Allow-Origin”设置为我的客户端本地主机地址,但我收到此错误:“访问 XMLHttpRequest at . .. from origin ...已被 CORS 策略阻止:对预检请求的响应未通过访问控制检查:响应中的“Access-Control-Allow-Origin”标头的值不能是通配符“* ' 当请求的凭证模式为 'include' 时。由 XMLHttpRequest 发起的请求的凭证模式由 withCredentials 属性控制。"【参考方案2】:

谢谢大家,我解决了!这个问题,正如我经常发生的那样,是一个疏忽。具体来说,在身份验证中间件中。 在这些代码行中:

const user = await User.findById (decoded.user._id)

我在“解码”后添加了一个多余的“用户”,但对象键只是“_id”。

【讨论】:

以上是关于Axios 发布请求适用于 Postman,但不适用于浏览器(它返回“错误 401-未授权”)的主要内容,如果未能解决你的问题,请参考以下文章

POST 请求适用于 Postman,但不适用于 axios.post()

请求适用于Postman,但不适用于axios.post

使用 JWT 访问 Rest API 适用于 Postman 但不适用于 Axios

GET 请求适用于 Postman,但为啥它不适用于 ReactJS fetch?

POST 请求适用于 Postman,但不适用于 Guzzle

React 本机 Axios 发布请求适用于 iOS 模拟器,但不适用于物理手机