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
Я решил это очень похоже, но с некоторыми подробностями и сосредоточен на службе oDatap >
Я не отключил анонимную аутентификацию в 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