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:
- Tutorial: criar uma API Web com o ASP.NET Core
- Habilitar o CORS (solicitações entre origens) no ASP.NET Core
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
- Visual Studio
- API Web 2.2
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.
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 diferentehttp://example.com:9000/foo.html
- Porta diferentehttps://example.com/foo.html
- Esquema diferentehttp://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.
Inicie o Visual Studio e crie um novo projeto de Aplicativo Web ASP.NET (.NET Framework ).
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 .
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") }; } } }
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".
Criar o projeto WebClient
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.
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.
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.
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.
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.
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á:
- Ação
- Controller
- 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.