Подтвердить что ты не робот

Chrome v37/38 CORS не работает (снова) с 401 для запросов перед полетом

Как и в версии Chrome версии 37, запросы на междоменные запросы не выполняются (снова), если сервер имеет аутентификацию, даже если все заголовки CORS установлены правильно. Это находится на localhost (мой dev ПК).

Некоторые из вас могут быть осведомлены об истории ошибок Chrome/CORS/auth, особенно при использовании HTTPS. Моя проблема не связана с HTTPS: у меня есть приложение AngularJS из localhost:8383, разговаривающее с сервером Java (Jetty) на localhost:8081, у которого активирован HTTP BASIC. GET работают нормально, но POSTs сбой 401:

XMLHttpRequest cannot load http://localhost:8081/cellnostics/rest/patient.
Invalid HTTP status code 401

Ранее я написал специальный (Java) фильтр CORS, который устанавливает правильные заголовки CORS, которые работали до v36. Он терпит неудачу в v37, а также последний v38 (38.0.2125.101 м). Он по-прежнему работает с Internet Explorer 11 (11.0.9600) и Opera 12.17 (сборка 1863).

GET выполняется успешно, но POST сбой. Похоже, что Chrome предварительно отправляет все мои POST-сообщения из-за типа контента: "application/json", и что это запрошенный запрос OPTIONS, который терпит неудачу.

В приложении Angular я явно устанавливаю следующие заголовки запросов. AFAIK этот параметр для withCredentials должен гарантировать, что учетные данные отправляются даже для запросов OPTIONS:

//Enable cross domain calls
$httpProvider.defaults.useXDomain = true;

//Send all requests, even OPTIONS, with credentials
$httpProvider.defaults.withCredentials = true;

Ниже приведен запрос/ответ. Вы можете видеть, что метод OPTIONS включен в заголовке Access-Control-Allow-Methods. Вы также можете увидеть, что начало приложения Javascript явно включено: Access-Control-Allow-Origin: http://localhost:8383.

Remote Address:[::1]:8081
Request URL:http://localhost:8081/cellnostics/rest/medicaltest
Request Method:OPTIONS
Status Code:401 Full authentication is required to access this resource

Request headers:

Accept:*/*
Accept-Encoding:gzip,deflate,sdch
Accept-Language:en-US,en;q=0.8,af;q=0.6
Access-Control-Request-Headers:accept, content-type
Access-Control-Request-Method:POST
Connection:keep-alive
Host:localhost:8081
Origin:http://localhost:8383
Referer:http://localhost:8383/celln-web/index.html
User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.101 Safari/537.36

Response headers:

Access-Control-Allow-Credentials:true
Access-Control-Allow-Headers:Cache-Control, Pragma, Origin, Authorization, Content-Type, X-Requested-With, Accept
Access-Control-Allow-Methods:POST, GET, OPTIONS, PUT, DELETE
Access-Control-Allow-Origin:http://localhost:8383
Access-Control-Max-Age:3600
Content-Length:0
Server:Jetty(8.1.8.v20121106)
WWW-Authenticate:Basic realm="Cellnostics"

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

Мне пришлось переключиться на IE 11 для тестирования моей веб-разработки. Тот факт, что одна и та же настройка клиента и сервера по-прежнему работает для IE и Opera, и тот факт, что есть история ошибок Chrome/CORS, заставляет меня подозревать Chrome.

EDIT: здесь выписка из списка событий net-internals Chrome:

t=108514 [st=0]   +URL_REQUEST_START_JOB  [dt=4]
    --> load_flags = 336011264 (BYPASS_DATA_REDUCTION_PROXY | DO_NOT_SAVE_COOKIES | DO_NOT_SEND_AUTH_DATA | DO_NOT_SEND_COOKIES | MAYBE_USER_GESTURE | VERIFY_EV_CERT)
    --> method = "OPTIONS"
    --> priority = "LOW"
    --> url = "http://localhost:8081/cellnostics/rest/patient"
...
t=108516 [st=2] HTTP_TRANSACTION_SEND_REQUEST_HEADERS
--> OPTIONS /cellnostics/rest/patient HTTP/1.1
   Host: localhost:8081
   Connection: keep-alive
   Access-Control-Request-Method: POST
   Origin: http://localhost:8383
   User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.101 Safari/537.36
   Access-Control-Request-Headers: accept, content-type
   Accept: */*
   Referer: http://localhost:8383/celln-web/index.html
   Accept-Encoding: gzip,deflate,sdch
   Accept-Language: en-US,en;q=0.8,af;q=0.6

Итак, похоже, что заголовок авторизации не отправляется с предварительным полем OPTIONS, хотя я явно устанавливаю withCredentials = true.

Однако почему IE и Opera все еще работают? Является ли Chrome более стандартизированным в этом отношении? Почему это сработало, а затем начать с отказа от версии v37?

EDIT: инструменты Chrome dev не показывают Content-Type запроса в дампах выше, но здесь он из журнала сети. Первая фотография показывает POST, когда аутентификация сервера отключена, а тип контента правильно отправлен как "application/json". Второй рис., Когда включен auth, показывая запрос запроса OPTIONS (кажется, OPTIONS всегда отправляется с типом контента "text/plain"?).

CORS POST with NO auth on serverCORS POST with auth enabled on server

4b9b3361

Ответ 1

@Корнель Массон, вы решили проблему? Я не понимаю, почему ваш сервер просит вас аутентифицировать запрос OPTIONS, но я столкнулся с этой же проблемой с сервером SAP NetWeaver. Я прочитал всю спецификацию CORS (я рекомендую), чтобы уточнить некоторые из ваших сомнений.

О вашем предложении

В приложении Angular я явно устанавливаю следующие заголовки запросов. AFAIK этот параметр для withCredentials должен гарантировать, что учетные данные отправляются даже для запросов OPTIONS:

  • В соответствии с спецификацией CORS, когда пользовательский агент (таким образом, браузер) предваряет запрос (запросы с помощью метода OPTIONS HTTP), он ДОЛЖЕН исключить учетные данные пользователя (файлы cookie, HTTP-аутентификация...), поэтому любой запрос OPTIONS не может запрашиваться как заверенный. Браузер запросит как аутентифицированный фактический запрос (тот, у кого запрошенный HTTP-метод, например GET, POST...), но не запрос предполетной проверки.
  • Таким образом, браузеры НЕ ДОЛЖНЫ отправлять учетные данные в запросе OPTIONS. Они будут делать в реальных запросах. Если вы пишете withCredentials = true, браузер должен делать то, что я говорю.

Согласно вашему предложению:

Похоже, что Chrome отправляет все мои POST-сообщения из-за типа контента: "application/json":

  • В спецификации также указывается, что браузер запрашивает предпросмотр, когда заголовок не является "простым заголовком", и здесь у вас есть то, что это означает:

    Заголовок называется простым заголовком, если имя поля заголовка - это ASCII-регистр, нечувствительный к регистру для Accept, Accept-Language или Content-Language, или если это несовместимое с ASCII совпадение для Content-Type и тип носителя значения поля заголовка (исключая параметры) является несоответствующим регистру ASCII совпадением для application/x-www-form-urlencoded, multipart/form-data или text/plain.

  • приложение /json не включено, поэтому браузер ДОЛЖЕН предварять запрос, как он.
Если кто-то найдет решение, это будет оценено.

EDIT: я просто нашел человека с той же проблемой, которая отражает реальные проблемы, и если вы используете тот же сервер, что и он, вам повезет, https://evolpin.wordpress.com/2012/10/12/the-cors/

Ответ 2

То же самое здесь. Я использую аутентификацию Windows NTLM. До версии Chrome 37 работало нормально. В версиях 37, 38 он не работает с 401 (неавторизованный) из-за отсутствия заголовка авторизации в предполетных ВАРИАНТАХ на обоих PUT и POST.

Серверная часть - это Microsoft Web Api 2.1. Я пробовал различные CORS, включая последний пакет NuGet от Microsoft, безрезультатно.

Мне нужно обходить Chrome, отправив GET-запрос вместо POST и разбивая довольно огромные данные по нескольким запросам, так как URL имеет ограничение по размеру, естественно.

Вот заголовки запроса/ответа:


Request URL: http://localhost:8082/api/ConfigurationManagerFeed/
Method: OPTIONS
Status: 401 Unauthorized

Request Headers
Accept: */*
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8,ru;q=0.6
Access-Control-Request-Headers: accept, content-type
Access-Control-Request-Method: POST
Connection: keep-alive
Host: localhost:8082
Origin: http://localhost:8383
Referer: http://localhost:8383/Application/index.html
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.104 Safari/537.36
X-DevTools-Emulate-Network-Conditions-Client-Id: 49E7FC68-A65C-4318-9292-852946051F27

Response Headers
Cache-Control: private
Content-Length: 6388
Content-Type: text/html; charset=utf-8
Date: Fri, 24 Oct 2014 13:40:07 GMT
Server: Microsoft-IIS/7.5
WWW-Authenticate: Negotiate
NTLM
X-Powered-By: ASP.NET

Ответ 3

В MS IIS я применил другое обходное решение, переопределив жизненный цикл стандартной страницы Microsoft, то есть обрабатывая ВАРИАНТЫ в начале HTTP-запроса в global.ascx:

public class Global : HttpApplication
{
    /// <summary>Check and cofigure CORS Pre-flight request on Begin Request lifecycle
    /// </summary>
    protected void Application_BeginRequest()
    {
        if (Request.Headers.AllKeys.Contains(CorsHandler.Origin) && Request.HttpMethod == "OPTIONS")
        {
            Response.StatusCode = (int)HttpStatusCode.OK;
            Response.Headers.Add(CorsHandler.AccessControlAllowCredentials, "true");
            Response.Headers.Add(CorsHandler.AccessControlAllowOrigin, Request.Headers.GetValues(CorsHandler.Origin).First());
            string accessControlRequestMethod = Request.Headers.GetValues(CorsHandler.AccessControlRequestMethod).FirstOrDefault();
            if (accessControlRequestMethod != null)
            {
                Response.Headers.Add(CorsHandler.AccessControlAllowMethods, accessControlRequestMethod);
            }
            var hdrs = Request.Headers.GetValues(CorsHandler.AccessControlRequestHeaders).ToList();
            hdrs.Add("X-Auth-Token");
            string requestedHeaders = string.Join(", ", hdrs.ToArray());
            Response.Headers.Add(CorsHandler.AccessControlAllowHeaders, requestedHeaders);
            Response.Headers.Add("Access-Control-Expose-Headers", "X-Auth-Token");
            Response.Flush();
        }
    }
}

Ответ 4

Мы столкнулись с этой же проблемой при попытке отладить внешнее приложение Angular 4, запущенное на localhost: 4200 (с помощью Angular CLI Live Development Server). Приложение Angular делает HTTP-запросы к приложению ASP.Net WebApi2, запущенному на локальном хосте (веб-сервере IIS) с проверкой подлинности Windows. Ошибка возникла только при выполнении запроса POST в Chrome (хотя наш WebApi настроен для COR). Мы смогли временно решить проблему, запустив Fiddler и запустив обратный прокси до тех пор, пока мы не найдем этот пост (спасибо).

Сообщение об ошибке в отладчике Chrome: Ответ на запрос перед полетом не проходит проверку контроля доступа: нет заголовка "Access-Control-Allow-Origin" на запрошенном ресурсе

Добавление приведенного ниже кода в наш веб файл Api2 - Global.asax устраняет эту проблему.

Если вы ищете "CorsHandler", вы можете просто заменить запись Джорджа строковыми строковыми значениями следующим образом:

    protected void Application_BeginRequest()
    {
        if (Request.Headers.AllKeys.Contains("Origin") && Request.HttpMethod == "OPTIONS")
        {
            Response.StatusCode = (int)HttpStatusCode.OK;
            Response.Headers.Add("Access-Control-Allow-Credentials", "true");
            Response.Headers.Add("Access-Control-Allow-Origin", Request.Headers.GetValues("Origin").First());
            string accessControlRequestMethod = Request.Headers.GetValues("Access-Control-Request-Method").FirstOrDefault();
            if (accessControlRequestMethod != null)
            {
                Response.Headers.Add("Access-Control-Allow-Methods", accessControlRequestMethod);
            }
            var hdrs = Request.Headers.GetValues("Access-Control-Request-Headers").ToList();
            hdrs.Add("X-Auth-Token");
            string requestedHeaders = string.Join(", ", hdrs.ToArray());
            Response.Headers.Add("Access-Control-Allow-Headers", requestedHeaders);
            Response.Headers.Add("Access-Control-Expose-Headers", "X-Auth-Token");
            Response.Flush();
        }
    }

С уважением,

Крис