AngularJS executa uma solicitação HTTP OPTIONS para um recurso de origem cruzada

264

Estou tentando configurar o AngularJS para se comunicar com um recurso de origem cruzada, onde o host do recurso que entrega meus arquivos de modelo está em um domínio diferente e, portanto, a solicitação XHR que o angular executa deve ser de domínio cruzado. Eu adicionei o cabeçalho CORS apropriado ao meu servidor para a solicitação HTTP para fazer isso funcionar, mas parece não funcionar. O problema é que, quando inspeciono as solicitações HTTP no meu navegador (chrome), a solicitação enviada ao arquivo do ativo é uma solicitação OPTIONS (deve ser uma solicitação GET).

Não tenho certeza se isso é um bug no AngularJS ou se preciso configurar alguma coisa. Pelo que entendi, o wrapper XHR não pode fazer uma solicitação HTTP OPTIONS, portanto, parece que o navegador está tentando descobrir se é "permitido" fazer o download do ativo primeiro antes de executar a solicitação GET. Se for esse o caso, também preciso definir o cabeçalho CORS (Access-Control-Allow-Origin: http://asset.host ... ) Com o host do recurso também?

Matsko
fonte

Respostas:

226

A solicitação OPTIONS não é de forma alguma um bug do AngularJS; é assim que o padrão de compartilhamento de recursos entre origens exige que os navegadores se comportem. Consulte este documento: https://developer.mozilla.org/en-US/docs/HTTP_access_control , onde na seção "Visão geral" diz:

O padrão de compartilhamento de recursos de origem cruzada funciona adicionando novos cabeçalhos HTTP que permitem aos servidores descrever o conjunto de origens que têm permissão para ler essas informações usando um navegador da web. Além disso, para métodos de solicitação HTTP que podem causar efeitos colaterais nos dados do usuário (em particular; para métodos HTTP diferentes de GET ou para uso do POST com certos tipos MIME). A especificação exige que os navegadores "preflight" a solicitação, solicitando métodos suportados do servidor com um cabeçalho de solicitação HTTP OPTIONS e, em seguida, mediante "aprovação" do servidor, enviando a solicitação real com o método de solicitação HTTP real. Os servidores também podem notificar os clientes se "credenciais" (incluindo dados de autenticação HTTP e cookies) devem ser enviadas com solicitações.

É muito difícil fornecer uma solução genérica que funcione para todos os servidores da WWW, pois a instalação varia dependendo do servidor e dos verbos HTTP aos quais você pretende oferecer suporte. Gostaria de encorajar você a superar este excelente artigo ( http://www.html5rocks.com/en/tutorials/cors/ ) que tem muito mais detalhes sobre os cabeçalhos exatos que precisam ser enviados por um servidor.

pkozlowski.opensource
fonte
23
@matsko Você pode elaborar o que fez para resolver isso? Estou enfrentando o mesmo problema pelo qual uma solicitação AngularJS $resource POST está gerando uma solicitação OPTIONS para meu servidor ExpressJS back-end (no mesmo host; mas em uma porta diferente).
dbau
6
Para todos os votantes que estão fora do ar - é quase impossível fornecer uma configuração exata para todos os servidores da web - a resposta levaria 10 páginas neste caso. Em vez disso, vinculei a um artigo que fornece mais detalhes.
precisa saber é o seguinte
5
Você está certo que não pode prescrever uma resposta - será necessário adicionar cabeçalhos à sua resposta OPTIONS que cubra todos os cabeçalhos solicitados pelo navegador; no meu caso, usando o Chrome, os cabeçalhos 'accept' e 'x -requested-with '. No chrome, descobri o que precisava adicionar olhando a solicitação de rede e vendo o que o chrome estava pedindo. Estou usando nodejs / expressjs como o backup, então criei um serviço que retornou uma resposta à solicitação OPTIONS que abrange todos os cabeçalhos necessários. -1 porque não consegui usar esta resposta para descobrir o que fazer, tive que descobrir por mim mesma.
Ed Sykes
2
Eu sei que é mais de dois anos depois, mas ... O OP refere-se a solicitações GET várias vezes (ênfase adicionada por mim): «[...] a solicitação enviada ao arquivo de ativo é uma solicitação OPTIONS ( deve ser uma GET pedido ). » e «o navegador está tentando descobrir se é" permitido "fazer o download do ativo primeiro antes de executar a solicitação GET ». Como não pode ser um bug do AngularJS então? Solicitações de comprovação não devem ser proibidas para GET?
polettix 13/09/15
4
Até a solicitação GET do @polettix pode acionar a solicitação antes do voo em um navegador se forem usados ​​cabeçalhos personalizados. Marque "solicitações não tão simples" no artigo que vinculei : html5rocks.com/en/tutorials/cors/#toc-making-a-cors-request . Mais uma vez, é o mecanismo do navegador , não o AngularJS.
pkozlowski.opensource
70

Para o Angular 1.2.0rc1 +, você precisa adicionar uma resourceUrlWhitelist.

1.2: versão de lançamento, eles adicionaram uma função escapeForRegexp para que você não precise mais escapar das strings. Você pode adicionar o URL diretamente

'http://sub*.assets.example.com/**' 

certifique-se de adicionar ** para subpastas. Aqui está um jsbin funcional para 1.2: http://jsbin.com/olavok/145/edit


1.2.0rc: se você ainda estiver em uma versão rc, o Angular 1.2.0rc1 será a solução:

.config(['$sceDelegateProvider', function($sceDelegateProvider) {
     $sceDelegateProvider.resourceUrlWhitelist(['self', /^https?:\/\/(cdn\.)?yourdomain.com/]);
 }])

Aqui está um exemplo de jsbin em que funciona para 1.2.0rc1: http://jsbin.com/olavok/144/edit


Pré 1.2: Para versões mais antigas (ref http://better-inter.net/enabling-cors-in-angular-js/ ), você precisa adicionar as 2 linhas a seguir à sua configuração:

$httpProvider.defaults.useXDomain = true;
delete $httpProvider.defaults.headers.common['X-Requested-With'];

Aqui está um exemplo do jsbin em que funciona nas versões anteriores à 1.2: http://jsbin.com/olavok/11/edit

JStark
fonte
Isso funcionou para mim. Aqui está a mágica apropriada para o lado do servidor com nodejs / express: gist.github.com/dirkk0/5967221
dirkk0
14
Isso funciona apenas para a solicitação GET, ainda não é possível encontrar a solução para a solicitação POST no domínio cruzado.
PNCT
2
Combinar esta resposta com a resposta do usuário2304582 acima deve funcionar para solicitações POST. Você precisa dizer ao seu servidor para aceitar solicitações POST do servidor externo que o enviou #
Charlie Martin
Isso não parece que funcionará sozinho para mim ... então um -1. Você precisa de algo no mesmo URL que responda a uma solicitação de OPÇÕES como parte de uma verificação antes do voo. Você pode explicar por que isso funcionaria?
Ed Sykes
1
@ EdSykes, atualizei a resposta para 1.2 e adicionei um exemplo de jsbin em funcionamento. Espero que isso resolva isso para você. Certifique-se de pressionar o botão "Executar com JS".
JStark #
62

NOTA: Não tenho certeza se funciona com a versão mais recente do Angular.

ORIGINAL:

Também é possível substituir a solicitação OPTIONS (foi testada apenas no Chrome):

app.config(['$httpProvider', function ($httpProvider) {
  //Reset headers to avoid OPTIONS request (aka preflight)
  $httpProvider.defaults.headers.common = {};
  $httpProvider.defaults.headers.post = {};
  $httpProvider.defaults.headers.put = {};
  $httpProvider.defaults.headers.patch = {};
}]);
user2899845
fonte
1
Funciona bem para mim com o Chrome, FF e IE 10. O IE 9 e abaixo devem ser testados nativamente em máquinas Windows 7 ou XP.
DOM
3
Como alternativa, pode-se definir isso apenas no método desse recurso $ e não em todo o mundo:$resource('http://yourserver/yourentity/:id', {}, {query: {method: 'GET'});
h7r 25/02/2014
3
Existe algum problema com isso? Parece tão simples, e ainda assim a resposta é tão profunda no tópico? editar: Não funcionou.
Sebastialonso
Para onde esse código vai? em app.js, controller.js ou onde exatamente.
Murlidhar Fichadia
Este código não faz nada para uma solicitação de obtenção. Eu adicionei regra separada para get bem na minha configuração
kushalvm
34

Seu serviço deve responder a uma OPTIONSsolicitação com cabeçalhos como estes:

Access-Control-Allow-Origin: [the same origin from the request]
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: [the same ACCESS-CONTROL-REQUEST-HEADERS from request]

Aqui está um bom documento: http://www.html5rocks.com/en/tutorials/cors/#toc-adding-cors-support-to-the-server

user2304582
fonte
1
Para elaborar a parte [o mesmo ACCESS-CONTROL-REQUEST-HEADERS from request], como mencionei em outra resposta, é necessário examinar a solicitação OPTIONS que seu navegador está adicionando. Em seguida, adicione esses cabeçalhos ao serviço que você está construindo (ou servidor da web). Em expressjs parecidos com: ejs.options (' ', function (request, response) {response.header ('Access-Control-Allow-Origin-Origin', ' '); response.header ('Access-Control-Allow-Methods ',' GET, PUT, POST, DELETE '); response.header (' Access-Control-Allow-Headers ',' Content-Type, Authorization, accept, x-request-with '); response.send (); });
Ed Sykes
1
O comentário de Ed Sykes é muito preciso, saiba que os cabeçalhos enviados na resposta OPTIONS e os enviados na resposta POST devem ser exatamente os mesmos, por exemplo: Acesso-Controle-Permitir-Origem: * não é o mesmo que Acesso- Control-Allow-Origin: * por causa dos espaços.
Lucia
20

O mesmo documento diz

Diferentemente das solicitações simples (discutidas acima), as solicitações "preflighted" primeiro enviam um cabeçalho de solicitação HTTP OPTIONS para o recurso no outro domínio, para determinar se a solicitação real é segura para enviar. As solicitações entre sites são comprovadas dessa maneira, pois podem ter implicações nos dados do usuário. Em particular, um pedido é comprovado se:

Ele usa métodos diferentes de GET ou POST. Além disso, se o POST for usado para enviar dados da solicitação com um Tipo de Conteúdo diferente de application / x-www-form-urlencoded, multipart / form-data ou text / plain, por exemplo, se a solicitação POST enviar uma carga XML para o servidor usando application / xml ou text / xml, a solicitação é comprovada.

Ele define cabeçalhos personalizados na solicitação (por exemplo, a solicitação usa um cabeçalho como X-PINGOTHER)

Quando a solicitação original é Obter sem cabeçalhos personalizados, o navegador não deve fazer a solicitação de Opções, como faz agora. O problema é que ele gera um cabeçalho X-Requested-With que força a solicitação de Opções. Consulte https://github.com/angular/angular.js/pull/1454 sobre como remover este cabeçalho

Grum Ketema
fonte
10

Isso corrigiu meu problema:

$http.defaults.headers.post["Content-Type"] = "text/plain";
Cem Arguvanlı
fonte
10

Se você estiver usando um servidor nodeJS, poderá usar esta biblioteca, ela funcionou bem para mim https://github.com/expressjs/cors

var express = require('express')
  , cors = require('cors')
  , app = express();

app.use(cors());

e depois que você pode fazer um npm update.

Zakaria.dem
fonte
4

Aqui está a maneira como eu corrigi esse problema no ASP.NET

  • Primeiro, você deve adicionar o pacote de pepitas Microsoft.AspNet.WebApi.Cors

  • Em seguida, modifique o arquivo App_Start \ WebApiConfig.cs

    public static class WebApiConfig    
    {
       public static void Register(HttpConfiguration config)
       {
          config.EnableCors();
    
          ...
       }    
    }
  • Adicione este atributo à sua classe de controlador

    [EnableCors(origins: "*", headers: "*", methods: "*")]
    public class MyController : ApiController
    {  
        [AcceptVerbs("POST")]
        public IHttpActionResult Post([FromBody]YourDataType data)
        {
             ...
             return Ok(result);
        }
    }
  • Consegui enviar json para a ação dessa maneira

    $http({
            method: 'POST',
            data: JSON.stringify(data),
            url: 'actionurl',
            headers: {
                'Content-Type': 'application/json; charset=UTF-8'
            }
        }).then(...)

Referência: Habilitando solicitações de origem cruzada na API da Web do ASP.NET 2

asfez
fonte
2

De alguma forma eu consertei mudando

<add name="Access-Control-Allow-Headers" 
     value="Origin, X-Requested-With, Content-Type, Accept, Authorization" 
     />

para

<add name="Access-Control-Allow-Headers" 
     value="Origin, Content-Type, Accept, Authorization" 
     />
Ved Prakash
fonte
0

Perfeitamente descrito no comentário de pkozlowski. Eu tinha uma solução funcional com o AngularJS 1.2.6 e o ​​ASP.NET Web Api, mas quando eu atualizei o AngularJS para 1.3.3, as solicitações falharam.

  • A solução para o servidor Web API foi adicionar o tratamento das solicitações OPTIONS no início do método de configuração (mais informações nesta postagem do blog ):

    app.Use(async (context, next) =>
    {
        IOwinRequest req = context.Request;
        IOwinResponse res = context.Response;
        if (req.Path.StartsWithSegments(new PathString("/Token")))
        {
            var origin = req.Headers.Get("Origin");
            if (!string.IsNullOrEmpty(origin))
            {
                res.Headers.Set("Access-Control-Allow-Origin", origin);
            }
            if (req.Method == "OPTIONS")
            {
                res.StatusCode = 200;
                res.Headers.AppendCommaSeparatedValues("Access-Control-Allow-Methods", "GET", "POST");
                res.Headers.AppendCommaSeparatedValues("Access-Control-Allow-Headers", "authorization", "content-type");
                return;
            }
        }
        await next();
    });
Jiří Kavulák
fonte
O acima não funciona para mim. Existe uma solução muito mais simples para habilitar o CORS para uso com o AngularJS com a API do ASP.NET WEB 2.2 e posterior. Obtenha o pacote Microsoft WebAPI CORS do Nuget e, em seguida, no seu arquivo de configuração WebAPI ... var cors = new EnableCorsAttribute ("www.example.com", " ", " "); config.EnableCors (cors); Os detalhes estão no site da Microsoft aqui asp.net/web-api/overview/security/...
kmcnamee
0

Se você estiver usando as APIs Jersey for REST, poderá fazer o seguinte

Você não precisa alterar sua implementação de serviços da web.

Vou explicar para o Jersey 2.x

1) Primeiro adicione um ResponseFilter como mostrado abaixo

import java.io.IOException;

import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;

public class CorsResponseFilter implements ContainerResponseFilter {

@Override
public void filter(ContainerRequestContext requestContext,   ContainerResponseContext responseContext)
    throws IOException {
        responseContext.getHeaders().add("Access-Control-Allow-Origin","*");
        responseContext.getHeaders().add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT");

  }
}

2) no web.xml, na declaração de servlet de jersey, adicione o seguinte

    <init-param>
        <param-name>jersey.config.server.provider.classnames</param-name>
        <param-value>YOUR PACKAGE.CorsResponseFilter</param-value>
    </init-param>
indefinido
fonte
0

Desisti de tentar corrigir esse problema.

Meu web.config do IIS tinha o "Access-Control-Allow-Methods " , experimentei adicionar definições de configuração ao meu código Angular, mas depois de algumas horas tentando convencer o Chrome a chamar um serviço da Web JSON entre domínios, desisti miseravelmente.

No final, eu adicionei uma página web mudo manipulador ASP.Net, tem que ligar para o meu serviço JSON web, e retornar os resultados. Começou a funcionar em 2 minutos.

Aqui está o código que eu usei:

public class LoadJSONData : IHttpHandler
{
    public void ProcessRequest(HttpContext context)
    {
        context.Response.ContentType = "text/plain";

        string URL = "......";

        using (var client = new HttpClient())
        {
            // New code:
            client.BaseAddress = new Uri(URL);
            client.DefaultRequestHeaders.Accept.Clear();
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            client.DefaultRequestHeaders.Add("Authorization", "Basic AUTHORIZATION_STRING");

            HttpResponseMessage response = client.GetAsync(URL).Result;
            if (response.IsSuccessStatusCode)
            {
                var content = response.Content.ReadAsStringAsync().Result;
                context.Response.Write("Success: " + content);
            }
            else
            {
                context.Response.Write(response.StatusCode + " : Message - " + response.ReasonPhrase);
            }
        }
    }

    public bool IsReusable
    {
        get
        {
            return false;
        }
    }
}

E no meu controlador Angular ...

$http.get("/Handlers/LoadJSONData.ashx")
   .success(function (data) {
      ....
   });

Tenho certeza de que existe uma maneira mais simples / mais genérica de fazer isso, mas a vida é muito curta ...

Isso funcionou para mim, e eu posso continuar fazendo o trabalho normal agora !!

Mike Gledhill
fonte
0

Para um projeto IIS MVC 5 / Angular CLI (Sim, eu estou ciente de que seu problema está com o Angular JS) com API, fiz o seguinte:

web.config no <system.webServer>

    <staticContent>
      <remove fileExtension=".woff2" />
      <mimeMap fileExtension=".woff2" mimeType="font/woff2" />
    </staticContent>
    <httpProtocol>
      <customHeaders>
        <clear />
        <add name="Access-Control-Allow-Origin" value="*" />
        <add name="Access-Control-Allow-Headers" value="Content-Type, atv2" />
        <add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, OPTIONS"/>
      </customHeaders>
    </httpProtocol>

global.asax.cs

protected void Application_BeginRequest() {
  if (Request.Headers.AllKeys.Contains("Origin", StringComparer.OrdinalIgnoreCase) && Request.HttpMethod == "OPTIONS") {
    Response.Flush();
    Response.End();
  }
}

Isso deve corrigir seus problemas para o MVC e o WebAPI sem ter que fazer tudo o que for possível. Em seguida, criei um HttpInterceptor no projeto CLI Angular que foi adicionado automaticamente às informações relevantes do cabeçalho. Espero que isso ajude alguém em uma situação semelhante.

Damon Drake
fonte
0

Um pouco tarde para a festa,

Se você estiver usando Angular 7 (ou 5/6/7) e PHP como a API e ainda estiver recebendo esse erro, tente adicionar as seguintes opções de cabeçalho ao ponto final (PHP API).

 header("Access-Control-Allow-Origin: *");
 header("Access-Control-Allow-Methods: PUT, GET, POST, PUT, OPTIONS, DELETE, PATCH");
 header("Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Authorization");

Nota : O que requer apenas é Access-Control-Allow-Methods. Mas, eu estou colando aqui outros dois Access-Control-Allow-Origine Access-Control-Allow-Headers, simplesmente porque você precisará que todos eles sejam configurados corretamente para que o Angular App fale corretamente com sua API.

Espero que isso ajude alguém.

Felicidades.

Anjana Silva
fonte