c# - mvc - enable cors web api.net core 2
Cómo habilitar CORS en ASP.net Core WebAPI (12)
Lo que estoy tratando de hacer
Tengo una API web ASP.Net Core de back-end alojada en un plan gratuito de Azure (código fuente: https://github.com/killerrin/Portfolio-Backend ).
También tengo un sitio web de cliente que quiero hacer que consuma esa API. La aplicación del cliente no se alojará en Azure, sino que se alojará en las páginas de Github o en otro servicio de alojamiento web al que tenga acceso. Debido a esto, los nombres de dominio no se alinearán.
Al investigar esto, necesito habilitar CORS en el lado de la API web, sin embargo, he intentado casi todo durante varias horas y se niega a funcionar.
Cómo tengo la configuración del cliente Es solo un cliente simple escrito en React.js. Llamo a las API a través de AJAX en Jquery. El sitio React funciona, así que sé que no es eso. La llamada a la API de Jquery funciona como lo confirmé en el intento 1. Así es como hago las llamadas
var apiUrl = "http://andrewgodfroyportfolioapi.azurewebsites.net/api/Authentication";
//alert(username + "|" + password + "|" + apiUrl);
$.ajax({
url: apiUrl,
type: "POST",
data: {
username: username,
password: password
},
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (response) {
var authenticatedUser = JSON.parse(response);
//alert("Data Loaded: " + authenticatedUser);
if (onComplete != null) {
onComplete(authenticatedUser);
}
},
error: function (xhr, status, error) {
//alert(xhr.responseText);
if (onComplete != null) {
onComplete(xhr.responseText);
}
}
});
Lo que he intentado
Intento 1 - La forma ''adecuada''
https://docs.microsoft.com/en-us/aspnet/core/security/cors
He seguido este tutorial en el sitio web de Microsoft hasta T, probando las 3 opciones de habilitarlo globalmente en Startup.cs, configurándolo en cada controlador y probándolo en cada acción.
Siguiendo este método, Cross Domain funciona, pero solo en una sola Acción en un solo controlador (POST al AccountController).
Para todo lo demás, el middleware
Microsoft.AspNetCore.Cors
niega a configurar los encabezados.
Instalé
Microsoft.AspNetCore.Cors
través de NUGET y la versión es
1.1.2
Así es como lo configuro en Startup.cs
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add Cors
services.AddCors(o => o.AddPolicy("MyPolicy", builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
}));
// Add framework services.
services.AddMvc();
services.Configure<MvcOptions>(options =>
{
options.Filters.Add(new CorsAuthorizationFilterFactory("MyPolicy"));
});
...
...
...
}
// This method gets called by the runtime. Use this method to configure
//the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env,
ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
// Enable Cors
app.UseCors("MyPolicy");
//app.UseMvcWithDefaultRoute();
app.UseMvc();
...
...
...
}
Como puede ver, estoy haciendo todo lo que me dijeron.
Agregué Cors antes de MVC las dos veces, y cuando eso no funcionó intenté poner
[EnableCors("MyPolicy")]
en cada controlador como tal
[Route("api/[controller]")]
[EnableCors("MyPolicy")]
public class AdminController : Controller
Intento 2 - Brute forzándolo
https://andrewlock.net/adding-default-security-headers-in-asp-net-core/
Después de varias horas de intentar el intento anterior, pensé que trataría de aplicar una fuerza bruta tratando de configurar los encabezados manualmente, obligándolos a ejecutar cada respuesta. Hice esto siguiendo este tutorial sobre cómo agregar encabezados manualmente a cada respuesta.
Estos son los encabezados que agregué
.AddCustomHeader("Access-Control-Allow-Origin", "*")
.AddCustomHeader("Access-Control-Allow-Methods", "*")
.AddCustomHeader("Access-Control-Allow-Headers", "*")
.AddCustomHeader("Access-Control-Max-Age", "86400")
Estos son otros encabezados que probé que fallaron
.AddCustomHeader("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE")
.AddCustomHeader("Access-Control-Allow-Headers", "content-type, accept, X-PINGOTHER")
.AddCustomHeader("Access-Control-Allow-Headers", "X-PINGOTHER, Host, User-Agent, Accept, Accept: application/json, application/json, Accept-Language, Accept-Encoding, Access-Control-Request-Method, Access-Control-Request-Headers, Origin, Connection, Content-Type, Content-Type: application/json, Authorization, Connection, Origin, Referer")
Con este método, los encabezados Cross Site se aplican correctamente y se muestran en mi consola de desarrollador y en Postman.
Sin embargo, el problema es que mientras pasa la verificación de
Access-Control-Allow-Origin
, el navegador web lanza un ataque siseante (creo)
Access-Control-Allow-Headers
indica
415 (Unsupported Media Type)
Entonces el método de fuerza bruta tampoco funciona
Finalmente
¿Alguien ha conseguido que esto funcione y pueda echar una mano, o simplemente poder señalarme en la dirección correcta?
EDITAR
Entonces, para que las llamadas a la API se realicen, tuve que dejar de usar JQuery y cambiar a un formato Pure Javascript
XMLHttpRequest
.
Intento 1
Logré que
Microsoft.AspNetCore.Cors
funcione siguiendo la respuesta de MindingData, excepto dentro del Método de
Configure
coloca la
app.UseCors
antes de
app.UseMvc
.
Además, cuando se combina con las
options.AllowAnyOrigin()
solución de la API de Javascript,
options.AllowAnyOrigin()
para la compatibilidad con comodines también comenzó a funcionar.
Intento 2
Así que he logrado que el Intento 2 (fuerza bruta) funcione ... con la única excepción de que el Comodín para
Access-Control-Allow-Origin
no funciona y, como tal, tengo que configurar manualmente los dominios que tienen acceso lo.
Obviamente no es ideal, ya que solo quiero que este WebAPI esté abierto para todos, pero al menos funciona para mí en un sitio separado, lo que significa que es un comienzo
app.UseSecurityHeadersMiddleware(new SecurityHeadersBuilder()
.AddDefaultSecurePolicy()
.AddCustomHeader("Access-Control-Allow-Origin", "http://localhost:3000")
.AddCustomHeader("Access-Control-Allow-Methods", "OPTIONS, GET, POST, PUT, PATCH, DELETE")
.AddCustomHeader("Access-Control-Allow-Headers", "X-PINGOTHER, Content-Type, Authorization"));
Creé mi propia clase de middleware que funcionó para mí, creo que hay algo mal con la clase de middleware .net core
public class CorsMiddleware
{
private readonly RequestDelegate _next;
public CorsMiddleware(RequestDelegate next)
{
_next = next;
}
public Task Invoke(HttpContext httpContext)
{
httpContext.Response.Headers.Add("Access-Control-Allow-Origin", "*");
httpContext.Response.Headers.Add("Access-Control-Allow-Credentials", "true");
httpContext.Response.Headers.Add("Access-Control-Allow-Headers", "Content-Type, X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Date, X-Api-Version, X-File-Name");
httpContext.Response.Headers.Add("Access-Control-Allow-Methods", "POST,GET,PUT,PATCH,DELETE,OPTIONS");
return _next(httpContext);
}
}
// Extension method used to add the middleware to the HTTP request pipeline.
public static class CorsMiddlewareExtensions
{
public static IApplicationBuilder UseCorsMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<CorsMiddleware>();
}
}
y lo usé de esta manera en el startup.cs
app.UseCorsMiddleware();
Creo que si usa su propio middleware CORS , debe asegurarse de que realmente sea una solicitud CORS marcando el encabezado de origen .
public class CorsMiddleware
{
private readonly RequestDelegate _next;
private readonly IMemoryCache _cache;
private readonly ILogger<CorsMiddleware> _logger;
public CorsMiddleware(RequestDelegate next, IMemoryCache cache, ILogger<CorsMiddleware> logger)
{
_next = next;
_cache = cache;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context, IAdministrationApi adminApi)
{
if (context.Request.Headers.ContainsKey(CorsConstants.Origin) || context.Request.Headers.ContainsKey("origin"))
{
if (!context.Request.Headers.TryGetValue(CorsConstants.Origin, out var origin))
{
context.Request.Headers.TryGetValue("origin", out origin);
}
bool isAllowed;
// Getting origin from DB to check with one from request and save it in cache
var result = _cache.GetOrCreateAsync(origin, async cacheEntry => await adminApi.DoesExistAsync(origin));
isAllowed = result.Result.Result;
if (isAllowed)
{
context.Response.Headers.Add(CorsConstants.AccessControlAllowOrigin, origin);
context.Response.Headers.Add(
CorsConstants.AccessControlAllowHeaders,
$"{HeaderNames.Authorization}, {HeaderNames.ContentType}, {HeaderNames.AcceptLanguage}, {HeaderNames.Accept}");
context.Response.Headers.Add(CorsConstants.AccessControlAllowMethods, "POST, GET, PUT, PATCH, DELETE, OPTIONS");
if (context.Request.Method == "OPTIONS")
{
_logger.LogInformation("CORS with origin {Origin} was handled successfully", origin);
context.Response.StatusCode = (int)HttpStatusCode.NoContent;
return;
}
await _next(context);
}
else
{
if (context.Request.Method == "OPTIONS")
{
_logger.LogInformation("Preflight CORS request with origin {Origin} was declined", origin);
context.Response.StatusCode = (int)HttpStatusCode.NoContent;
return;
}
_logger.LogInformation("Simple CORS request with origin {Origin} was declined", origin);
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return;
}
}
await _next(context);
}
Debido a que tiene una política CORS muy simple (Permitir todas las solicitudes del dominio XXX), no necesita hacerla tan complicada. Intente hacer lo siguiente primero (Una implementación muy básica de CORS).
Si aún no lo ha hecho, instale el paquete nuget CORS.
Install-Package Microsoft.AspNetCore.Cors
En el método ConfigureServices de su startup.cs, agregue los servicios CORS.
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(); // Make sure you call this previous to AddMvc
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
Luego, en el método Configure de su startup.cs, agregue lo siguiente:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
// Make sure you call this before calling app.UseMvc()
app.UseCors(
options => options.WithOrigins("http://example.com").AllowAnyMethod()
);
app.UseMvc();
}
Ahora inténtalo. Las políticas son para cuando desea diferentes políticas para diferentes acciones (por ejemplo, diferentes hosts o diferentes encabezados). Por su simple ejemplo, realmente no lo necesita. Comience con este sencillo ejemplo y modifique como lo necesite desde allí.
Más información: http://dotnetcoretutorials.com/2017/01/03/enabling-cors-asp-net-core/
En mi caso, solo
get
solicitud funciona bien de acuerdo con la respuesta de MindingData.
Para otros tipos de solicitud, debe escribir:
app.UseCors(corsPolicyBuilder =>
corsPolicyBuilder.WithOrigins("http://localhost:3000")
.AllowAnyMethod()
.AllowAnyHeader()
);
No olvides agregar
.AllowAnyHeader()
La solución más simple es agregar
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseCors(options => options.AllowAnyOrigin());
app.UseHttpsRedirection();
app.UseMvc();
}
a Startup.cs.
Ninguno de los procedimientos anteriores ayudó y luego leí el article que resolvió el problema.
Debajo está el código.
public void ConfigureServices(IServiceCollection services)
{
// Add service and create Policy with options
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials() );
});
services.AddMvc();
}
y
public void Configure(IApplicationBuilder app)
{
// ...
// global policy - assign here or on each controller
app.UseCors("CorsPolicy");
y en la parte superior de mi método de acción
[EnableCors("CorsPolicy")]
Obtuve la respuesta anterior de MindingData para trabajar, pero tuve que usar Microsoft.AspNet.Cors en lugar de Microsoft.AspNetCore.Cors. Estoy usando el proyecto de API de aplicación web .NetCore en Visual Studio 2019
Para ampliar la answer , descubrí que todavía necesitaba proporcionar la respuesta OPTIONS para las solicitudes de verificación previa en .NET Core 2.1-preview para mi caso de uso:
// https://.com/a/45844400
public class CorsMiddleware
{
private readonly RequestDelegate _next;
public CorsMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
context.Response.Headers.Add("Access-Control-Allow-Origin", "*");
context.Response.Headers.Add("Access-Control-Allow-Credentials", "true");
// Added "Accept-Encoding" to this list
context.Response.Headers.Add("Access-Control-Allow-Headers", "Content-Type, X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Accept-Encoding, Content-Length, Content-MD5, Date, X-Api-Version, X-File-Name");
context.Response.Headers.Add("Access-Control-Allow-Methods", "POST,GET,PUT,PATCH,DELETE,OPTIONS");
// New Code Starts here
if (context.Request.Method == "OPTIONS")
{
context.Response.StatusCode = (int)HttpStatusCode.OK;
await context.Response.WriteAsync(string.Empty);
}
// New Code Ends here
await _next(context);
}
}
y luego habilitó el middleware como en Startup.cs
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMiddleware(typeof(CorsMiddleware));
// ... other middleware inclusion such as ErrorHandling, Caching, etc
app.UseMvc();
}
Para mí, no tenía nada que ver con el código que estaba usando. Para Azure tuvimos que ir a la configuración del Servicio de aplicaciones, en el menú lateral, la entrada "CORS". Allí tuve que agregar el dominio del que estaba solicitando cosas. Una vez que tuve eso, todo fue mágico.
Según su comentario en la respuesta de MindingData, no tiene nada que ver con su CORS, está funcionando bien.
Su acción de controlador está devolviendo los datos incorrectos. HttpCode 415 significa "Tipo de medio no compatible". Esto sucede cuando pasa el formato incorrecto al controlador (es decir, XML a un controlador que solo acepta json) o cuando devuelve un tipo incorrecto (devuelve Xml en un controlador que se declara que solo devuelve xml).
Para más adelante, verifique la existencia del atributo
[Produces("...")]
en su acción
intente agregar
jQuery.support.cors = true;
antes de la llamada de Ajax
También podría ser que los datos que envía a la API son inestables,
intente agregar la siguiente función JSON
var JSON = JSON || {};
// implement JSON.stringify serialization
JSON.stringify = JSON.stringify || function (obj) {
var t = typeof (obj);
if (t != "object" || obj === null) {
// simple data type
if (t == "string") obj = ''"'' + obj + ''"'';
return String(obj);
}
else {
// recurse array or object
var n, v, json = [], arr = (obj && obj.constructor == Array);
for (n in obj) {
v = obj[n]; t = typeof (v);
if (t == "string") v = ''"'' + v + ''"'';
else if (t == "object" && v !== null) v = JSON.stringify(v);
json.push((arr ? "" : ''"'' + n + ''":'') + String(v));
}
return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}");
}
};
// implement JSON.parse de-serialization
JSON.parse = JSON.parse || function (str) {
if (str === "") str = ''""'';
eval("var p=" + str + ";");
return p;
};
luego en sus datos: objeto cambiarlo a
data: JSON.stringify({
username: username,
password: password
}),
-
En
ConfigureServices
agregue
services.AddCors();
ANTES de services.AddMvc (); -
Añadir UseCors en Configurar
app.UseCors(builder => builder .AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader() .AllowCredentials()); app.UseMvc();
El punto principal es que agrega
app.UseCors
, antes de
app.UseMvc()
.
Asegúrese de declarar la funcionalidad CORS antes de MVC para que el middleware se active antes de que la tubería MVC obtenga el control y finalice la solicitud.
Después de que el método anterior funcione, puede cambiarlo, configurar un ORIGEN específico para aceptar llamadas API y evitar dejar su API tan abierta a cualquiera
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options => options.AddPolicy("ApiCorsPolicy", builder =>
{
builder.WithOrigins("http://localhost:4200").AllowAnyMethod().AllowAnyHeader();
}));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
En el método de configuración, dígale a CORS que use la política que acaba de crear:
app.UseCors("ApiCorsPolicy");
app.UseMvc();
Acabo de encontrar este artículo compacto sobre el tema: https://dzone.com/articles/cors-in-net-core-net-core-security-part-vi