使用 MEAN 堆栈应用程序(托管在 Heroku 上)消除 CORS 策略错误

Posted

技术标签:

【中文标题】使用 MEAN 堆栈应用程序(托管在 Heroku 上)消除 CORS 策略错误【英文标题】:Getting rid of CORS policy error with MEAN stack app (hosted on Heroku) 【发布时间】:2021-02-13 04:04:05 【问题描述】:

我已经在 MEAN stack 应用程序上工作了一段时间,在过去的一个月里,我一直在努力解决这个 CORS 政策错误:

“从源访问 的 XMLHTTPRequest 已被 CORS 策略阻止:对预检请求的响应未通过访问控制检查:'Access-Control-Allow-Origin' 已一个值:不等于提供的原点。”

我已经一些成功地让它在一些设备上工作。在我的 server.js 文件中的中间件函数中的请求/响应标头设置和与后端通信的服务中的 POST 请求搞乱之后,我已经让它在我的桌面(Windows 10)上工作,在我的 Ubuntu 操作系统上我的台式机、笔记本电脑 (Windows 10)、我朋友的 macbook 等。

但是,我的一些朋友设备上仍然存在 CORS 政策问题。哪些设备工作和哪些设备不工作似乎没有任何共同点(不同的操作系统工作和不工作,当它不工作时,它适用于所有浏览器)。

还可能值得注意的是,在出现 CORS 错误的设备上,我的应用页面右上角会显示“不安全”,而在它工作的设备上它被标记为安全。

我做过的事情:

在我的 server.js 文件中导入了 cors 模块 尝试使用 csp(内容安全策略)模块 修改了对我的服务器的请求的标头 修改了从服务器发送回客户端的响应的标头 大量搜索和摆弄 CORS 策略设置

如果相关的话,我还使用护照和 bcrypt 进行用户身份验证。

这里是相关的 server.js 代码:

var express = require('express');
var cors = require('cors');
//var csp = require('content-security-policy');
var app = express();
var dotenv = require('dotenv');
dotenv.config();

var url = require('url');

var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var session = require('express-session'); // should give us persistent sessions...
var passport = require('passport');
var ejs = require('ejs');
var bcrypt = require('bcrypt');
var flash = require('express-flash');
var mongoose = require('mongoose');
var path = require('path');
//const env = require('./src/environments/environment');
//const methodOverride = require('method-override')
//var initializePassport = require('passport-config');
var initializePassport = require('./passport-config');
// this completes passport authentication strategy
// passes passport, a function for finding a user by their username,
// and a function for finding a user by id to the initialize() function inside passport-config
// the authenticateUser function then uses these methods to get what it needs
var connurl = '';
if(process.env.NODE_ENV == 'development')
  connurl = 'http://localhost:4200';

else
  connurl = 'https://to-do-bentancock.herokuapp.com';


initializePassport(
  passport,
  // both of these things are functions, passed into passport config
  // I think I pass mongoose middleware stuff here to return the right things
  username => Users.find(username:  $eq: username ),
  id => Users.find(id:  $eq: id )
)

var MongoStore = require('connect-mongo')(session);
var router = express.Router();

app.use(bodyParser.json());
app.use(bodyParser.urlencoded(extended: false));
app.use(cookieParser('process.env.SECRET'));

var cspPolicy = 
  'default-src': 'self, https://to-do-bentancock.herokuapp.com/*',
  'img-src': '*',


/*app.use(csp(
  policies: 
    'default-src': [csp.NONE],
    'img-src': [csp.SELF],
  
));

const globalCSP = csp.getCSP(cspPolicy);
app.use(globalCSP)

*/

app.use(session(
  secret: 'process.env.SECRET',
  resave: true, // should we reset our session variables if nothing has changed?
  // NOTE: this MUST be set to true otherwise the user authentication / session data won't be saved between middleware methods
  // e.g. if you log in (via /tasks post method), it will print the session data at the end, but if you then do '/create' method right after the req object will be null (because it wasn't saved)
  saveUninitialized: true, // do you want to save an empty value in the session if there is no value?
  cookie: 
    // might want to look into changing this in the future, as cookie stores user stuff
    // for now I have it off until I'm certain I've got all this passport js, cookie and session stuff down pat
    secure: false,
    maxAge: 600000
  ,
  store: new MongoStore(
    mongooseConnection: mongoose.connection,
    ttl: 60 * 60, // keeps the session open for 1 hour
    collection: 'sessions'
  )
));

app.use(passport.initialize());
app.use(passport.session());

app.use(cors(credentials: true, origin: true));

// enables pre-flight requests across the board
app.options('*', cors()) // include before other routes

app.get('/with-cors', cors(), (req, res, next) => 
  console.log("testing cors:");
);

app.use('/', express.query());

app.get('/*', function(req,res) 
  res.header("Access-Control-Allow-Origin", connurl);
  res.header('Access-Control-Allow-Credentials', true);
  //res.header('Content-Type', 'application/json');
  console.log("here's what app.get is receiving: " + req.url);
  console.log("sending file!");
  res.sendFile(path.join(__dirname + '/dist/to-do-heroku/index.html'));
);

app.post('/loginCheck', function(req, res)
  res.header("Access-Control-Allow-Origin", connurl);
  res.header('Access-Control-Allow-Credentials', true);
  res.header('Content-Type', 'application/json');

  console.log("res header: %j", res.getHeaders());
  console.log("\nlogin check");
  if(req.isAuthenticated())
    console.log("authentication returns true!");
    //console.log("printing req passport data: ");
    //console.log(req.session);
    //console.log(req.user);
    //res.headersSent();
    res.send(authenticated: true);
  
  else
    console.log("user is not authenticated");
    res.send(authenticated: false);
  
);

app.post('/login', function(req, res, next) 
  res.header("Access-Control-Allow-Origin", connurl);
  res.header('Access-Control-Allow-Credentials', true);
  res.header('Content-Type', 'application/json');
  console.log("res header: %j", res.getHeaders());
  passport.authenticate('local', function(err, user, info) 
    console.log("printing error: " + err);
    console.log("passport info: " + JSON.stringify(info)); // undefined
    if(err)
      console.log("error authenticating!");
      res.send(status: 'error logging in user');
      return;
    

    if(!err)
      req.logIn(user, function(err)
        if(err)
          console.log("error logging in");
          //return
        
        res.send(status: 'success');
      );
    
  )(req, res, next);
);

app.post('/logout', checkAuthenticated, async function(req, res)
  res.header("Access-Control-Allow-Origin", connurl);
  res.header('Access-Control-Allow-Credentials', true);
  res.header('Content-Type', 'application/json');
  console.log("\nlogging out user");
  await req.logout(); // logOut or logout??
  res.send(status: 'redirect', url: '/login');
);


app.post('/getTasks', checkAuthenticated, function(req, res)
    res.header("Access-Control-Allow-Origin", connurl);
    res.header('Access-Control-Allow-Credentials', true);
    res.header('Content-Type', 'application/json');
    console.log("\n Successful authentication, request: " + JSON.stringify(req.body));

    Users.find(id:  $eq: req.session.passport.user, function(err, doc)
      if(!doc.length || doc == null) // if the user is not found
        console.log("ERROR: USER NOT FOUND, LOGGING OUT");
        req.logOut();
        res.send(error:'not found'); // send some kind of message back to client-side
      
      else
        res.send(tasks: doc[0].tasks, idCount: doc[0].idCount);
      

    );
);



var uri = '';
if(process.env.NODE_ENV == 'development')
  uri = 'mongodb://localhost/todoDB'

else
  uri = "mongodb+srv://todoApp:7211@cluster0.huawl.mongodb.net/toDoDB?retryWrites=true&w=majority";



//const uri = "mongodb+srv://todoApp:7211@cluster0.huawl.mongodb.net/toDoDB?retryWrites=true&w=majority";
mongoose.connect(uri);
const connection = mongoose.connection;
connection.once('open', function()
  mongoose.connection.db.collection('usersCollection').countDocuments(function(err, docs)
    console.log("there are " + docs + " docs in the collection\n");
  );
  console.log("MongoDB database connection established successfully\n");
);

function checkAuthenticated(req, res, next) 
  if (req.isAuthenticated()) 
    console.log("user is authenticated!");
    return next();
  
  console.log("WARNING: USER NOT AUTHENTICATED");
  res.send(authenticated: false);


function checkNotAuthenticated(req, res, next) 
  if (req.isAuthenticated()) 
    console.log("\nuser IS authenticated, stopping this request...");
    // Send a message back to the client telling it to redirect instead
    //res.send(authenticated: true);
    return;
  
  //res.send(authenticated: false);
  console.log("user is NOT authenticated");
  next();




if(process.env.NODE_ENV == 'development')
  app.listen(4000, function(req, res)
    console.log("express server listening on port 4000");
  );

else
  app.listen(process.env.PORT || 8080, function(req, res)
    console.log("express server listening on port 8080");
  );




这是与 server.js 通信的服务的代码:

auth.service:

import  Injectable  from '@angular/core';
import  HttpClient, HttpHeaders  from '@angular/common/http';
import  Router, ActivatedRoute, ParamMap  from '@angular/router';
import  Subject  from 'rxjs';
import  environment  from './../environments/environment';


@Injectable(
  providedIn: 'root'
)
export class AuthService 

  url = environment.apiUrl;


  username;
  password;
  loggedIn = false;



  constructor(private http: HttpClient, private router: Router)  

  login(uname, pw)
    console.log("logging in user: " + uname + " " + pw + '\n');
    console.log("the url: " + this.url);
    this.username = uname;
    this.password = pw;
    return this.http.post(this.url + '/login', username: uname, password: pw, 
      headers: new HttpHeaders(
        'Access-Control-Allow-Credentials' : 'true',
        'Access-Control-Allow-Origin': "*" // this might just need to be the api url
      ),
      withCredentials: true
    );
  

  setLogin(bool)
    this.loggedIn = bool;
  

  logout()
    console.log("test auth logout");
    return this.http.post(this.url + '/logout', body: 'logout',  
      headers: new HttpHeaders(
        'Content-Type' : 'application/json',
        'Access-Control-Allow-Credentials' : 'true',
        'Access-Control-Allow-Origin': "*"
      ),
      withCredentials: true
    );
  

  loginCheck()
    console.log("test auth service login check");
    return this.http.post(this.url + '/loginCheck', 
      headers: new HttpHeaders(
        'Content-Type' : 'application/json',
        'Access-Control-Allow-Credentials' : 'true',
        'Access-Control-Allow-Origin': "*"
      ),
      withCredentials: true
    );
  


  registerUser(uname, pw)
    console.log("registering user: " + uname + " " + pw + '\n');

    // the object sent needs to be a user object, which contains task objects
    // new user won't have any tasks tho (obvi)
    // also need to check for duplicate usernames
    return this.http.post(this.url + '/register',
    
      username: uname,
      password: pw,
      idCount: 0,
      id: Date.now().toString(),
      tasks: []
    ,
    
      headers: new HttpHeaders(
        'Content-Type' : 'application/json',
        'Access-Control-Allow-Credentials' : 'true',
        'Access-Control-Allow-Origin': "*"
      ),
      withCredentials: true
    );
  


  // takes user tasks data as input, passes it to tasks component
  renderTasks()
    console.log("render tasks \n");
    this.router.navigate(['/tasks']);
  

task.service:

import  Injectable  from '@angular/core';
import  HttpClient, HttpHeaders  from '@angular/common/http';
import  Observable  from 'rxjs';
import  catchError, tap, share, map  from 'rxjs/operators';
import  environment  from './../environments/environment';

@Injectable(
  providedIn: 'root'
)
export class TasksService 
  //url = 'http://localhost:4000' // the port the mongo database is listening on
  //url = 'https://to-do-bentancock.herokuapp.com';
  url = environment.apiUrl;
  constructor(private http: HttpClient)  


  getTasks(userName, pw)
    console.log("tasks service: get tasks \n");
    console.log("username and pw to post: " + userName + " " + pw);

    return this.http.post(this.url + '/getTasks', username: userName, password: pw,
    
      headers: new HttpHeaders(
        'Content-Type' : 'application/json',
        'Access-Control-Allow-Credentials' : 'true',
        'Access-Control-Allow-Origin': "*"
      ),
      withCredentials: true
    );
  

  deleteTask(uname, pw, id: number )
    var delUrl = this.url + "/deleteTask/" + id;

    return this.http.post(delUrl, username: uname, password: pw,
      
        headers: new HttpHeaders(
          'Content-Type' : 'application/json',
          'Access-Control-Allow-Credentials' : 'true',
          'Access-Control-Allow-Origin': "*"
        ),
        withCredentials: true
      
    );

  

  /*
  a task object has a:
  - name
  - date
  - description
  - priority
  - id
  */
  createTask(uname, pw, task)
    console.log("test create task (service)");

    // send user data, and a task object
    return this.http.post(this.url + '/create',
    
      task:
      
        name: task.name,
        date: task.date,
        description: task.description,
        priority: task.priority,
        id: task.id,
        state: task.state
      ,
      user:
      
        username: uname,
        password: pw
      
    ,
    
      headers: new HttpHeaders(
        'Content-Type' : 'application/json',
        'Access-Control-Allow-Credentials' : 'true',
        'Access-Control-Allow-Origin': "*"
      ),
      withCredentials: true
    
  );
  

我一直在谷歌上搜索并排除故障,我已经筋疲力尽了。有人有建议吗?我错过了什么?

【问题讨论】:

【参考方案1】:

我只是在你的 get 中使用 app.use(cors()) 而没有 app.options('*', cors())cors()

【讨论】:

以上是关于使用 MEAN 堆栈应用程序(托管在 Heroku 上)消除 CORS 策略错误的主要内容,如果未能解决你的问题,请参考以下文章

Heroku + MEAN 堆栈错误:参数“url”必须是字符串,而不是未定义

Heroku中来自外部文件的角度路由

如何在 Heroku Cedar 堆栈上使用 .htaccess 重定向到 HTTPS

如何将 MEAN 堆栈部署到我的托管服务器?

在 Heroku 问题上部署平均堆栈应用程序

部署 MEAN Stack 应用程序,托管问题