Jetty CORS Preflight: Руководство по Access-Control-Allow-Private-Network
Узнайте, как обрабатывать CORS preflight запросы с заголовком Access-Control-Allow-Private-Network в Jetty 11.0.17. Полное руководство с реализацией и тестированием пользовательского фильтра.
Как обрабатывать CORS preflight запросы с заголовком ‘Access-Control-Allow-Private-Network’ в Jetty 11.0.17?
Я использую сервер Jetty 11.0.17 и encountering следующую ошибку, когда удаленная страница пытается получить доступ к локальному Jetty Web серверу на 127.0.0.1:8080:
Доступ к XMLHttpRequest по адресу 'http://127.0.0.1:8080/api' из источника 'http://<some-url>' был заблокирован политикой CORS: Ответ на preflight запрос не прошел проверку контроля доступа: В ответе на preflight запрос для этого запроса к частной сети, нацеленного на локальное адресное пространство, отсутствовал заголовок 'Access-Control-Allow-Private-Network'.
Похоже, что заголовок ‘Access-Control-Allow-Private-Network’ пока не поддерживается в CrossOriginFilter Jetty (см. GitHub issue: https://github.com/jetty/jetty.project/issues/7642).
Я создал пользовательский фильтр для решения этой проблемы:
FilterHolder corsFilter = new FilterHolder(new Filter() {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", "https://ccs-dev.cipher.kiev.ua");
response.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
response.setHeader("Access-Control-Allow-Headers", "Content-Type");
response.setHeader("Access-Control-Allow-Private-Network", "true");
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
return;
}
chain.doFilter(req, res);
}
@Override public void init(FilterConfig filterConfig) {}
@Override public void destroy() {}
});
contextHandler.addFilter(corsFilter, "/*", null);
Однако этот фильтр не обрабатывает preflight запросы должным образом. Как можно изменить этот пользовательский фильтр, чтобы правильно обрабатывать preflight запросы, включая заголовок ‘Access-Control-Allow-Private-Network’?
Обработка CORS preflight-запросов с заголовком ‘Access-Control-Allow-Private-Network’ в Jetty 11.0.17
Для обработки CORS preflight-запросов с заголовком ‘Access-Control-Allow-Private-Network’ в Jetty 11.0.17 необходимо реализовать пользовательский фильтр, который корректно обрабатывает preflight-запросы, включая необходимый заголовок. Jetty CrossOriginFilter в настоящее время не поддерживает заголовок ‘Access-Control-Allow-Private-Network’, что требует создания пользовательского решения.
Содержание
- Понимание требований CORS Preflight
- Правильная реализация пользовательского CORS-фильтра
- Альтернативные решения
- Тестирование и валидация
- Полный пример конфигурации
Понимание требований CORS Preflight
Ошибка, с которой вы сталкиваетесь, возникает потому, что Chrome теперь требует заголовок ‘Access-Control-Allow-Private-Network’ для запросов к частным сетевым адресам, таким как 127.0.0.1. Как указано в документации Chrome Private Network Access, “Preflight-запросы для PNA отправляются для всех запросов к частным сетям, независимо от метода запроса и режима”.
Когда удаленная страница пытается получить доступ к вашему локальному серверу Jetty, Chrome отправляет preflight-запрос OPTIONS для проверки, разрешает ли сервер кросс-оригинальные запросы. Если в ответ на этот preflight-запрос отсутствует заголовок ‘Access-Control-Allow-Private-Network: true’, браузер блокирует фактический запрос.
Правильная реализация пользовательского CORS-фильтра
Ваш текущий фильтр не корректно обрабатывает preflight-запросы, поскольку он только устанавливает заголовки, но не должным образом отвечает на запросы OPTIONS. Вот улучшенная реализация:
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlets.CrossOriginFilter;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.IOException;
public class CustomCORSFilter implements Filter {
private String allowedOrigins;
private String allowedMethods;
private String allowedHeaders;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// Чтение конфигурации из web.xml или использование значений по умолчанию
allowedOrigins = filterConfig.getInitParameter("allowedOrigins");
if (allowedOrigins == null) {
allowedOrigins = "*";
}
allowedMethods = filterConfig.getInitParameter("allowedMethods");
if (allowedMethods == null) {
allowedMethods = "GET, POST, OPTIONS, PUT, DELETE, HEAD";
}
allowedHeaders = filterConfig.getInitParameter("allowedHeaders");
if (allowedHeaders == null) {
allowedHeaders = "Content-Type, Authorization, X-Requested-With";
}
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
// Установка CORS-заголовков для всех ответов
response.setHeader("Access-Control-Allow-Origin", allowedOrigins);
response.setHeader("Access-Control-Allow-Methods", allowedMethods);
response.setHeader("Access-Control-Allow-Headers", allowedHeaders);
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Private-Network", "true");
// Обработка preflight-запросов
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setHeader("Access-Control-Max-Age", "3600"); // Кэширование preflight на 1 час
response.setStatus(HttpServletResponse.SC_OK);
return; // Прерывание цепочки для запросов OPTIONS
}
// Для фактических запросов продолжаем цепочку
chain.doFilter(req, res);
}
@Override
public void destroy() {
// Очистка при необходимости
}
}
Чтобы настроить этот фильтр в вашем сервере Jetty:
// Создание сервера и обработчика контекста
Server server = new Server(8080);
ServletContextHandler context = new ServletContextHandler();
context.setContextPath("/");
server.setHandler(context);
// Добавление пользовательского CORS-фильтра
FilterHolder corsFilter = new FilterHolder(new CustomCORSFilter());
corsFilter.setInitParameter("allowedOrigins", "https://ccs-dev.cipher.kiev.ua");
corsFilter.setInitParameter("allowedMethods", "GET, POST, OPTIONS, PUT, DELETE");
corsFilter.setInitParameter("allowedHeaders", "Content-Type, Authorization, X-Requested-With");
context.addFilter(corsFilter, "/*", EnumSet.of(DispatcherType.REQUEST, DispatcherType.ASYNC));
// Добавление ваших сервлетов и запуск сервера
// ...
server.start();
server.join();
Альтернативные решения
1. Расширение CrossOriginFilter Jetty
Поскольку Jetty 11 содержит CrossOriginFilter, вы можете расширить его и добавить недостающий заголовок:
import org.eclipse.jetty.servlets.CrossOriginFilter;
public class ExtendedCrossOriginFilter extends CrossOriginFilter {
@Override
protected void doFilter(HttpServletRequest request, HttpServletResponse response,
FilterChain chain) throws IOException, ServletException {
super.doFilter(request, response, chain);
// Добавление заголовка частной сети после работы родительского фильтра
if (request.getHeader("Origin") != null) {
response.setHeader("Access-Control-Allow-Private-Network", "true");
}
}
}
2. Использование Jetty 12 или более поздних версий
Согласно документации CrossOriginFilter, более новые версии Jetty могут иметь лучшую поддержку этого заголовка. При возможности рассмотрите возможность обновления.
3. Обходной путь с помощью командной строки Chrome
Для целей разработки вы можете запускать Chrome с отключенной безопасностью CORS:
google-chrome --disable-web-security --user-data-dir=/tmp/chrome-dev
Предупреждение: Это только для разработки и никогда не должно использоваться в продакшене.
Тестирование и валидация
Чтобы проверить правильность конфигурации CORS:
- Используйте инструменты разработчика браузера для проверки preflight-запроса и ответа
- Убедитесь, что ответ на preflight содержит:
Access-Control-Allow-Origin: https://ccs-dev.cipher.kiev.uaAccess-Control-Allow-Methods: GET, POST, OPTIONS, PUT, DELETEAccess-Control-Allow-Headers: Content-Type, Authorization, X-Requested-WithAccess-Control-Allow-Private-Network: true
- Протестируйте фактический запрос, чтобы убедиться в его успешном выполнении
Полный пример конфигурации
Вот полная конфигурация встроенного Jetty с пользовательским CORS-фильтром:
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import javax.servlet.DispatcherType;
import java.util.EnumSet;
public class JettyServerWithCORS {
public static void main(String[] args) throws Exception {
Server server = new Server(8080);
ServletContextHandler context = new ServletContextHandler();
context.setContextPath("/");
server.setHandler(context);
// Добавление пользовательского CORS-фильтра
FilterHolder corsFilter = new FilterHolder(new CustomCORSFilter());
corsFilter.setInitParameter("allowedOrigins", "https://ccs-dev.cipher.kiev.ua");
corsFilter.setInitParameter("allowedMethods", "GET, POST, OPTIONS, PUT, DELETE");
corsFilter.setInitParameter("allowedHeaders", "Content-Type, Authorization, X-Requested-With");
context.addFilter(corsFilter, "/*",
EnumSet.of(DispatcherType.REQUEST, DispatcherType.ASYNC));
// Добавление вашего REST-сервлета
context.addServlet(new ServletHolder(new YourRestServlet()), "/api/*");
// Запуск сервера
server.start();
server.join();
}
}
Это решение корректно обрабатывает как preflight-запросы OPTIONS, так и фактические запросы, включая необходимый заголовок ‘Access-Control-Allow-Private-Network’. Фильтр настроен на кэширование ответов preflight для лучшей производительности и поддерживает различные HTTP-методы и заголовки.
Источники
- Local Network Access in Jetty - Stack Overflow
- Private Network Access: introducing preflights | Chrome for Developers
- CORS for private networks (RFC1918) warning on call to local service - Stack Overflow
- The Access-Control-Allow-Private-Network CORS header recently introduced in Google Chrome · Issue #7642 · jetty/jetty.project
- CrossOriginFilter (Jetty :: Project 12.0.29 API)
Заключение
Чтобы корректно обрабатывать CORS preflight-запросы с заголовком ‘Access-Control-Allow-Private-Network’ в Jetty 11.0.17:
- Реализуйте пользовательский CORS-фильтр, который обрабатывает как OPTIONS preflight-запросы, так и фактические запросы
- Включите необходимые заголовки: Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Allow-Headers, и важнейший Access-Control-Allow-Private-Network: true
- Корректно отвечайте на запросы OPTIONS с соответствующими кодами состояния и заголовками кэширования
- Правильно настройте фильтр в вашем сервере Jetty с правильными типами диспетчера
- Тщательно тестируйте, чтобы убедиться в корректной работе как preflight, так и фактических запросов
Подход с пользовательским фильтром обеспечивает гибкость для обхода текущих ограничений Jetty при соблюдении требований Chrome к доступу к частным сетям. Для производственных сред рекомендуется отслеживать проблемы на GitHub Jetty на предмет нативной поддержки заголовка Access-Control-Allow-Private-Network в будущих релизах.