Перейти к основному содержимому

Исправление ошибок 401 с помощью CORS Preflights и Spring Security

· 4 мин. чтения

1. Обзор

В этом кратком руководстве мы узнаем, как решить ошибку «Ответ на предварительную проверку имеет недопустимый код состояния HTTP 401», которая может возникать в приложениях, поддерживающих связь между источниками и использующих Spring Security.

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

2. Запросы между источниками

Коротко говоря, кросс-запросы — это HTTP-запросы, в которых источник и цель запроса различны. Например, это тот случай, когда веб-приложение обслуживается из одного домена, а браузер отправляет запрос AJAX на сервер в другом домене.

Для управления запросами между источниками сервер должен включить определенный механизм, известный как CORS или совместное использование ресурсов между источниками.

Первым шагом в CORS является запрос OPTIONS , чтобы определить, поддерживает ли его цель запроса. Это называется предварительным запросом.

Затем сервер может ответить на предварительный запрос набором заголовков:

  • Access-Control-Allow-Origin : определяет, какие источники могут иметь доступ к ресурсу. '*' представляет любое происхождение
  • Access-Control-Allow-Methods : указывает разрешенные методы HTTP для запросов между источниками
  • Access-Control-Allow-Headers : указывает разрешенные заголовки запроса для запросов из разных источников
  • Access-Control-Max-Age : определяет время истечения срока действия кэшированного запроса предварительной проверки.

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

Добавить поддержку CORS в наш сервис на базе Spring несложно , но при неправильной настройке этот предварительный запрос всегда будет завершаться с ошибкой 401.

3. Создание REST API с поддержкой CORS

Чтобы смоделировать проблему, давайте сначала создадим простой REST API, который поддерживает запросы между источниками:

@RestController
@CrossOrigin("http://localhost:4200")
public class ResourceController {

@GetMapping("/user")
public String user(Principal principal) {
return principal.getName();
}
}

Аннотация @CrossOrigin гарантирует , что наши API доступны только из источника, указанного в его аргументе.

4. Защита нашего REST API

Давайте теперь защитим наш REST API с помощью Spring Security:

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic();
}
}

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

5. Оформление запроса перед полетом

Теперь, когда мы создали наш REST API, давайте попробуем выполнить предварительный запрос с помощью curl :

curl -v -H "Access-Control-Request-Method: GET" -H "Origin: http://localhost:4200" 
-X OPTIONS http://localhost:8080/user
...
< HTTP/1.1 401
...
< WWW-Authenticate: Basic realm="Realm"
...
< Vary: Origin
< Vary: Access-Control-Request-Method
< Vary: Access-Control-Request-Headers
< Access-Control-Allow-Origin: http://localhost:4200
< Access-Control-Allow-Methods: POST
< Access-Control-Allow-Credentials: true
< Allow: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH
...

Из вывода этой команды мы видим, что запрос был отклонен с ошибкой 401.

Поскольку это команда curl , мы не увидим в выводе ошибки «Ответ на предварительную проверку имеет недопустимый код состояния HTTP 401».

Но мы можем воспроизвести именно эту ошибку, создав внешнее приложение, которое использует наш REST API из другого домена, и запустив его в браузере.

6. Решение

Мы не исключили явным образом предварительные запросы из авторизации в нашей конфигурации Spring Security . Помните, что Spring Security по умолчанию защищает все конечные точки.

В результате наш API также ожидает маркер авторизации в запросе OPTIONS.

Spring предоставляет готовое решение для исключения запросов OPTIONS из проверок авторизации:

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// ...
http.cors();
}
}

Метод cors() добавит предоставленный Spring CorsFilter в контекст приложения, который, в свою очередь, обходит проверки авторизации для запросов OPTIONS.

Теперь мы можем снова протестировать наше приложение и убедиться, что оно работает.

7. Заключение

В этой короткой статье мы узнали, как исправить ошибку «Ответ на предварительную проверку имеет недопустимый код состояния HTTP 401», которая связана с Spring Security и запросами из разных источников.

Обратите внимание, что в этом примере клиент и API должны работать на разных доменах или портах, чтобы воссоздать проблему. Например, мы можем сопоставить имя хоста по умолчанию с клиентом, а IP-адрес машины с нашим REST API при работе на локальной машине.

Как всегда, пример, показанный в этом руководстве, можно найти на Github .