Как включить CORS в ASP.net Core WebAPI

Что я пытаюсь сделать

У меня есть внутренний веб-API ASP.Net Core, размещенный на бесплатном плане Azure (исходный код: https://github.com/killerrin/Portfolio-Backend).

У меня также есть клиентский веб-сайт, который я хочу заставить использовать этот API. Клиентское приложение не будет размещаться в Azure, а будет размещаться на страницах Github или в другой службе веб-хостинга, к которой у меня есть доступ. Из-за этого доменные имена не будут выстраиваться.

Глядя на это, мне нужно включить CORS на стороне Web API, однако я уже несколько часов пробовал все, и он отказывается работать.

Как у меня есть установка клиента Это просто простой клиент, написанный на React.js. Я вызываю API через AJAX в Jquery. Сайт React работает, поэтому я знаю, что это не так. Вызов API Jquery работает, как я подтвердил в Попытке 1. Вот как я делаю вызовы

    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);
            }
        }
    });

Что я пробовал


Попытка 1 - "правильный" путь

https://docs.microsoft.com/en-us/aspnet/core/security/cors

Я следовал этому руководству на веб-сайте Microsoft до T, пробуя все 3 варианта включения его в глобальном масштабе в Startup.cs, настраивая его на каждом контроллере и пробуя его на каждом действии.

Следуя этому методу, кросс-домен работает, но только на одном действии на одном контроллере (POST для AccountController). Во всем остальном промежуточное ПО Microsoft.AspNetCore.Cors отказывается устанавливать заголовки.

Я установил Microsoft.AspNetCore.Cors через NUGET и версия 1.1.2

Вот как у меня это настроено в 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();

        ...
        ...
        ...
    }

Как видите, я делаю все как было сказано. Я добавляю Cors перед MVC оба раза, и когда это не сработало, я попытался поместить [EnableCors("MyPolicy")] на каждый контроллер, так что

[Route("api/[controller]")]
[EnableCors("MyPolicy")]
public class AdminController : Controller

Попытка 2 - грубое принуждение

https://andrewlock.net/adding-default-security-headers-in-asp-net-core/

После нескольких часов попыток с предыдущей попыткой я решил, что я попытаюсь перебить его, пытаясь установить заголовки вручную, заставляя их запускаться при каждом ответе. Я сделал это после этого урока о том, как вручную добавлять заголовки к каждому ответу.

Это заголовки, которые я добавил

.AddCustomHeader("Access-Control-Allow-Origin", "*")
.AddCustomHeader("Access-Control-Allow-Methods", "*")
.AddCustomHeader("Access-Control-Allow-Headers", "*")
.AddCustomHeader("Access-Control-Max-Age", "86400")

Это другие заголовки, которые я пробовал.

.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")

С помощью этого метода, кросс-сайт заголовки применяются правильно, и они отображаются в моей консоли разработчика и в Postman. Проблема, однако, заключается в том, что в то время как он проходит проверку Access-Control-Allow-Origin, веб-браузер бросает шипящее соответствие (я полагаю) Access-Control-Allow-Headers заявляя 415 (Unsupported Media Type)

Так что метод грубой силы тоже не работает


в заключение

Кто-нибудь заставил это сработать и мог бы протянуть руку или просто указать мне правильное направление?


РЕДАКТИРОВАТЬ

Таким образом, чтобы выполнить вызовы API, мне пришлось прекратить использовать JQuery и переключиться на формат Pure Javascript XMLHttpRequest.

Попытка 1

Мне удалось заставить работать Microsoft.AspNetCore.Cors, следуя ответу MindingData, за исключением метода Configure app.UseCors перед app.UseMvc.

Кроме того, при смешивании с параметрами Javascript API Solution.AllowAnyOrigin options.AllowAnyOrigin() для поддержки подстановочных знаков также начал работать.

Попытка 2

Таким образом, мне удалось заставить Попытку 2 (грубое принуждение) работать... с единственным исключением, что подстановочный знак для Access-Control-Allow-Origin не работает, и поэтому я должен вручную установить домены, которые имеют доступ к этому.

Это явно не идеально, так как я просто хочу, чтобы этот WebAPI был широко открыт для всех, но, по крайней мере, он работает для меня на отдельном сайте, что означает, что это начало

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"));

Ответы

Ответ 1

Поскольку у вас очень простая политика CORS (разрешить все запросы из домена XXX), вам не нужно усложнять ее. Сначала попробуйте выполнить следующее (очень простая реализация CORS).

Если вы еще этого не сделали, установите пакет CORS nuget.

Install-Package Microsoft.AspNetCore.Cors

В методе ConfigureServices вашего startup.cs добавьте службы CORS.

public void ConfigureServices(IServiceCollection services)
{
    services.AddCors(); // Make sure you call this previous to AddMvc
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}

Затем в методе Configure вашего startup.cs добавьте следующее:

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();
}

Теперь попробуй. Политики предназначены для случаев, когда вам нужны разные политики для разных действий (например, разные хосты или разные заголовки). Для вашего простого примера вам это действительно не нужно. Начните с этого простого примера и настройте его по необходимости.

Дальнейшее чтение: http://dotnetcoretutorials.com/2017/01/03/enabling-cors-asp-net-core/

Ответ 2

  • В ConfigureServices добавьте services.AddCors(); ДО службы .AddMvc();
  • Добавьте UseCors в Configure

    app.UseCors(builder => builder
        .AllowAnyOrigin()
        .AllowAnyMethod()
        .AllowAnyHeader()
        .AllowCredentials());   
    app.UseMvc();
    

Главное, что добавьте app.UseCors, прежде чем app.UseMvc().

Убедитесь, что вы объявили функциональность CORS перед MVC, чтобы промежуточное программное обеспечение сработало до того, как конвейер MVC получит управление и завершит запрос.

После того, как описанный выше метод работает, вы можете изменить его, настроить конкретный ORIGIN для приема вызовов API и не оставлять свой API открытым для всех

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);
}

В методе configure скажите CORS использовать только что созданную политику:

app.UseCors("ApiCorsPolicy");
app.UseMvc();

Я только что нашел эту компактную статью на эту тему - https://dzone.com/articles/cors-in-net-core-net-core-security-part-vi

Ответ 3

Я создал свой собственный класс промежуточного программного обеспечения, который работал для меня, я думаю, что что-то не так с классом промежуточного класса.net

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>();
    }
}

и использовал его таким образом в startup.cs

app.UseCorsMiddleware();

Ответ 4

В моем случае только get запрос работает хорошо в соответствии с MindingData ответа. Для других типов запросов вам необходимо написать:

app.UseCors(corsPolicyBuilder =>
   corsPolicyBuilder.WithOrigins("http://localhost:3000")
  .AllowAnyMethod()
  .AllowAnyHeader()
);

Не забудьте добавить .AllowAnyHeader()

Ответ 5

Чтобы развернуть ответ user8266077, я обнаружил, что мне все еще необходимо предоставить ответ OPTIONS для предварительных запросов в.NET Core 2.1-preview для моего варианта использования:

// https://stackoverflow.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);
  }
}

а затем включил промежуточное ПО, как в Startup.cs

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
  app.UseMiddleware(typeof(CorsMiddleware));
  // ... other middleware inclusion such as ErrorHandling, Caching, etc
  app.UseMvc();
}

Ответ 6

Ни одна из вышеперечисленных процедур не помогла, и я прочитал статью, в которой решил проблему.

Ниже приведен код.

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(); 
}

а также

public void Configure(IApplicationBuilder app)
{
    // ...

    // global policy - assign here or on each controller
    app.UseCors("CorsPolicy");

и на вершине моего actionmethod

[EnableCors("CorsPolicy")]

Ответ 7

попробуйте добавить jQuery.support.cors = true; перед вызовом Ajax

Также может быть, что данные, отправляемые в API,

попробуйте добавить следующую функцию 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;
    };

то в ваших данных: объект меняет его на

    data: JSON.stringify({
        username: username,
        password: password
    }),

Ответ 8

Я думаю, что если вы используете свое собственное промежуточное ПО CORS, вам нужно убедиться, что это действительно CORS- запрос, проверив заголовок источника.

 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);
    }

Ответ 9

Основываясь на вашем комментарии в ответе MindingData, он не имеет ничего общего с вашим CORS, он отлично работает.

Действие вашего контроллера возвращает неверные данные. HttpCode 415 означает "Тип неподдерживаемых носителей". Это происходит, когда вы либо передаете неправильный формат контроллеру (т.е. XML в контроллер, который принимает только json), либо когда вы возвращаете неправильный тип (возвращайте Xml в контроллере, который объявлен только для возврата xml).

Для более поздней проверки наличие атрибута [Produces("...")] на вашем действии

Ответ 10

Для меня это не имеет ничего общего с кодом, который я использовал. Для Azure нам пришлось зайти в настройки службы приложений, в боковом меню пункт "CORS". Там мне пришлось добавить домен, с которого я запрашивал материал. Как только я это понял, все стало волшебным.

Ответ 11

Самое простое решение это добавить

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseHsts();
        }

        app.UseCors(options => options.AllowAnyOrigin());

        app.UseHttpsRedirection();
        app.UseMvc();
    }

в Startup.cs.

Ответ 12

В файле launchSettings.json в разделе iisSettings установите для anonymousAuthentication значение true:

"iisSettings": {
    "windowsAuthentication": true,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "http://localhost:4200/",
      "sslPort": 0
    }
  }

Затем в Startup.cs в разделе ConfigureServices перед службами. AddMvc добавьте:

services.AddCors(options => options.AddPolicy("ApiCorsPolicy", builder =>
{
    builder
        .AllowAnyOrigin()
        .WithHeaders(HeaderNames.AccessControlAllowHeaders, "Content-Type")
        .AllowAnyMethod()
        .AllowCredentials();
}));

а затем, в методе настройки, перед app.UseMvc() добавьте:

app.UseCors("ApiCorsPolicy");

Ответ 13

Я получил ответ MindingData выше, чтобы работать, но я должен был использовать Microsoft.AspNet.Cors вместо Microsoft.AspNetCore.Cors. Я использую проект API.NetCore Web Application в Visual Studio 2019

Ответ 14

services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
                .AddJsonOptions(options => {
                    var resolver = options.SerializerSettings.ContractResolver;
                    if (resolver != null)
                        (resolver as DefaultContractResolver).NamingStrategy = null;
                });

            services.AddDbContext<PaymentDetailContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DevConnection"))); //Dependency Injection
            // options => options.UseSqlServer() Lamda Expression

            services.AddCors(options =>
            {
                options.AddPolicy(MyAllowSpecificOrigins,
                    builder =>
                    {
                        builder.WithOrigins("http://localhost:4200").AllowAnyHeader()
                                .AllowAnyMethod(); ;
                    });
            });