AngularJS 应用程序的 CORS GET 中未发送 HTTP 标头

Posted

技术标签:

【中文标题】AngularJS 应用程序的 CORS GET 中未发送 HTTP 标头【英文标题】:HTTP headers are not being sent in CORS GET from AngularJS application 【发布时间】: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 请求 【参考方案1】:

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

说到CORS,有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请求。

【讨论】:

以上是关于AngularJS 应用程序的 CORS GET 中未发送 HTTP 标头的主要内容,如果未能解决你的问题,请参考以下文章

AngularJS + Nginx CORS

AngularJS on Rails 中的 CORS 问题

从 angularjs 调用 REST 服务时本地主机上的 CORS 问题

angularjs1.4 CORS 失败

rails-api 和 angularJs 中的 CORS

AngularJS和rails cors标头不起作用