HTTP headers 未在来自 AngularJS 应用程序的 CORS GET 中发送

HTTP headers are not being sent in CORS GET from AngularJS application

我的问题是我的 AngularJS HTTP GET 请求没有发送 HTTP headers。但是,对于 HTTP POST,我确实看到设置了 headers。这些 HTTP 请求通过 CORS。

虽然有很多关于这个问题的 SO 帖子,但我已经尝试过了,其中 none 个有效。其中一个解决方案表明,如果数据字段为空,则不会发送 HTTP headers,并且我已经尝试了添加空数据值的建议(这对于 HTTP GET 请求没有意义,由方式),但 HTTP headers 仍然没有成功。

附带一提,我可能会捍卫这个 post/question 可能值得自己作为 "not a duplicate"(来自其他 SO 帖子),因为它处理 HTTP GET(而不是 HTTP POST) 和 CORS(相对于 not-CORS)。

这是我的技术栈。

为了启用 CORS,我遵循了这里的示例 http://enable-cors.org/server_expressjs.html。我的 NodeJS 服务器应用程序如下所示。

var express = require('express');
var bodyParser = require('body-parser');
var morgan = require('morgan');
var jwt = require('jsonwebtoken'); 

var port = process.env.PORT || 8080;

var router = express.Router();
var app = express();

app.set('secret', 'mySecret');
app.use(express.static('public'));
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(morgan('dev'));
app.use('/api', router);


router.use(function(req, res, next) {
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, x-access-token');
  res.header('Access-Control-Allow-Methods', 'POST, PUT, GET, OPTIONS, DELETE');
  next();
});

router.post('/authenticate', function(req, res) {
  var username = req.body.username;
  var pw = req.body.password;
  if(username !== 'root') {
    res.json({
      success: false,
      message: 'User not found'
    });
  } else if(pw !== 'root') {
    res.json({
      success: false,
      message: 'Password wrong'
    });
  } else {
    var user = {
      username: username,
      pw: pw
    };
    var token = jwt.sign(user, app.get('secret'), {
      expiresIn: 60 * 60 * 24 * 365
    });
    res.json({
      success: true,
      message: 'Enjoy your token!',
      token: token
    });
  }
});

router.use(function(req, res, next) {
  /* according to comments, have to ignore OPTIONS request from protection */
  if('OPTIONS' === req.method) { next(); return; } //original post modified here to show, after adding this line, the OPTIONS is accessible, then the GET does actually send the required HTTP header
  if('/api/authenticate' === req.originalUrl) {
    next();
    return;
  }

  var token = req.body.token || req.params['token'] || req.headers['x-access-token'];
  if(token) {
    jwt.verify(token, app.get('secret'), function(err, decoded) {
      if(err) {
        return res.json({
          success: false,
          message: 'Failed to authenticate token'
        });
      } else {
        req.decoded = decoded;
        next();
      }
    })
  } else {
    return res.status(403).send({
      success: false,
      message: 'No token provided'
    });
  }
});

router.get('/users', function(req, res) {
  res.json([
    { fname: 'john', lname: 'doe' },
    { fname: 'jane', lname: 'smith' }
  ]);
})

var server = app.listen(port, function() {
  var host = server.address().address;
  var port = server.address().port;
  console.log('Example app listening at http://%s:%s', host, port);
});

我的 AngularJS 服务如下所示。

myServices.factory('HomeService', ['$resource', '$http', '$location', '$cookies', 'conf', function($resource, $http, $location, $cookies, conf) {
  var svc = {};

  svc.getRestUrl = function() {
    return 'http://localhost:8080';
  };

  svc.sendData = function(url, data, method) {
    var restUrl = svc.getRestUrl() + url;
    var options = {
      method: method,
      url: restUrl,
      withCredentials: false
    };

    var token = $cookies.get('token');
    if(_.isEmpty(token)) {
      options.headers = {
        'X-Requested-With': 'XMLHttpRequest'
      };
    } else {
      options.headers = {
        'X-Requested-With': 'XMLHttpRequest',
        'x-access-token': token
      };
    }

    if(data) {
      options.data = data;
    } else {
      options.data = '';
    }

    return $http(options);
  }

  svc.getData = function(url) {
    return svc.sendData(url, null, 'GET');
  };

  svc.postData = function(url, data) {
    return svc.sendData(url, data, 'POST');
  };

  svc.authenticate = function(username, password) {
    var data = JSON.stringify({
      username: username,
      password: password
    });
    return svc.postData('/api/authenticate', data);
  };

  svc.getUsers = function() {
    return svc.getData('/api/users');
  };

  return svc;
}]);

备注

使用 Fiddler,对于 authenticate 我看到以下 HTTP 请求。

POST http://localhost:8080/api/authenticate HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Content-Length: 37
Accept: application/json, text/plain, */*
Origin: http://localhost
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36
Content-Type: application/json;charset=UTF-8
Referer: http://localhost/
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.8

{"username":"root","password":"root"}

对于 getUsers,我看到了以下 HTTP 请求。

OPTIONS http://localhost:8080/api/users HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Access-Control-Request-Method: GET
Origin: http://localhost
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36
Access-Control-Request-Headers: accept, x-access-token, x-requested-with
Accept: */*
Referer: http://localhost/
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8

关于通过 CORS 的 HTTP GET 未发送 HTTP headers,我是否遗漏了什么?

根据您的 HTTP 请求,OPTIONS 请求被触发到您的 getUsers 方法的 CORS API icase。

说到CORS,有2种请求

  1. 简单请求
  2. 预检请求

简单请求

一个简单的cross-site请求是满足以下所有条件的请求:

唯一允许的方法是:

  • 获取
  • 头部
  • POST

除了由用户代理自动设置的 header 之外,唯一允许手动设置的 header 是:

  • 接受
  • Accept-Language
  • Content-Language
  • Content-Type

Content-Type header 的唯一允许值是:

  • application/x-www-form-urlencoded
  • multipart/form-data
  • text/plain

预检请求

如果您提出任何违反简单请求条件的请求,则会发送 "preflighted" OPTIONS 请求以确定实际请求对 send.In 是否安全,特别是,请求预检如果:

  • 它使用 GET、HEAD 或 POST 以外的方法。另外,如果使用 POST 发送带有 Content-Type 以外的请求数据 application/x-www-form-urlencoded、multipart/form-data 或 text/plain,例如如果 POST 请求向 服务器使用 application/xml 或 text/xml,则请求是 预检。
  • 它在请求中设置自定义 headers(例如请求使用 header 比如X-PINGOTHER)

关于Preflighted Requests的更多细节,你可以参考这个MDN link

我相信这就是您的情况。即使您发出一个简单的 GET 请求,您也会添加 2 个自定义 headers X-Requested-With & x-access-token,这使得有必要验证您的 API 的安全性,因此浏览器发送预检 OPTIONS 请求。浏览器只有在收到有效响应后才会继续处理您的 GET 请求。

在您的 NodeJS 服务器代码中,您只处理对 /authenticate 的 POST 请求和对 /users 的 GET 请求,因此如果是 OPTIONS 请求,它将转到您正在检查令牌的默认处理程序,如果它不可用,您将使用 403 进行响应。所以我建议您更改代码以处理 OPTIONS 请求。