【问题标题】:HTTP headers are not being sent in CORS GET from AngularJS applicationAngularJS 应用程序的 CORS GET 中未发送 HTTP 标头
【发布时间】:2016-04-05 03:31:54
【问题描述】:

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

虽然有很多关于这个问题的 SO 帖子,但我已经尝试过,但没有一个有效。一种解决方案建议如果数据字段为空,则不发送 HTTP 标头,并且我尝试了添加空数据值的建议(顺便说一下,这对于 HTTP GET 请求并没有真正意义),但是仍然,HTTP 标头没有成功。

在旁注中,我可能会捍卫这个帖子/问题可能值得称其为“不重复”(来自其他 SO 帖子),因为它处理 HTTP GET(与 HTTP POST 相对)和 CORS(相对于到非CORS)。

这是我的技术栈。

  • NodeJS v4.2.2
  • Express v4.13.3
  • AngularJS v1.4.0

为了启用 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;
}]);

注意

  • 对于服务的authenticate 方法,这是一个HTTP POST
  • 对于服务的getUsers,这是一个HTTP GET
  • 当没有数据发送(HTTP GET)时,数据设置为空data: ''

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

POST http://localhost:8080/api/authenticate HTTP/1.1 主机:本地主机:8080 连接:保持活动 内容长度:37 接受:应用程序/json、文本/纯文本、*/* 来源:http://localhost X-Requested-With: XMLHttpRequest 用户代理:Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36 内容类型:application/json;charset=UTF-8 参考:http://localhost/ 接受编码:gzip,放气 接受语言:en-US,en;q=0.8 {"用户名":"root","密码":"root"}

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

选项 http://localhost:8080/api/users HTTP/1.1 主机:本地主机:8080 连接:保持活动 访问控制请求方法:GET 来源:http://localhost 用户代理:Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36 访问控制请求标头:接受、x-access-token、x-requested-with 接受: */* 参考:http://localhost/ 接受编码:gzip、deflate、sdch 接受语言:en-US,en;q=0.8

关于 HTTP GET over CORS,我是否遗漏了一些未发送 HTTP 标头的内容?

【问题讨论】:

  • 哪一个首先出现在您的代码中? GET 请求还是 POST 请求?如果您注意到,在 GET 的情况下,一个 OPTIONS 请求被触发,这是一个预检请求,当您在请求中发送自定义标头时发生。
  • POST 应该首先发生在 authenticate 以获取 JSON Web 令牌 (JWT),然后是 GET 到 getUsers(使用 JWT)。
  • OPTIONS 的响应似乎不错,列出了允许的来源、标头和方法。这是我所期望的。
  • 这似乎与我的中间件和选项有关。当发出 OPTIONS 请求时,我得到一个 403(在 Fiddler 中看不到,但在 Chrome 控制台中显示)。看来我应该将 OPTIONS 请求排除为受保护的资源。关于如何做到这一点的任何想法?
  • 由于您只在 nodejs 服务器中处理 post 和 get 请求,因此 OPTIONS 请求将发送到您正在检查令牌的默认处理程序,如果令牌不可用,您将提交 @987654331 @回复res.status(403).send({})。我认为您应该在此处更改代码以处理 OPTIONS 请求

标签: angularjs node.js express cors angularjs-http


【解决方案1】:

根据您的 HTTP 请求,在您的 getUsers 方法中向您的 CORS API 发出一个 OPTIONS 请求。

说到CORS,有2种请求

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

简单请求

一个简单的跨站请求是满足以下所有条件的:

唯一允许的方法是:

  • 获取
  • 发布

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

  • 接受
  • 接受语言
  • 内容-语言
  • 内容类型

Content-Type 标头的唯一允许值是:

  • application/x-www-form-urlencoded
  • 多部分/表单数据
  • 文本/纯文本

预检请求

如果您发出任何违反简单请求条件的请求,则会发送“预检”OPTIONS 请求以确定实际请求是否可以安全发送。特别是,如果满足以下条件,则预检请求:

  • 它使用 GET、HEAD 或 POST 以外的方法。此外,如果使用 POST 发送 Content-Type 以外的请求数据 application/x-www-form-urlencoded, multipart/form-data, 或 文本/纯文本,例如如果 POST 请求向 服务器使用 application/xml 或 text/xml,那么请求是 预检。
  • 它在请求中设置自定义标头(例如,请求使用标头 例如 X-PINGOTHER)

有关 Preflighted Requests 的更多详细信息,您可以参考此 MDN link

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

在您的 NodeJS 服务器代码中,您只处理对 /authenticate 的 POST 请求和对 /users 的 GET 请求,因此在 OPTIONS 请求的情况下,它将转到您正在检查令牌和的默认处理程序如果它不可用,您回复403。所以我建议你改变你的代码来处理OPTIONS请求。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-04-23
    • 1970-01-01
    • 1970-01-01
    • 2014-09-25
    • 2011-09-20
    • 1970-01-01
    • 2012-12-20
    相关资源
    最近更新 更多