WebAPI CORS с Windows Authentication - разрешить анонимный запрос OPTIONS

У меня есть служба RAP WebAPI 2, работающая под управлением Windows Authentication. Он размещается отдельно от веб-сайта, поэтому я включил CORS с использованием пакета ASP.NET CORS NuGet. Мой клиентский сайт использует AngularJS.

До сих пор, вот что я пережил:

  • У меня не было установлен набор Credentials, поэтому запросы CORS возвращали 401. Решено путем добавления withCredentials в мою конфигурацию $httpProvider.
  • Затем я установил свой EnableCorsAttribute с подстановочным знаком, который не разрешен при использовании учетных данных. Решено путем установки явного списка истоков.
  • Это позволило моим запросам GET преуспеть, но мой POST выпустил предпродажный запрос, и я не создал никаких действий контроллера для поддержки глагола OPTIONS. Чтобы решить эту проблему, я применил MessageHandler как глобальный обработчик OPTIONS. Он просто возвращает 200 для любого запроса OPTIONS. Я знаю, что это не идеально, но работает пока, в Fiddler.

Где я застрял - мои предварительные вызовы Angular не включают учетные данные. Согласно этот ответ, это по дизайну, так как запросы OPTIONS предназначены для анонимности. Однако проверка подлинности Windows останавливает запрос с помощью 401.

Я попытался поместить атрибут [AllowAnonymous] в свой MessageHandler. На моем компьютере-разработчике он работает - глаголы OPTIONS не требуют аутентификации, но другие глаголы. Однако, когда я создаю и развертываю на тестовом сервере, я продолжаю получать 401 в моем запросе OPTIONS.

Можно ли применить [AllowAnonymous] к моему MessageHandler при использовании Windows Authentication? Если да, то какие-либо рекомендации о том, как это сделать? Или это неправильная дыра кролика, и я должен смотреть на другой подход?

UPDATE: Я смог заставить его работать, установив для проверки подлинности Windows и анонимной аутентификации на сайте в IIS. Это заставило всех разрешить анонимность, поэтому я добавил глобальный фильтр Authorize, сохраняя AllowAnonymous в своем MessageHandler.

Однако это похоже на взлом... Я всегда понимал, что нужно использовать только один метод аутентификации (не смешанный). Если у кого-то будет лучший подход, я буду благодарен за это.

Ответ 1

Я использовал самостоятельный хостинг с HttpListener, и следующее решение работало для меня:

  • Я разрешаю анонимные запросы OPTIONS
  • Включить CORS с поддержкой SupportsCredentials set true
var cors = new EnableCorsAttribute("*", "*", "*");
cors.SupportsCredentials = true;
config.EnableCors(cors);
var listener = appBuilder.Properties["System.Net.HttpListener"] as HttpListener;
if (listener != null)
{
    listener.AuthenticationSchemeSelectorDelegate = (request) => {
    if (String.Compare(request.HttpMethod, "OPTIONS", true) == 0)
    {
        return AuthenticationSchemes.Anonymous;
    }
    else
    {
        return AuthenticationSchemes.IntegratedWindowsAuthentication;
    }};
}

Ответ 2

Я некоторое время боролся за то, чтобы запросы CORS работали в следующих ограничениях (очень похожих на те, которые были у OP):

  • Аутентификация Windows для всех пользователей
  • Анонимная авторизация не разрешена
  • Работает с IE11, который в некоторых случаях, не отправляет запросы предварительного просмотра CORS (или, по крайней мере, не достигает global.asax BeginRequest в качестве запроса OPTIONS)

Моя последняя конфигурация такова:

web.config - разрешить непрофессиональные (анонимные) предполетные запросы (ОПЦИИ)

<system.web>
    <authentication mode="Windows" />
    <authorization>
        <allow verbs="OPTIONS" users="*"/>
        <deny users="?" />
    </authorization>
</system.web>

global.asax.cs - правильно отвечать заголовками, которые позволяют вызывающему из другого домена получать данные

protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
    if (Context.Request.HttpMethod == "OPTIONS")
    {
        if (Context.Request.Headers["Origin"] != null)
            Context.Response.AddHeader("Access-Control-Allow-Origin", Context.Request.Headers["Origin"]);

        Context.Response.AddHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, MaxDataServiceVersion");
        Context.Response.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
        Context.Response.AddHeader("Access-Control-Allow-Credentials", "true");

        Response.End();
    }
}

CORS, позволяющий

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // all requests are enabled in this example. SupportsCredentials must be here to allow authenticated requests          
        var corsAttr = new EnableCorsAttribute("*", "*", "*") { SupportsCredentials = true };
        config.EnableCors(corsAttr);
    }
}

protected void Application_Start()
{
    GlobalConfiguration.Configure(WebApiConfig.Register);
}

Ответ 3

Я решил это очень похоже, но с некоторыми подробностями и сосредоточен на службе oData​​p >

Я не отключил анонимную аутентификацию в IIS, так как мне был нужен запрос POST

И я добавил в Global.aspx(добавление MaxDataServiceVersion в Access-Control-Allow-Headers) того же кода, что и выше

protected void Application_BeginRequest(object sender, EventArgs e)
{
    if ((Context.Request.Path.Contains("api/") || Context.Request.Path.Contains("odata/")) && Context.Request.HttpMethod == "OPTIONS")
    {
        Context.Response.AddHeader("Access-Control-Allow-Origin", Context.Request.Headers["Origin"]);
        Context.Response.AddHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept,MaxDataServiceVersion");
        Context.Response.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
        Context.Response.AddHeader("Access-Control-Allow-Credentials", "true");
        Context.Response.End();
    }
} 

и WebAPIConfig.cs

public static void Register(HttpConfiguration config)
{
   // Web API configuration and services
   var cors = new EnableCorsAttribute("*", "*", "*");
   cors.SupportsCredentials = true;
   config.EnableCors(cors);


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

и вызов AngularJS

$http({
       method: 'POST',
        url: 'http://XX.XXX.XXX.XX/oData/myoDataWS.svc/entityName',
        withCredentials: true,
        headers: {
            'Content-Type': 'application/json;odata=verbose',
            'Accept': 'application/json;odata=light;q=1,application/json;odata=verbose;q=0.5',
            'MaxDataServiceVersion': '3.0'
        },
        data: {
            '@odata.type':'entityName',
            'field1': 1560,
            'field2': 24,
            'field3': 'sjhdjshdjsd',
            'field4':'wewewew',
            'field5':'ewewewe',
            'lastModifiedDate':'2015-10-26T11:45:00',
            'field6':'1359',
            'field7':'5'
        }
    });

Ответ 4

Это гораздо более простое решение - несколько строк кода, позволяющих всем запросам "OPTIONS" эффективно выдавать себя за учетную запись приложения. Вы можете отключить "Аноним" и настроить политики CORS для обычной практики, но затем добавить в свой файл global.asax.cs следующее:

            protected void Application_AuthenticateRequest(object sender, EventArgs e)
            {
                if (Context.Request.HttpMethod == "OPTIONS" && Context.User == null)
                {
                    Context.User = System.Security.Principal.WindowsPrincipal.Current;
                }
            }

Ответ 5

Дэйв,

После игры с пакетом CORS это заставило его работать для меня: [EnableCors (originins: ", headers:" ", methods:" *", SupportsCredentials = true)]

Мне пришлось включить SupportsCredentials = true. Происхождение, заголовки и методы установлены на "*"

Ответ 6

отключить анонимную аутентификацию в IIS, если она вам не нужна.

Добавьте это в свой глобальный asax:

protected void Application_BeginRequest(object sender, EventArgs e)
{
    if ((Context.Request.Path.Contains("api/") || Context.Request.Path.Contains("odata/")) && Context.Request.HttpMethod == "OPTIONS")
    {
        Context.Response.AddHeader("Access-Control-Allow-Origin", Context.Request.Headers["Origin"]);
        Context.Response.AddHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
        Context.Response.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
        Context.Response.AddHeader("Access-Control-Allow-Credentials", "true");
        Context.Response.End();
    }
} 

Убедитесь, что при включении cors вы также включаете использование учетных данных, например:

public static void Register(HttpConfiguration config)
{
   // Web API configuration and services
   var cors = new EnableCorsAttribute("*", "*", "*");
   cors.SupportsCredentials = true;
   config.EnableCors(cors);

   // Web API routes
   config.MapHttpAttributeRoutes();

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

Как вы можете видеть, я разрешаю CORS глобально и используя приложение BeginRequest hook, я аутентифицирую все запросы OPTIONS для api (Web Api) и запросов odata (если вы его используете).

Это прекрасно работает со всеми браузерами, на стороне клиента не забудьте добавить xhrFiled withCredentials, как показано ниже.

$.ajax({
    type : method,
    url : apiUrl,
    dataType : "json",
    xhrFields: {
        withCredentials: true
    },
    async : true,
    crossDomain : true,
    contentType : "application/json",
    data: data ? JSON.stringify(data) : ''
}).....

Я пытаюсь найти другое решение, избегая использовать крючок, но без успеха до сих пор, Я бы использовал конфигурацию web.config, чтобы сделать что-то вроде следующего: ПРЕДУПРЕЖДЕНИЕ КОНФИГУРАЦИЯ НИЖЕ НЕ РАБОТАЕТ!

  <system.web>
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5" />
    <authentication mode="Windows" />
    <authorization>
      <deny verbs="GET,PUT,POST" users="?" />
      <allow verbs="OPTIONS" users="?"/>
    </authorization>
  </system.web>
  <location path="api">
    <system.web>
      <authorization>
        <allow users="?"/>
      </authorization>
    </system.web>
  </location>

Ответ 7

Другие решения, которые я нашел в Интернете, не работали для меня или казались слишком взломанными; в конце я придумал более простое и эффективное решение:

web.config:

<system.web>
    ...
    <authentication mode="Windows" />
    <authorization>
        <deny users="?" />
    </authorization>
</system.web>

Свойства проекта:

  • Включить Windows Authentication
  • Отключить Anonymous Authentication

Настройка CORS:

[assembly: OwinStartup(typeof(Startup))]
namespace MyWebsite
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.UseCors(CorsOptions.AllowAll);

Для этого требуется сборка Microsoft.Owin.Cors, доступная на NUget.

Angular инициализация:

$httpProvider.defaults.withCredentials = true;

Ответ 8

Это мое решение.

Global.asax *

protected void Application_BeginRequest(object sender, EventArgs e)
{
    if(!ListOfAuthorizedOrigins.Contains(Context.Request.Headers["Origin"])) return;

    if (Request.HttpMethod == "OPTIONS")
    {
        HttpContext.Current.Response.Headers.Remove("Access-Control-Allow-Origin");
        HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", Context.Request.Headers["Origin"]);
        HttpContext.Current.Response.StatusCode = 200;
        HttpContext.Current.Response.End();
    }

    if (Request.Headers.AllKeys.Contains("Origin"))
    {
        HttpContext.Current.Response.Headers.Remove("Access-Control-Allow-Origin");
        HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", Context.Request.Headers["Origin"]);
    }
}

Ответ 9

В нашей ситуации:

  • Проверка подлинности Windows
  • Многократное происхождение CORS
  • SupportCredentials установлен в true
  • IIS хостинг

мы обнаружили, что решение было в другом месте:

В Web.Config все, что нам нужно было сделать, это добавить runAllManagedModulesForAllRequests = true

<modules runAllManagedModulesForAllRequests="true">

Мы пришли к этому решению, посмотрев на решение о том, почему Application_BeginRequest не запускался.

Другие конфигурации, которые у нас были:

в Web.Config

    <authentication mode="Windows" />
    <authorization>
      <allow verbs="OPTIONS" users="*" />
      <deny users="?"/>
    </authorization>

в WebApiConfig

        private static string GetAllowedOrigins()
        {
            return ConfigurationManager.AppSettings["CorsOriginsKey"];
        }

        public static void Register(HttpConfiguration config)
        {
            //set cors origins
            string origins = GetAllowedOrigins();
            var cors = new EnableCorsAttribute(origins, "*", "*");
            config.EnableCors(cors);

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

КСТАТИ "*" Cors origin не совместима с аутентификацией Windows/SupportCredentials = true

https://docs.microsoft.com/en-us/aspnet/web-api/overview/security/enabling-cross-origin-requests-in-web-api#pass-credentials-in-cross-origin-requests