簡體   English   中英

Express CORS 有時有效,有時無效

[英]Express CORS sometimes works and sometimes doesn't

我有一個 Express API 托管在位於main-domain.tld/api/的 ngnix 上,還有一個位於sub.main-domain.tld的管理面板,它向我的 API 發送請求。

當我嘗試從管理面板向 API 發送請求時,我收到 CORS 錯誤,70% 的時間與我請求的路由和使用的方法(POST、GET 等)無關。

我無法理解兩件事:

  • 首先是我收到 CORS 錯誤的原因,因為我啟用了 API 源代碼中的所有來源。
  • 第二個是為什么我收到 CORS 錯誤只有 70% 的時間我的管理面板發出請求,如果我的 API 設置錯誤,這不應該發生,對吧?

我整天都在尋找解決方案並嘗試以各種可能的方式創建corsOptions但我仍然遇到同樣的問題,不管我做什么。

CORS 錯誤:

CORS 錯誤(Chrome 控制台)

API源代碼:

import cors from 'cors';
import express from 'express';
import jwt from 'jsonwebtoken';

import { generateToken, getCleanUser } from './utils';

import { Admins, Utenti, Prodotti, Acquisti } from './models';

require('dotenv').config();

const app = express();
const port = process.env.PORT;

const mongoose = require('mongoose');
mongoose.connect('mongodb://domain', {
  useNewUrlParser: true,
  useUnifiedTopology: true,
  useFindAndModify: false,
}).then(db => console.log('Il DB è connesso!'))
  .catch(err => console.log(err));

// CORS
app.use(cors());
// parse application/json
app.use(express.json());
// parse application/x-www-form-urlencoded
app.use(express.urlencoded({ extended: true }));

// Middleware that checks if JWT token exists and verifies it if it does exist.
// In all future routes, this helps to know if the request is authenticated or not.
app.use((req, res, next) => {
  // check header or url parameters or post parameters for token
  let token = req.headers['authorization'];
  if (!token) return next(); //if no token, continue

  token = token.replace('Bearer ', '');
  jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
    if (err) {
      return res.status(401).json({
        error: true,
        message: "Invalid user."
      });
    } else {
      req.user = user; //set the user to req so other routes can use it
      next();
    }
  });
});

// request handlers
app.get('/api', (req, res) => {
  if (!req.user) return res.status(401).json({ success: false, message: 'Invalid user to access it.' });
  res.send('Welcome! - ' + req.user.username);
});


//*================================
//* ADMINS SIGNIN
//*================================
app.post('/api/admins/signin', async (req, res) => {
  try {
    const user = req.body.username;
    const pwd = req.body.password;

    // return 400 status if username/password is not exist
    if (!user || !pwd) {
      return res.status(401).json({
        error: true,
        message: "Username or Password required!"
      });
    }

    await Admins.findOne({ 'username': user, 'password': pwd }, (err, data) => {

      if (err) {
        console.error('DB ERROR  =>  ', err);
      }

      // return 401 status if the credential is not match.
      if (!data) {
        return res.status(401).json({
          error: true,
          message: "Username or Password is Wrong!"
        });
      }

      // generate token
      const token = generateToken(data);
      // get basic user details
      const userObj = getCleanUser(data);
      // return the token along with user details
      return res.json({ user: userObj, token });

    });
  } catch (error) {
    console.log(`ERRORE NELLA POST REQUEST DI ADMIN SIGNIN >> ${error}`);
    return res.status(400);
  }
});

//*================================
//* USERS SIGNIN
//*================================
app.post('/api/users/signin', async (req, res) => {
  try {
    const user = req.body.username;
    const pwd = req.body.password;

    // return 400 status if username/password is not exist
    if (!user || !pwd) {
      return res.status(401).json({
        error: true,
        message: "Username or Password required!"
      });
    }

    await Utenti.findOne({ 'username': user, 'password': pwd }, (err, data) => {

      if (err) {
        console.error('DB ERROR  =>  ', err);
      }

      // return 401 status if the credential is not match.
      if (!data) {
        return res.status(401).json({
          error: true,
          message: "Username or Password is Wrong!"
        });
      }

      // generate token
      const token = generateToken(data);
      // get basic user details
      const userObj = getCleanUser(data);
      // return the token along with user details
      return res.json({ user: userObj, token });

    });
  } catch (error) {
    console.log(`ERRORE NELLA POST REQUEST DI USERS SIGNIN >> ${error}`);
    return res.status(400);
  }
});

//*================================
//* USERS SIGNUP
//*================================
app.post('/api/users/signup', async (req, res) => {
  try {
    // return 400 status if username/password is not exist
    if (!req.body.username || !req.body.password || !req.body.email) {
      return res.status(400).json({
        error: true,
        message: "Every field in the form is required!"
      });
    }

    await Utenti.findOne({ 'username': req.body.username }, async (err, data) => {
      if (err) {
        console.error('DB ERROR  =>  ', err);
      }

      if (data) {
        return res.status(400).json({
          error: true,
          message: "Username already taken!"
        });
      }

      await Utenti.find().sort({ _id: -1 }).exec(async (err, lastUserSignedUp) => {
        if (err) return res.status(401).json({
          error: true,
          message: 'DB Problem... '
        });

        let newUser;

        if (lastUserSignedUp[0]) {
          newUser = new Utenti({
            id: (lastUserSignedUp[0].id + 1),
            username: req.body.username,
            password: req.body.password,
            email: req.body.email
          });
        } else {
          newUser = new Utenti({
            id: 0,
            username: req.body.username,
            password: req.body.password,
            email: req.body.email
          });
        }

        await newUser.save();
        return res.json(newUser);
      });
    });
  } catch (error) {
    console.log(`ERRORE NELLA POST REQUEST DI USERS SIGNUP >> ${error}`);
    return res.status(401)
  }
});

//*================================
//* ADMINS VERIFY THE TOKEN
//*================================
app.get('/api/verifyAdminToken', (req, res) => {
  // check header or url parameters or post parameters for token
  const token = req.body.token || req.query.token;
  if (!token) {
    return res.status(400).json({
      error: true,
      message: "Token is required."
    });
  }

  // check token that was passed by decoding token using secret
  jwt.verify(token, process.env.JWT_SECRET, async (err, user) => {
    try {
      if (err) return res.status(401).json({
        error: true,
        message: "Invalid token."
      });

      await Admins.findOne({ 'username': user.username, 'password': user.password }, (err, data) => {

        if (err) {
          console.error('DB ERROR  =>  ', err);
        }

        // return 401 status if the userId does not match.
        if (user._id !== data._id.toString()) {
          return res.status(401).json({
            error: true,
            message: "Invalid user."
          });
        }
        // get basic user details
        const userObj = getCleanUser(data);
        return res.json({ user: userObj, token });

      });
    } catch (error) {
      console.log(`ERRORE NELLA GET REQUEST DI VERIFY-TOKEN >> ${error}`);
    }
  });
});

//*================================
//* USERS VERIFY THE TOKEN
//*================================
app.get('/api/verifyToken', (req, res) => {
  // check header or url parameters or post parameters for token
  const token = req.body.token || req.query.token;
  if (!token) {
    return res.status(400).json({
      error: true,
      message: "Token is required."
    });
  }

  // check token that was passed by decoding token using secret
  jwt.verify(token, process.env.JWT_SECRET, async (err, user) => {
    try {
      if (err) return res.status(401).json({
        error: true,
        message: "Invalid token."
      });

      await Utenti.findOne({ 'username': user.username, 'password': user.password }, (err, data) => {

        if (err) {
          console.error('DB ERROR  =>  ', err);
        }

        // return 401 status if the userId does not match.
        if (user._id !== data._id.toString()) {
          return res.status(401).json({
            error: true,
            message: "Invalid user."
          });
        }
        // get basic user details
        const userObj = getCleanUser(data);
        return res.json({ user: userObj, token });

      });
    } catch (error) {
      console.log(`ERRORE NELLA GET REQUEST DI VERIFY-TOKEN >> ${error}`);
    }
  });
});

//*================================
//* PRODOTTI
//*================================
app.route('/api/prodotti')
  .get(async (req, res) => {
    try {
      if (req.query.categoria !== undefined) {
        if (req.query.categoria === 'all') {
          await Prodotti.find().exec((err, data) => {
            if (err) return res.status(401).json({
              error: true,
              message: 'DB Problem... '
            });

            return res.json(data);
          })
        } else {
          await Prodotti.find({ 'categoria': req.query.categoria }, (err, data) => {
            if (err) return res.status(401).json({
              error: true,
              message: 'DB Problem... '
            });

            return res.json(data);
          })
        }
      } else {
        if (!req.query.id) {
          await Prodotti.findOne({ 'nome': req.query.nome }, (err, data) => {
            if (err) return res.status(401).json({
              error: true,
              message: 'DB Problem... '
            });

            if (req.query.desc === 'true' && data) {
              return res.json(data.desc);
            } else {
              return res.json(data);
            }
          });
        } else {
          await Prodotti.findOne({ 'id': req.query.id }, (err, data) => {
            if (err) return res.status(401).json({
              error: true,
              message: 'DB Problem... '
            });

            return res.json(data);
          });
        }
      }
    } catch (error) {
      console.log(`ERRORE NELLA GET REQUEST DEI PRODOTTI >> ${error}`);
    }
  })
  .post(async (req, res) => {
    if (req.user) {
      try {
        await Admins.findOne({ 'username': req.user.username, 'password': req.user.password }, async (err, data) => {

          if (err) {
            console.error('DB ERROR  =>  ', err);
          }

          // return 401 status if the credential is not match.
          if (!data) {
            return res.status(401).json({
              error: true,
              message: "Access Denied"
            });
          }

          await Prodotti.find().sort({ _id: -1 }).exec(async (err, lastProdottoAggiunto) => {
            if (err) return res.status(401).json({
              error: true,
              message: 'DB Problem... '
            });

            let nuovoProdotto;

            if (lastProdottoAggiunto[0]) {
              nuovoProdotto = new Prodotti({
                id: (lastProdottoAggiunto[0].id + 1),
                nome: req.body.nome,
                categoria: req.body.categoria,
                prezzo: req.body.prezzo,
                id_api: req.body.id_api,
                desc: req.body.desc
              });
            } else {
              nuovoProdotto = new Prodotti({
                id: 0,
                nome: req.body.nome,
                categoria: req.body.categoria,
                prezzo: req.body.prezzo,
                id_api: req.body.id_api,
                desc: req.body.desc
              });
            }

            await nuovoProdotto.save();
            return res.json(nuovoProdotto);
          });
        });
      } catch (error) {
        console.log(`ERRORE NELLA POST REQUEST DEI PRODOTTI >> ${error}`);
      }
      return res.status(403).json({
        error: true,
        message: 'Access Denied'
      });
    }
  })
  .delete(async (req, res) => {
    try {
      await Prodotti.findOneAndDelete({ 'id': req.body.id }, (err, removed) => {
        if (err) return res.status(401).json({
          error: true,
          message: 'DB Problem... '
        });
        return res.json({ success: 'true' });
      });
    } catch (error) {
      console.log(`ERRORE NELLA POST REQUEST DEI PRODOTTI >> ${error}`);
    }
  });

//*================================
//* ACQUISTI
//*================================
app.route('/api/acquisti')
  .get(async (req, res) => {
    try {
      if (!req.query.id) {
        await Acquisti.find({ 'id': req.query.id }, (err, data) => {
          if (err) return res.status(401).json({
            error: true,
            message: 'DB Problem... '
          });

          return res.json(data);
        });
      }
    } catch (error) {
      console.log(`ERRORE NELLA GET REQUEST DEGLI ACQUISTI >> ${error}`);
    }
  })
  .post(async (req, res) => {
    if (req.user) {
      try {
        const nuovoAcquisto = new Acquisti({
          id: orderId,
          id: req.body.id,
          categoria: req.body.categoria,
          nome: req.body.nome,
          link: req.body.link,
          qty: req.body.qty,
          spesa: req.body.spesa
        });
        await nuovoAcquisto.save();
        return res.json(nuovoAcquisto);

      } catch (error) {
        return res.status(401).json({
          error: true,
          message: `ERRORE NELLA POST REQUEST DEGLI ACQUISTI >> ${error}`
        });
      }
    } else {
      return res.status(403).json({
        error: true,
        message: 'Access Denied'
      });
    }
  });

//*================================
//* UTENTI
//*================================
app.route('/api/utenti')
  .get(async (req, res) => {
    if (req.user) {
      try {
        if (req.query.id) {
          await Utenti.findOne({ 'id': req.query.id }, (err, data) => {
            if (err) return res.status(401).json({
              error: true,
              message: 'DB Problem... '
            });

            return res.json(data);
          });
        } else {
          await Utenti.find().exec((err, data) => {
            if (err) return res.status(401).json({
              error: true,
              message: 'DB Problem... '
            });

            return res.json(data);
          })
        }
      } catch (error) {
        console.log(`ERRORE NELLA GET REQUEST DEGLI UTENTI >> ${error}`);
      }
    } else {
      return res.status(403).send(`
      <div style="text-align: center; font-family: 'Trebuchet MS', sans-serif;">
      </div>
      `);
    }
  })
  .delete(async (req, res) => {
    try {
      await Utenti.findOneAndDelete({ 'id': req.body.id }, (err, removed) => {
        if (err) return res.status(401).json({
          error: true,
          message: 'DB Problem... '
        });
        return res.json({ success: 'true' });
      });
    } catch (error) {
      console.log(`ERRORE NELLA POST REQUEST DEI PRODOTTI >> ${error}`);
    }
  });

app.listen(port, () => {
  console.log('Porta API: ' + port);
});

您正在發出觸發飛行前的跨源請求(這可以從瀏覽器中記錄的內容中看出)。 這是 CORs 的附加級別,可能是由任何數量的事情引起的,例如自定義標頭、超出允許的小集的內容類型等...顯然只有您的一些請求觸發飛行前,這就是為什么一些他們工作正常,而其他人則不然。 您可以閱讀有關簡單發送請求的信息,並查看是什么導致瀏覽器決定它必須預發送請求。

如果您查看 Chrome 調試器的網絡選項卡,您將能夠看到瀏覽器發出 OPTIONS 請求,並且可能返回 404 或沒有返回正確的標頭,這就是飛行前請求失敗和瀏覽器的原因然后拒絕 CORs 請求。

要允許預先發送的 CORs 請求,您的服務器必須以 2xx 狀態和正確的 CORS 標頭響應 OPTIONS 請求。

您將需要一個app.options(...)請求處理程序,它通過返回正確的 CORS 標頭並以 2xx 狀態(通常為 204)響應來允許所有請求通過或僅允許某些請求通過。

由於您使用cors模塊來幫助您,您可以在此處閱讀有關該模塊的飛行前請求。

問題是我的nginx配置和Cloudflare阻止了 CORs 標頭。

這是新的工作 nginx 配置:

server {
        listen 80;
        listen [::]:80;
        
        root /var/www/main-domain.tld;
        index index.html;

        server_name main-domain.tld www.main-domain.tld;
        
        error_page 404 /404.html;
        location = /404.html {
                root /var/www/main-domain.tld;
                internal;
        }

        error_page 400 401 403 503 /custom_50x.html;
        location = /custom_50x.html {
                root /usr/share/nginx/html;
                internal;
        }
        location / {
          if ($request_method = 'OPTIONS') {
            add_header 'Access-Control-Allow-Origin' '*';
            add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, DELETE, HEAD';
            #
            # Custom headers and headers various browsers *should* be OK with but aren't
            #
            add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';
            #
            # Tell client that this pre-flight info is valid for 20 days
            #
            add_header 'Access-Control-Max-Age' 1728000;
            add_header 'Content-Type' 'text/plain; charset=utf-8';
            add_header 'Content-Length' 0;
            return 204;
          }
          if ($request_method = 'POST') {
            add_header 'Access-Control-Allow-Origin' '*';
            add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, DELETE, HEAD';
            add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';
            add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
          }
          if ($request_method = 'GET') {
            add_header 'Access-Control-Allow-Origin' '*';
            add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, DELETE, HEAD';
            add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';
            add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
          }
        
          try_files $uri $uri/ =404;
          if ($request_filename ~* ^.+.html$) {
            break;
          }
          # add .html to URI and serve file, directory, or symlink if it exists
          if (-e $request_filename.html) {
            rewrite ^/(.*)$ /$1.html last;
            break;
          }
          if (!-e $request_filename){
            rewrite ^(.*)$ /index.html break;
          }
        }
        location /api {
          proxy_redirect off;
          proxy_set_header host $host;
          proxy_set_header X-real-ip $remote_addr;
          proxy_set_header X-forward-for $proxy_add_x_forwarded_for;
          proxy_pass http://localhost:api-port;
        }
        location ~ /\.ht {
                deny all;
        }

}

這是修復Cloudflare的方法。 遵循“添加或更改 CORS 標頭”的說明並至少發送一次正確的 CORs 標頭:

https://support.cloudflare.com/hc/en-us/articles/200308847-Using-cross-origin-resource-sharing-CORS-with-Cloudflare

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM