Partilhar via


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

Por Mike Wasson

Este 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 as 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 de Origem Cruzada) é 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 as técnicas anteriores, como o 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 na API Web ASP.NET. Começaremos criando dois projetos ASP.NET – um chamado "WebService", que hospeda um controlador de API Web, e o 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 de origem cruzada.

Mostra o serviço Web e o cliente Web

O que é "mesma origem"?

Duas URLs têm 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

O 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 à API Web ASP.NET.

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

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

    Nova caixa de diálogo ASP.NET projeto no Visual Studio

  3. Adicione um controlador de API Web nomeado 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 nos Aplicativos Web do Serviço de Aplicativo do Azure.) Para verificar se a API Web está funcionando, navegue até http://hostname/api/test/, em que hostname é o domínio em que você implantou o aplicativo. Você deve ver o texto de resposta, "GET: Mensagem de Teste".

    Navegador da Web mostrando mensagem de teste

Criar o projeto WebClient

  1. Crie outro projeto de Aplicativo Web ASP.NET (.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. No 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 que você examine diferentes solicitações de origem cruzada. Atualmente, o aplicativo WebService não oferece suporte a CORS, portanto, se você clicar no botão, receberá um erro.

Erro 'Experimente' no navegador

Observação

Se você observar 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 do 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 -Version sinalizador 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 de origem cruzada do WebClient, enquanto ainda não permite todas as outras solicitações entre domínios. Mais tarde, descreverei os parâmetros para [EnableCors] com mais detalhes.

Não inclua uma barra no final do URL de origem.

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

Navegador da Web mostrando mensagem de teste bem-sucedida

Como funciona o CORS

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 introduz vários novos cabeçalhos HTTP que permitem solicitações entre origens. Se um navegador der suporte a CORS, ele definirá esses cabeçalhos automaticamente para solicitações entre origens; você não precisa fazer nada de especial em seu código JavaScript.

Aqui está um exemplo de uma solicitação de origem cruzada. 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 disponibilizará a resposta para o aplicativo cliente.

Solicitações de comprovação

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

O navegador pode ignorar a solicitação de simulação 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
    • text/plain

A regra sobre cabeçalhos de solicitação se aplica a cabeçalhos que o aplicativo define chamando setRequestHeader no objeto XMLHttpRequest . (A especificação CORS chama isso de "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 simulaçã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 de simulação usa o método HTTP OPTIONS. 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 definidos pelo navegador.)

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 simulaçã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 simulação não enviam os cabeçalhos OPTIONS necessários por padrão. Confirme se os Access-Control-Request-Method cabeçalhos and 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 impede OPTIONSVerbHandler que o IIS manipule solicitações OPTIONS. A substituição de permite que as solicitações OPTIONS cheguem ao aplicativo porque o registro do ExtensionlessUrlHandler-Integrated-4.0 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 do controlador, ele 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 origins 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 simulaçã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 os cabeçalhos como "*". Para permitir cabeçalhos específicos, defina os cabeçalhos como 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 na forma 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 coisa diferente de "*", deverá incluir pelo menos "accept", "content-type" e "origin", além de quaisquer cabeçalhos personalizados aos quais 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
  • Content-Type
  • Expira
  • 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 de origem cruzada. 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;
    }
}

Credenciais de aprovação em solicitações de origem cruzada

As credenciais exigem tratamento especial em uma solicitação CORS. Por padrão, o navegador não envia nenhuma credencial com uma solicitação de origem cruzada. As credenciais incluem cookies, bem como esquemas de autenticação HTTP. Para enviar credenciais com uma solicitação de origem cruzada, 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;

No 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 de origem cruzada 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 true, 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 de origem cruzada.

Se o navegador enviar credenciais, mas a resposta não incluir um cabeçalho Access-Control-Allow-Credentials válido, 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 saiba. A especificação CORS também afirma que a configuração de 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 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 principais navegadores incluem suporte para CORS.