Запутался в том, как обрабатывать предварительные запросы CORS OPTIONS

Я новичок в работе с общим доступом к ресурсам Cross Origin и пытаюсь заставить свое веб-приложение отвечать на запросы CORS. Мое веб-приложение — это приложение Spring 3.2, работающее на Tomcat 7.0.42.

В файле web.xml моего веб-приложения я включил фильтр Tomcat CORS:

<!-- Enable CORS (cross origin resource sharing) -->
<!-- http://tomcat.apache.org/tomcat-7.0-doc/config/filter.html#CORS_Filter -->
<filter>
  <filter-name>CorsFilter</filter-name>
  <filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>CorsFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>   

Мой клиент (написанный с помощью AngularJS 1.2.12) пытается получить доступ к конечной точке REST с включенной базовой аутентификацией. Когда он делает запрос GET, Chrome сначала выполняет предварительную проверку запроса, но получает от сервера ответ 403 Forbidden:

Request URL:http://dev.mydomain.com/joeV2/users/listUsers
Request Method:OPTIONS
Status Code:403 Forbidden
Request Headers:
   OPTIONS /joeV2/users/listUsers HTTP/1.1
   Host: dev.mydomain.com
   Connection: keep-alive
   Cache-Control: max-age=0
   Access-Control-Request-Method: GET
   Origin: http://localhost:8000
   User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.107 Safari/537.36
   Access-Control-Request-Headers: accept, authorization
   Accept: */*
   Referer: http://localhost:8000/
   Accept-Encoding: gzip,deflate,sdch
   Accept-Language: en-US,en;q=0.8
Response Headers:
   HTTP/1.1 403 Forbidden
   Date: Sat, 15 Feb 2014 02:16:05 GMT
   Content-Type: text/plain; charset=UTF-8
   Content-Length: 0
   Connection: close

Я не совсем уверен, как поступить. Фильтр Tomcat по умолчанию принимает заголовок OPTIONS для доступа к ресурсу.

Я считаю, что проблема в том, что мой ресурс (URL-адрес запроса) http://dev.mydomain.com/joeV2/users/listUsers настроен на прием только методов GET:

@RequestMapping( method=RequestMethod.GET, value="listUsers", produces=MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public List<User> list(){
    return userService.findAllUsers();
}

Означает ли это, что я должен заставить этот метод/конечную точку также принимать метод OPTIONS? Если да, значит ли это, что я должен явно заставить каждую конечную точку REST принимать метод OPTIONS? Помимо загромождения кода, я не понимаю, как это вообще может работать. Насколько я понимаю, предварительная проверка OPTIONS предназначена для браузера, чтобы проверить, что браузер должен иметь доступ к указанному ресурсу. Я понимаю, что это означает, что мой метод контроллера не должен даже вызываться во время предварительной проверки. Таким образом, указание OPTIONS в качестве принятого метода было бы контрпродуктивным.

Должен ли Tomcat отвечать на запрос OPTIONS напрямую, даже не обращаясь к моему коду? Если да, то чего-то не хватает в моей конфигурации?


person Eric B.    schedule 15.02.2014    source источник


Ответы (2)


Я сел и отладил org.apache.catalina.filters.CorsFilter, чтобы выяснить, почему запрос был запрещен. Надеюсь, это может помочь кому-то в будущем.

Согласно W3 CORS Spec Section 6.2 Preflight Requests, предварительная проверка должна отклонить запрос, если какой-либо представленный заголовок не соответствует разрешенным заголовкам.

конфигурация по умолчанию для CorsFilter cors.allowed.headers (как у вас ) не включает заголовок Authorization, отправленный вместе с запросом.

Я обновил настройку фильтра cors.allowed.headers, чтобы принять заголовок authorization, и теперь предварительный запрос выполнен успешно.

<filter>
  <filter-name>CorsFilter</filter-name>
  <filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
    <init-param>
        <param-name>cors.allowed.headers</param-name>
        <param-value>Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers,Authorization</param-value>
    </init-param>     
</filter>

Конечно, я не уверен, почему заголовок authorization по умолчанию не разрешен фильтром CORS.

person Eric B.    schedule 15.02.2014
comment
любопытно, попробовали ли вы первый кусочек моего ответа? Я использовал apache, и мне не нужно было устанавливать заголовок authorization. Проверьте настройки $httpProvider для настроек Access-Control-Request-Headers. Ваш запрос на получение ожидает принятия заголовка authorization. настройка вашего $httpProvider с помощью $httpProvider.defaults.headers.common = {} должна сбросить это. (вот почему мне было интересно, пробовали ли вы это). Я предпочитаю использовать наиболее подходящие настройки Tomcat в качестве правила безопасности. общие настройки CORS могут привести к проблемам в будущем. - person Brian Vanderbusch; 15.02.2014
comment
@BrianVanderbusch Нет, на самом деле я не получил вашего ответа, пока не закончил отладку TC и не выяснил, в чем проблема. Я также проверю предложенное вами решение, хотя я не совсем уверен, будет ли это иметь значение, учитывая, что проблема была вызвана заголовком authorization. В моем случае мне нужен заголовок authorization, так как это конечная точка REST, в которой мне нужно проходить аутентификацию для каждого запроса. - person Eric B.; 17.02.2014
comment
@Eric Немного поздно, но заголовок Authorization на самом деле не должен сопровождать предварительные запросы, согласно спецификации здесь. Суть предварительной проверки заключается исключительно в том, чтобы убедиться, что метод (GET в вашем случае) и заголовки действительны для отправки на сервер. Не должно происходить авторизация/аутентификация - person Matt Dodge; 31.05.2014
comment
@mattedgod Означает ли это, что это проблема браузера, если он отправляет заголовок Auth вместе с предварительным запросом? Я использую Хром. - person Eric B.; 02.06.2014
comment
@ЭрикБ. Я удивлен, что Chrome сделал это, но я так понимаю, что эти предварительные запросы обрабатывались браузером, а не библиотекой, делающей запрос. Другими словами, jQuery не нужно обрабатывать предварительный запрос, он доверяет это браузеру. Однако в моей версии Chrome (35) в OS X заголовок Auth не передается. Может быть, это потому, что ваш authorization в нижнем регистре? Странно, но другого объяснения у меня нет. - person Matt Dodge; 02.06.2014
comment
Мой запрос CORS preflight OPTIONS удовлетворяется сервером с ошибкой 401 Неавторизованная остановка при рукопожатии CORS. - person Stephane; 21.08.2014
comment
@StephaneEybert Что делает аутентификация на вашей стороне? Ваше приложение или сам сервер? Я использую Tomcat, но выполняю аутентификацию в своем приложении. Tomcat CorsFilter запускается еще до того, как приложение будет достигнуто, поэтому аутентификация не является проблемой. - person Eric B.; 21.08.2014
comment
Привет, извините, сейчас в постели, 2 часа ночи здесь, я сделал собственный фильтр CORS, вызванный http раньше... в моей конфигурации безопасности Spring. - person Stephane; 21.08.2014
comment
Я опубликую больше завтра, сейчас на смартфоне. - person Stephane; 21.08.2014
comment
Привет, Эрик, у меня есть собственный фильтр: открытый класс SimpleCORSFilter реализует фильтр {}, и я пробую эти три альтернативы. Либо используйте только аннотацию @WebFilter(urlPatterns = /*), ИЛИ используйте аннотацию @Component в сочетании со следующим в моей конфигурации безопасности: http.addFilterBefore(simpleCORSFilter, ChannelProcessingFilter.class); ИЛИ http.authorizeRequests().antMatchers(HttpMethod.OPTIONS).permitAll(); Но беда в том, что 401 Unauthorized приходит только от Chrome и Firefox и то не каждый раз. - person Stephane; 21.08.2014
comment
@Eric Первый вариант с @WebFilter(urlPatterns = /*) дает 401 Unauthorized по запросу OPTIONS рукопожатия CORS в Chrome. - person Stephane; 21.08.2014
comment
@Eric Первый и третий варианты, объединенные вместе, также дают неавторизованный запрос OPTIONS рукопожатия CORS в Chrome и Firefox. - person Stephane; 21.08.2014
comment
Второй вариант предотвращает неавторизованный запрос OPTIONS рукопожатия CORS в Chrome и Firefox. Таким образом, кажется, что нужно использовать: аннотацию @Component в сочетании с http.addFilterBefore(simpleCORSFilter, ChannelProcessingFilter.class); инъекция. - person Stephane; 21.08.2014
comment
Даже немного позже у меня возникла эта проблема, и я использую Chrome, и я не вижу, чтобы он отправлял заголовок Authorization. Он отправляет Access-Control-Request-Headers со значением authorization во время предварительного запроса, но для меня это не то же самое, что отправка заголовка Authorization. Кстати, я попробовал вашу рекомендацию, но она не сработала для меня... - person Pere; 08.03.2017
comment
@отец. Это было пару лет назад, поэтому я не помню точных деталей, но мне нужно было изменить параметр CorsFilter, чтобы он принимал соответствующие заголовки. Возможно, конфигурация фильтра изменилась со времени моего сообщения или в зависимости от вашего приложения вам может потребоваться настроить его по-другому. - person Eric B.; 08.03.2017

Первое, что я хотел бы попробовать, это установить ваши общие заголовки для ваших http-запросов, которые отправляет angular, вставив следующий блок конфигурации в ваш модуль:

.config(function($httpProvider){
    $httpProvider.defaults.headers.common = {};
    $httpProvider.defaults.headers.post = {};
    $httpProvider.defaults.headers.put = {};
    $httpProvider.defaults.headers.patch = {};
})

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

Ваш фильтр CORS должен быть достаточным на стороне сервера, чтобы разрешать эти типы запросов, но иногда вам нужно указать методы запроса в дополнение к вашему происхождению, а также допустимые типы контента. В документах tomcat есть этот расширенный блок, который решает эти проблемы.

 <filter>
  <filter-name>CorsFilter</filter-name>
  <filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
  <init-param>
    <param-name>cors.allowed.origins</param-name>
    <param-value>*</param-value>
  </init-param>
  <init-param>
    <param-name>cors.allowed.methods</param-name>
    <param-value>GET,POST,HEAD,OPTIONS,PUT</param-value>
  </init-param>
  <init-param>
    <param-name>cors.allowed.headers</param-name>
    <param-value>Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers</param-value>
  </init-param>
  <init-param>
    <param-name>cors.exposed.headers</param-name>
    <param-value>Access-Control-Allow-Origin,Access-Control-Allow-Credentials</param-value>
  </init-param>
  <init-param>
    <param-name>cors.support.credentials</param-name>
    <param-value>true</param-value>
  </init-param>
  <init-param>
    <param-name>cors.preflight.maxage</param-name>
    <param-value>10</param-value>
  </init-param>
</filter>
<filter-mapping>
  <filter-name>CorsFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

Если первый не работает сам по себе, попробуйте улучшить фильтры, особенно:

<init-param>
        <param-name>cors.allowed.methods</param-name>
        <param-value>GET,POST,HEAD,OPTIONS,PUT</param-value>
      </init-param>
      <init-param>
        <param-name>cors.allowed.headers</param-name>
        <param-value>Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers</param-value>
      </init-param>
      <init-param>
        <param-name>cors.exposed.headers</param-name>
        <param-value>Access-Control-Allow-Origin,Access-Control-Allow-Credentials</param-value>
      </init-param>
person Brian Vanderbusch    schedule 15.02.2014
comment
Это помогло мне. Спасибо! - person steady_daddy; 08.09.2015