Анализ междоменной проблемы

задняя часть

причина события

Требование просило открыть HTTP-интерфейс к front-end.В процессе совместной отладки произошла CORS-ошибка во время front-end запроса, то есть междоменная проблема.Ошибка следующая 👇

В начале я думал, что междоменные проблемы мне знакомы, и я часто сталкиваюсь с ними при написании кода в школе, разве это не решается за считанные минуты?

Но после изменения я остолбенел, почему оно не вступило в силу? Я задумался.

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

что такое кросс домен

Так называемый кросс-домен, полное названиеСовместное использование ресурсов между источниками (CORS)Cross-Origin Resource Sharing — это механизм на основе HTTP-заголовка, который позволяет серверу идентифицировать другие источники (домен, протокол и порт), кроме своего собственного, чтобы браузеры могли получать доступ к этим ресурсам и загружать их.

Например: бежать дальшеdomain-a.comКод JavaScript с использованиемXMLHttpRequestСделайте два запроса отдельно 👇

请求A: https://domain-a.com/query
请求B: https://domain-b.com/query

Поскольку запрашивающим сайтом страницы является домен-a.com, запрос A является запросом того же происхождения, а фоновый сервер домена-a.com разрешает запрос. Однако домен сайта, запрашивающего B, — domain-b.com. Если вы хотите отправить запрос на домен-b.com, это доступ из разных источников. Из соображений безопасности браузер ограничивает HTTP-запросы из разных источников. запрос, инициированный в сценарии. Как показано ниже:

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

КОРС рабочий механизм

Стандарт Cross-Origin Resource Sharing добавляет новый набор полей заголовка HTTP, которые позволяют серверам объявлять, какие источники имеют доступ к каким ресурсам через браузер.

Кроме того, для методов HTTP-запросов, которые могут иметь побочные эффекты для данных сервера (особенно HTTP-запросы, отличные от GET, или POST-запросы в сочетании с определенными типами MIME), браузер должен сначала использоватьOPTIONSЭтот метод инициирует предварительный запрос, чтобы узнать, разрешает ли сервер запрос между источниками.

Фактический HTTP-запрос выполняется только после того, как сервер подтвердил разрешение. В ответе на предварительный запрос сервер также может уведомить клиента о том, нужно ли ему передавать учетные данные (включая файлы cookie и данные, связанные с аутентификацией HTTP).

Общий процесс показан на рисунке выше.Сбой запроса CORS приведет к ошибке, но в целях безопасности невозможно точно знать, что пошло не так на уровне кода JavaScript. Вы можете только посмотреть на консоль браузера, чтобы увидеть, где именно произошла ошибка.

Среди нового набора полей заголовка HTTP наиболее важным является Access-Control-Allow-Origin, синтаксис которого выглядит следующим образом:

Access-Control-Allow-Origin: <origin> | *

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

Например, следующие значения полей позволят использовать значения изwww.domain-a.comзапрос:

Access-Control-Allow-Origin: http://www.domain-a.com

Если сервер указывает конкретное доменное имя, отличное от «*», значение поля Vary в заголовке ответа должно содержать Origin. Это сообщит клиенту: сервер возвращает разный контент для разных источников.

Решение, описанное далее, в основном выполняется вокруг этого поля.

Общие решения для междоменного взаимодействия в Spring

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

  1. Установите заголовок запроса напрямую
  2. Аннотация @CrossOrigin
  3. Конфигурация WebMvcConfigurer
  4. Использовать перехватчик CorsFilter

Этот фрагмент знаний был найден в Интернете, и я не буду объяснять его здесь. Вернитесь к анализу этой проблемы.

Приведенная выше схема все еще не работает после ее использования?

Чтобы решить эту проблему, прошли несколько процессов.

При использовании аннотации @CrossOrigin запросы на интерфейсы 1 и 2 идут нормально, но это решение недостаточно универсально и временно отбрасывается.

Не удалось настроить интерфейс 3 с помощью метода addCorsMappings конфигурации WebMvcConfigurer, по-прежнему возникает междоменная проблема.

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

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

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

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

Симптом или лечение

Позже я случайно обнаружил, что у внешнего интерфейса была проблема с URL-адресом при вызове интерфейса, и он не склеивал URL-адрес в соответствии с правилами, которые я ему дал.И действительно, после запроса правильного URL-адреса междоменная проблема сразу исчезла. .

То есть все произошло из-за того, что параметр запроса был ненормальным.

На сегодняшний день эта проблема фактически решена, и временное лечение завершено.

Однако в это время у меня возник новый вопрос, почему исключение параметра запроса не пошло на обработку бизнес-логики, а возникла междоменная проблема 🤔️

Давайте воссоздадим сцену 👇

@GetMapping("/error")
public ApiResult<String> errorTest(@RequestParam String id) {
    if (StringUtils.isBlank(id)) {
        return RestUtil.error(1, "参数异常");
    }
    return RestUtil.success(id);
}

Пример кода выше, запрос выглядит следующим образом 👇

fetch("http://www.a.com/error"); // 出现跨域
fetch("http://www.a.com/error?id=123"); // 正常访问

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

Причина, по которой система сообщает об исключении, заключается в том, что@RequestParamНа заметку, давайте взглянем на его исходный код

Этот параметр обязателен по умолчанию!

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

Таким образом, на самом деле, пока он установлен на@RequestParam(required = false), или не используйте эту аннотацию для решения проблемы в источнике.

Доберитесь до сути

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

Расспросив братьев и найдя соответствующую информацию, я обнаружил, что это было вызвано конфигурацией error_page в tengine (волшебная модификация Nginx внутри Ali). происходит, поэтому даже если междоменная обработка выполняется в бизнес-интерфейсе, внешний интерфейс все равно будет иметь междоменные проблемы, потому что на этот раз он пересекает домен страницы ошибки Taobao.

Каталог конфигурации nginx находится в/home/admin/cai/conf

Страница перенаправления не отображается в файле конфигурации, конфигурация страницы перенаправления находится в другом файле/opt/taobao/tengine/conf/services.conf

error_page              400 http://err.taobao.com/error1.html;
error_page              403 http://err.taobao.com/error1.html;
error_page              404 http://err.taobao.com/error1.html;
error_page              405 http://err.taobao.com/error1.html;
error_page              408 http://err.taobao.com/error1.html;
error_page              410 http://err.taobao.com/error1.html;
error_page              411 http://err.taobao.com/error1.html;
error_page              412 http://err.taobao.com/error1.html;
error_page              413 http://err.taobao.com/error1.html;
error_page              414 http://err.taobao.com/error1.html;
error_page              415 http://err.taobao.com/error1.html;
error_page              500 http://err.taobao.com/error2.html;
error_page              501 http://err.taobao.com/error2.html;
error_page              502 http://err.taobao.com/error2.html;
error_page              503 http://err.taobao.com/error2.html;
error_page              506 http://err.taobao.com/error2.html;

перезагрузить конфигурацию/home/admin/cai/bin/nginxctl reload

На данный момент суть исчерпана.

Обобщить решение

Сценарий 1. Отключите параметр proxy_intercept_errors в nginx.

proxy_intercept_errorsинструкция, за которой следуетon или off, соответственно указывая на включение или отключение функции блокировки страниц ошибок.

Вариант 2: Избегайте генерации ошибки непосредственно при запросе, в данном случае это проблема отсутствия параметров запроса

@RequestParamКомментарии обязательны по умолчанию, если ошибки 400 нет, то будет перенаправлено на страницу ошибок Taobao.

изменить на@RequestParam(required = false)

Затем проверьте следующую бизнес-логику

@GetMapping("/error")
public ApiResult<String> errorTest(@RequestParam(required = false) String id) {
    if (StringUtils.isBlank(id)) {
        return RestUtil.error(1, "参数异常");
    }
    return RestUtil.success(id);
}

Экспериментальная проверка

После того, как у нас есть разумная гипотеза, нам нужно проверить, вызвана ли она вышеупомянутой гипотезой, поэтому нам нужно ее проверить.

Проверка: модифицируйте nginxproxy_intercept_errorsПараметр конфигурации для отключения перехвата

Ожидаемый эффект: отсутствие перенаправления и встроенная страница ошибок tomcat

После эксперимента:

При получении консоли не будет междоменных ошибок.

На данный момент проблема решена.