Habilitar solicitações entre origens no ASP.NET Web API 2

Por Mike Wasson

Esse conteúdo é para uma versão anterior do .NET. O novo desenvolvimento deve usar ASP.NET Core. Para obter mais informações sobre como usar a API Web e o CORS (solicitações entre origens) no ASP.NET Core, consulte:

A segurança do navegador impede que uma página da Web envie solicitações do AJAX para outro domínio. Essa restrição se chama política da mesma origem e impede que um site mal-intencionado leia dados confidenciais de outro site. No entanto, às vezes, talvez seja interessante permitir que outros sites chamem sua API Web.

O CORS (Compartilhamento de Recursos entre Origens) é um padrão W3C que permite que um servidor relaxe a política de mesma origem. Usando o CORS, um servidor pode explicitamente permitir algumas solicitações entre origens e rejeitar outras. O CORS é mais seguro e flexível do que técnicas anteriores, como JSONP. Este tutorial mostra como habilitar o CORS em seu aplicativo de API Web.

Software usado no tutorial

Introdução

Este tutorial demonstra o suporte ao CORS em ASP.NET Web API. Começaremos criando dois projetos ASP.NET – um chamado "WebService", que hospeda um controlador de API Web e outro chamado "WebClient", que chama WebService. Como os dois aplicativos são hospedados em domínios diferentes, uma solicitação AJAX de WebClient para WebService é uma solicitação entre origens.

Mostra o serviço Web e o cliente Web

O que é "mesma origem"?

Duas URLs terão a mesma origem se tiverem esquemas, hosts e portas idênticos. (RFC 6454)

Essas duas URLs têm a mesma origem:

  • http://example.com/foo.html
  • http://example.com/bar.html

Essas URLs têm origens diferentes das duas anteriores:

  • http://example.net - Domínio diferente
  • http://example.com:9000/foo.html - Porta diferente
  • https://example.com/foo.html - Esquema diferente
  • http://www.example.com/foo.html - Subdomínio diferente

Observação

A internet Explorer não considera a porta ao comparar origens.

Criar o projeto WebService

Observação

Esta seção pressupõe que você já saiba como criar projetos de API Web. Caso contrário, consulte Introdução com ASP.NET Web API.

  1. Inicie o Visual Studio e crie um novo projeto de aplicativo Web ASP.NET (.NET Framework).

  2. Na caixa de diálogo Novo aplicativo Web ASP.NET , selecione o modelo de projeto Vazio . Em Adicionar pastas e referências principais para, marque a caixa de seleção API Web .

    Caixa de diálogo Novo projeto de ASP.NET no Visual Studio

  3. Adicione um controlador de API Web chamado TestController com o seguinte código:

    using System.Net.Http;
    using System.Web.Http;
    
    namespace WebService.Controllers
    {
        public class TestController : ApiController
        {
            public HttpResponseMessage Get()
            {
                return new HttpResponseMessage()
                {
                    Content = new StringContent("GET: Test message")
                };
            }
    
            public HttpResponseMessage Post()
            {
                return new HttpResponseMessage()
                {
                    Content = new StringContent("POST: Test message")
                };
            }
    
            public HttpResponseMessage Put()
            {
                return new HttpResponseMessage()
                {
                    Content = new StringContent("PUT: Test message")
                };
            }
        }
    }
    
  4. Você pode executar o aplicativo localmente ou implantar no Azure. (Para as capturas de tela neste tutorial, o aplicativo é implantado no Serviço de Aplicativo do Azure Aplicativos Web.) Para verificar se a API Web está funcionando, navegue até http://hostname/api/test/, em que nome do host é o domínio em que você implantou o aplicativo. Você deverá ver o texto de resposta"GET: Mensagem de Teste".

    Navegador da Web mostrando a mensagem de teste

Criar o projeto WebClient

  1. Crie outro projeto ASP.NET Aplicativo Web (.NET Framework) e selecione o modelo de projeto MVC. Opcionalmente, selecione Alterar Autenticação>Sem Autenticação. Você não precisa de autenticação para este tutorial.

    Modelo MVC na caixa de diálogo Novo projeto ASP.NET no Visual Studio

  2. Em Gerenciador de Soluções, abra o arquivo Views/Home/Index.cshtml. Substitua o código neste arquivo pelo seguinte:

    <div>
        <select id="method">
            <option value="get">GET</option>
            <option value="post">POST</option>
            <option value="put">PUT</option>
        </select>
        <input type="button" value="Try it" onclick="sendRequest()" />
        <span id='value1'>(Result)</span>
    </div>
    
    @section scripts {
    <script>
        // TODO: Replace with the URL of your WebService app
        var serviceUrl = 'http://mywebservice/api/test'; 
    
        function sendRequest() {
            var method = $('#method').val();
    
            $.ajax({
                type: method,
                url: serviceUrl
            }).done(function (data) {
                $('#value1').text(data);
            }).fail(function (jqXHR, textStatus, errorThrown) {
                $('#value1').text(jqXHR.responseText || textStatus);
            });
        }
    </script>
    }
    

    Para a variável serviceUrl , use o URI do aplicativo WebService.

  3. Execute o aplicativo WebClient localmente ou publique-o em outro site.

Quando você clica no botão "Experimentar", uma solicitação AJAX é enviada ao aplicativo WebService usando o método HTTP listado na caixa suspensa (GET, POST ou PUT). Isso permite examinar diferentes solicitações entre origens. Atualmente, o aplicativo WebService não dá suporte a CORS, portanto, se você clicar no botão, receberá um erro.

Erro 'Experimente' no navegador

Observação

Se você watch o tráfego HTTP em uma ferramenta como o Fiddler, verá que o navegador envia a solicitação GET e a solicitação é bem-sucedida, mas a chamada AJAX retorna um erro. É importante entender que a política de mesma origem não impede que o navegador envie a solicitação. Em vez disso, ele impede que o aplicativo veja a resposta.

Depurador da Web fiddler mostrando solicitações da Web

Habilitar CORS

Agora, vamos habilitar o CORS no aplicativo WebService. Primeiro, adicione o pacote NuGet cors. No Visual Studio, no menu Ferramentas , selecione Gerenciador de Pacotes NuGet e, em seguida, Selecione Console do Gerenciador de Pacotes. Na janela Console do Gerenciador de Pacotes, digite o seguinte comando:

Install-Package Microsoft.AspNet.WebApi.Cors

Esse comando instala o pacote mais recente e atualiza todas as dependências, incluindo as principais bibliotecas de API Web. Use o sinalizador -Version para direcionar uma versão específica. O pacote CORS requer a API Web 2.0 ou posterior.

Abra o arquivo App_Start/WebApiConfig.cs. Adicione o seguinte código ao método WebApiConfig.Register :

using System.Web.Http;
namespace WebService
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // New code
            config.EnableCors();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
        }
    }
}

Em seguida, adicione o atributo [EnableCors] à TestController classe :

using System.Net.Http;
using System.Web.Http;
using System.Web.Http.Cors;

namespace WebService.Controllers
{
    [EnableCors(origins: "http://mywebclient.azurewebsites.net", headers: "*", methods: "*")]
    public class TestController : ApiController
    {
        // Controller methods not shown...
    }
}

Para o parâmetro origins , use o URI em que você implantou o aplicativo WebClient. Isso permite solicitações entre origens do WebClient, enquanto ainda não permite todas as outras solicitações entre domínios. Posteriormente, descreverei os parâmetros para [EnableCors] mais detalhadamente.

Não inclua uma barra no final da URL de origens .

Reimplante o aplicativo WebService atualizado. Você não precisa atualizar o WebClient. Agora, a solicitação AJAX do WebClient deve ter êxito. Os métodos GET, PUT e POST são todos permitidos.

Navegador da Web mostrando mensagem de teste bem-sucedida

Como o CORS funciona

Esta seção descreve o que acontece em uma solicitação CORS, no nível das mensagens HTTP. É importante entender como o CORS funciona, para que você possa configurar o atributo [EnableCors] corretamente e solucionar problemas se as coisas não funcionarem conforme o esperado.

A especificação CORS apresenta vários novos cabeçalhos HTTP que habilitam solicitações entre origens. Se um navegador der suporte ao CORS, ele definirá esses cabeçalhos automaticamente para solicitações entre origens; você não precisa fazer nada especial em seu código JavaScript.

Aqui está um exemplo de uma solicitação entre origens. O cabeçalho "Origem" fornece o domínio do site que está fazendo a solicitação.

GET http://myservice.azurewebsites.net/api/test HTTP/1.1
Referer: http://myclient.azurewebsites.net/
Accept: */*
Accept-Language: en-US
Origin: http://myclient.azurewebsites.net
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)
Host: myservice.azurewebsites.net

Se o servidor permitir a solicitação, ele definirá o cabeçalho Access-Control-Allow-Origin. O valor desse cabeçalho corresponde ao cabeçalho Origin ou é o valor curinga "*", o que significa que qualquer origem é permitida.

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: text/plain; charset=utf-8
Access-Control-Allow-Origin: http://myclient.azurewebsites.net
Date: Wed, 05 Jun 2013 06:27:30 GMT
Content-Length: 17

GET: Test message

Se a resposta não incluir o cabeçalho Access-Control-Allow-Origin, a solicitação AJAX falhará. Especificamente, o navegador não permite a solicitação. Mesmo que o servidor retorne uma resposta bem-sucedida, o navegador não disponibiliza a resposta para o aplicativo cliente.

Solicitações de simulação

Para algumas solicitações CORS, o navegador envia uma solicitação adicional, chamada de "solicitação de pré-voo", antes de enviar a solicitação real para o recurso.

O navegador poderá ignorar a solicitação de pré-voo se as seguintes condições forem verdadeiras:

  • O método de solicitação é GET, HEAD ou POST e

  • O aplicativo não define nenhum cabeçalho de solicitação diferente de Accept, Accept-Language, Content-Language, Content-Type ou Last-Event-ID e

  • O cabeçalho Content-Type (se definido) é um dos seguintes:

    • Application/x-www-form-urlencoded
    • multipart/form-data
    • texto/sem formatação

A regra sobre cabeçalhos de solicitação se aplica aos cabeçalhos que o aplicativo define chamando setRequestHeader no objeto XMLHttpRequest . (A especificação cors chama esses "cabeçalhos de solicitação de autor".) A regra não se aplica a cabeçalhos que o navegador pode definir, como User-Agent, Host ou Content-Length.

Aqui está um exemplo de uma solicitação de pré-vôo:

OPTIONS http://myservice.azurewebsites.net/api/test HTTP/1.1
Accept: */*
Origin: http://myclient.azurewebsites.net
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: accept, x-my-custom-header
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)
Host: myservice.azurewebsites.net
Content-Length: 0

A solicitação pré-voo usa o método HTTP OPTIONS. Ele inclui dois cabeçalhos especiais:

  • Access-Control-Request-Method: o método HTTP que será usado para a solicitação real.
  • Access-Control-Request-Headers: uma lista de cabeçalhos de solicitação que o aplicativo definiu na solicitação real. (Novamente, isso não inclui cabeçalhos que o navegador define.)

Aqui está um exemplo de resposta, supondo que o servidor permita a solicitação:

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 0
Access-Control-Allow-Origin: http://myclient.azurewebsites.net
Access-Control-Allow-Headers: x-my-custom-header
Access-Control-Allow-Methods: PUT
Date: Wed, 05 Jun 2013 06:33:22 GMT

A resposta inclui um cabeçalho Access-Control-Allow-Methods que lista os métodos permitidos e, opcionalmente, um cabeçalho Access-Control-Allow-Headers, que lista os cabeçalhos permitidos. Se a solicitação de pré-vôo for bem-sucedida, o navegador enviará a solicitação real, conforme descrito anteriormente.

As ferramentas comumente usadas para testar pontos de extremidade com solicitações OPTIONS de pré-vôo (por exemplo, Fiddler e Postman) não enviam os cabeçalhos OPTIONS necessários por padrão. Confirme se os Access-Control-Request-Method cabeçalhos e Access-Control-Request-Headers são enviados com a solicitação e se os cabeçalhos OPTIONS chegam ao aplicativo por meio do IIS.

Para configurar o IIS para permitir que um aplicativo ASP.NET receba e manipule solicitações OPTION, adicione a seguinte configuração ao arquivo web.config do aplicativo na <system.webServer><handlers> seção:

<system.webServer>
  <handlers>
    <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
    <remove name="OPTIONSVerbHandler" />
    <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
  </handlers>
</system.webServer>

A remoção do OPTIONSVerbHandler impede o IIS de lidar com solicitações OPTIONS. A substituição de ExtensionlessUrlHandler-Integrated-4.0 permite que as solicitações OPTIONS cheguem ao aplicativo porque o registro de módulo padrão só permite solicitações GET, HEAD, POST e DEBUG com URLs sem extensão.

Regras de escopo para [EnableCors]

Você pode habilitar o CORS por ação, por controlador ou globalmente para todos os controladores de API Web em seu aplicativo.

Por Ação

Para habilitar o CORS para uma única ação, defina o atributo [EnableCors] no método de ação. O exemplo a seguir habilita o CORS somente para o GetItem método .

public class ItemsController : ApiController
{
    public HttpResponseMessage GetAll() { ... }

    [EnableCors(origins: "http://www.example.com", headers: "*", methods: "*")]
    public HttpResponseMessage GetItem(int id) { ... }

    public HttpResponseMessage Post() { ... }
    public HttpResponseMessage PutItem(int id) { ... }
}

Por Controlador

Se você definir [EnableCors] na classe de controlador, ela se aplicará a todas as ações no controlador. Para desabilitar o CORS para uma ação, adicione o atributo [DisableCors] à ação. O exemplo a seguir habilita o CORS para todos os métodos, exceto PutItem.

[EnableCors(origins: "http://www.example.com", headers: "*", methods: "*")]
public class ItemsController : ApiController
{
    public HttpResponseMessage GetAll() { ... }
    public HttpResponseMessage GetItem(int id) { ... }
    public HttpResponseMessage Post() { ... }

    [DisableCors]
    public HttpResponseMessage PutItem(int id) { ... }
}

globalmente

Para habilitar o CORS para todos os controladores de API Web em seu aplicativo, passe uma instância enableCorsAttribute para o método EnableCors :

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        var cors = new EnableCorsAttribute("www.example.com", "*", "*");
        config.EnableCors(cors);
        // ...
    }
}

Se você definir o atributo em mais de um escopo, a ordem de precedência será:

  1. Ação
  2. Controller
  3. Global

Definir as origens permitidas

O parâmetro de origens do atributo [EnableCors] especifica quais origens têm permissão para acessar o recurso. O valor é uma lista separada por vírgulas das origens permitidas.

[EnableCors(origins: "http://www.contoso.com,http://www.example.com", 
    headers: "*", methods: "*")]

Você também pode usar o valor curinga "*" para permitir solicitações de qualquer origem.

Considere cuidadosamente antes de permitir solicitações de qualquer origem. Isso significa que, literalmente, qualquer site pode fazer chamadas AJAX para sua API Web.

// Allow CORS for all origins. (Caution!)
[EnableCors(origins: "*", headers: "*", methods: "*")]

Definir os métodos HTTP permitidos

O parâmetro methods do atributo [EnableCors] especifica quais métodos HTTP têm permissão para acessar o recurso. Para permitir todos os métodos, use o valor curinga "*". O exemplo a seguir permite apenas solicitações GET e POST.

[EnableCors(origins: "http://www.example.com", headers: "*", methods: "get,post")]
public class TestController : ApiController
{
    public HttpResponseMessage Get() { ... }
    public HttpResponseMessage Post() { ... }
    public HttpResponseMessage Put() { ... }    
}

Definir os cabeçalhos de solicitação permitidos

Este artigo descreveu anteriormente como uma solicitação de pré-vôo pode incluir um cabeçalho Access-Control-Request-Headers, listando os cabeçalhos HTTP definidos pelo aplicativo (os chamados "cabeçalhos de solicitação de autor"). O parâmetro headers do atributo [EnableCors] especifica quais cabeçalhos de solicitação de autor são permitidos. Para permitir quaisquer cabeçalhos, defina cabeçalhos como "*". Para permitir cabeçalhos específicos, defina cabeçalhos para uma lista separada por vírgulas dos cabeçalhos permitidos:

[EnableCors(origins: "http://example.com", 
    headers: "accept,content-type,origin,x-my-header", methods: "*")]

No entanto, os navegadores não são totalmente consistentes em como definem Access-Control-Request-Headers. Por exemplo, o Chrome atualmente inclui "origem". O FireFox não inclui cabeçalhos padrão, como "Aceitar", mesmo quando o aplicativo os define no script.

Se você definir cabeçalhos para qualquer outra coisa que não seja "*", deverá incluir pelo menos "aceitar", "tipo de conteúdo" e "origem", além de todos os cabeçalhos personalizados que você deseja dar suporte.

Definir os cabeçalhos de resposta permitidos

Por padrão, o navegador não expõe todos os cabeçalhos de resposta ao aplicativo. Os cabeçalhos de resposta disponíveis por padrão são:

  • Cache-Control
  • Content-Language
  • Tipo de conteúdo
  • Expires
  • Last-Modified
  • Pragma

A especificação CORS chama esses cabeçalhos de resposta simples. Para disponibilizar outros cabeçalhos para o aplicativo, defina o parâmetro exposedHeaders de [EnableCors].

No exemplo a seguir, o método do Get controlador define um cabeçalho personalizado chamado 'X-Custom-Header'. Por padrão, o navegador não exporá esse cabeçalho em uma solicitação entre origens. Para disponibilizar o cabeçalho, inclua 'X-Custom-Header' em exposedHeaders.

[EnableCors(origins: "*", headers: "*", methods: "*", exposedHeaders: "X-Custom-Header")]
public class TestController : ApiController
{
    public HttpResponseMessage Get()
    {
        var resp = new HttpResponseMessage()
        {
            Content = new StringContent("GET: Test message")
        };
        resp.Headers.Add("X-Custom-Header", "hello");
        return resp;
    }
}

Passar credenciais em solicitações entre origens

As credenciais exigem tratamento especial em uma solicitação CORS. Por padrão, o navegador não envia credenciais com uma solicitação entre origens. As credenciais incluem cookies, bem como esquemas de autenticação HTTP. Para enviar credenciais com uma solicitação entre origens, o cliente deve definir XMLHttpRequest.withCredentials como true.

Usando XMLHttpRequest diretamente:

var xhr = new XMLHttpRequest();
xhr.open('get', 'http://www.example.com/api/test');
xhr.withCredentials = true;

Em jQuery:

$.ajax({
    type: 'get',
    url: 'http://www.example.com/api/test',
    xhrFields: {
        withCredentials: true
    }

Além disso, o servidor deve permitir as credenciais. Para permitir credenciais entre origens na API Web, defina a propriedade SupportsCredentials como true no atributo [EnableCors] :

[EnableCors(origins: "http://myclient.azurewebsites.net", headers: "*", 
    methods: "*", SupportsCredentials = true)]

Se essa propriedade for verdadeira, a resposta HTTP incluirá um cabeçalho Access-Control-Allow-Credentials. Esse cabeçalho informa ao navegador que o servidor permite credenciais para uma solicitação entre origens.

Se o navegador enviar credenciais, mas a resposta não incluir um cabeçalho válido Access-Control-Allow-Credentials, o navegador não exporá a resposta ao aplicativo e a solicitação AJAX falhará.

Tenha cuidado ao definir SupportsCredentials como true, pois isso significa que um site em outro domínio pode enviar as credenciais de um usuário conectado para sua API Web em nome do usuário, sem que o usuário esteja ciente. A especificação CORS também afirma que definir origens como "*" é inválida se SupportsCredentials for true.

Provedores de política cors personalizados

O atributo [EnableCors] implementa a interface ICorsPolicyProvider . Você pode fornecer sua própria implementação criando uma classe que deriva de Attribute e implementa ICorsPolicyProvider.

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)]
public class MyCorsPolicyAttribute : Attribute, ICorsPolicyProvider 
{
    private CorsPolicy _policy;

    public MyCorsPolicyAttribute()
    {
        // Create a CORS policy.
        _policy = new CorsPolicy
        {
            AllowAnyMethod = true,
            AllowAnyHeader = true
        };

        // Add allowed origins.
        _policy.Origins.Add("http://myclient.azurewebsites.net");
        _policy.Origins.Add("http://www.contoso.com");
    }

    public Task<CorsPolicy> GetCorsPolicyAsync(HttpRequestMessage request)
    {
        return Task.FromResult(_policy);
    }
}

Agora você pode aplicar o atributo em qualquer lugar que você colocaria [EnableCors].

[MyCorsPolicy]
public class TestController : ApiController
{
    .. //

Por exemplo, um provedor de política cors personalizado pode ler as configurações de um arquivo de configuração.

Como alternativa ao uso de atributos, você pode registrar um objeto ICorsPolicyProviderFactory que cria objetos ICorsPolicyProvider .

public class CorsPolicyFactory : ICorsPolicyProviderFactory
{
    ICorsPolicyProvider _provider = new MyCorsPolicyProvider();

    public ICorsPolicyProvider GetCorsPolicyProvider(HttpRequestMessage request)
    {
        return _provider;
    }
}

Para definir o ICorsPolicyProviderFactory, chame o método de extensão SetCorsPolicyProviderFactory na inicialização, da seguinte maneira:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.SetCorsPolicyProviderFactory(new CorsPolicyFactory());
        config.EnableCors();

        // ...
    }
}

Suporte ao navegador

O pacote CORS da API Web é uma tecnologia do lado do servidor. O navegador do usuário também precisa dar suporte ao CORS. Felizmente, as versões atuais de todos os navegadores principais incluem suporte para CORS.