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 .