Как обрабатывать CORS, используя JAX-RS с Джерси
Я разрабатываю клиентское приложение java-скрипта, на стороне сервера мне нужно обработать CORS, все сервисы, которые я написал в JAX-RS с JERSEY. Мой код:
@CrossOriginResourceSharing(allowAllOrigins = true)
@GET
@Path("/readOthersCalendar")
@Produces("application/json")
public Response readOthersCalendar(String dataJson) throws Exception {
//my code. Edited by gimbal2 to fix formatting
return Response.status(status).entity(jsonResponse).header("Access-Control-Allow-Origin", "*").build();
}
На данный момент я получаю сообщение об ошибке Нет заголовка "Access-Control-Allow-Origin" на запрошенном ресурсе. Поэтому происхождение " http://localhost:8080/" не разрешено ".
Пожалуйста, помогите мне с этим.
Спасибо и С уважением Будда Пунит
7 ответов
Примечание: обязательно прочитайте ОБНОВЛЕНИЕ внизу
@CrossOriginResourceSharing
это аннотация CXF, поэтому она не будет работать с Джерси.
С Джерси, чтобы справиться с CORS, я обычно просто использую ContainerResponseFilter
, ContainerResponseFilter
для Джерси 1 и 2 немного отличаются. Поскольку вы не упомянули, какую версию вы используете, я выложу обе.
Джерси 2
import java.io.IOException;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
@Provider
public class CORSFilter implements ContainerResponseFilter {
@Override
public void filter(ContainerRequestContext request,
ContainerResponseContext response) throws IOException {
response.getHeaders().add("Access-Control-Allow-Origin", "*");
response.getHeaders().add("Access-Control-Allow-Headers",
"origin, content-type, accept, authorization");
response.getHeaders().add("Access-Control-Allow-Credentials", "true");
response.getHeaders().add("Access-Control-Allow-Methods",
"GET, POST, PUT, DELETE, OPTIONS, HEAD");
}
}
Если вы используете сканирование пакетов для обнаружения поставщиков и ресурсов, @Provider
аннотация должна позаботиться о конфигурации для вас. Если нет, то вам нужно будет явно зарегистрировать его в ResourceConfig
или Application
подкласс.
Пример кода для явной регистрации фильтра в ResourceConfig
:
final ResourceConfig resourceConfig = new ResourceConfig();
resourceConfig.register(new CORSFilter());
final final URI uri = ...;
final HttpServer httpServer = GrizzlyHttpServerFactory.createHttpServer(uri, resourceConfig);
Для Jersey 2.x, если у вас возникают проблемы при регистрации этого фильтра, вот пара ресурсов, которые могут помочь
Джерси 1
import com.sun.jersey.spi.container.ContainerRequest;
import com.sun.jersey.spi.container.ContainerResponse;
import com.sun.jersey.spi.container.ContainerResponseFilter;
public class CORSFilter implements ContainerResponseFilter {
@Override
public ContainerResponse filter(ContainerRequest request,
ContainerResponse response) {
response.getHttpHeaders().add("Access-Control-Allow-Origin", "*");
response.getHttpHeaders().add("Access-Control-Allow-Headers",
"origin, content-type, accept, authorization");
response.getHttpHeaders().add("Access-Control-Allow-Credentials", "true");
response.getHttpHeaders().add("Access-Control-Allow-Methods",
"GET, POST, PUT, DELETE, OPTIONS, HEAD");
return response;
}
}
Конфигурация web.xml, вы можете использовать
<init-param>
<param-name>com.sun.jersey.spi.container.ContainerResponseFilters</param-name>
<param-value>com.yourpackage.CORSFilter</param-value>
</init-param>
Или же ResourceConfig
ты можешь сделать
resourceConfig.getContainerResponseFilters().add(new CORSFilter());
Или сканирование пакета с @Provider
аннотаций.
РЕДАКТИРОВАТЬ
Обратите внимание, что приведенный выше пример может быть улучшен. Вам нужно будет узнать больше о том, как работает CORS. Пожалуйста, смотрите здесь. Например, вы получите заголовки для всех ответов. Это может быть нежелательно. Возможно, вам просто нужно обработать предпечатную проверку (или ОПЦИИ). Если вы хотите увидеть лучше реализованный фильтр CORS, вы можете проверить исходный код для RESTeasy CorsFilter
ОБНОВИТЬ
Поэтому я решил добавить более правильную реализацию. Вышеуказанная реализация является ленивой и добавляет все заголовки CORS ко всем запросам. Другая ошибка заключается в том, что, поскольку это всего лишь фильтр ответа, запрос все еще обрабатывается. Это означает, что при поступлении предварительного запроса, который является запросом OPTIONS, метод OPTIONS не будет реализован, поэтому мы получим ответ 405, что неверно.
Вот как это должно работать. Таким образом, существует два типа запросов CORS: простые запросы и запросы перед проверкой. Для простого запроса браузер отправит фактический запрос и добавит Origin
заголовок запроса. Браузер ожидает, что ответ получит Access-Control-Allow-Origin
заголовок, сказав, что происхождение от Origin
заголовок разрешен. Чтобы его можно было считать "простым запросом", он должен соответствовать следующим критериям:
- Будьте одним из следующих методов:
- ПОЛУЧИТЬ
- ГОЛОВА
- СООБЩЕНИЕ
- Помимо заголовков, автоматически устанавливаемых браузером, запрос может содержать только следующие заголовки, установленные вручную:
Accept
Accept-Language
Content-Language
Content-Type
DPR
Save-Data
Viewport-Width
Width
- Единственные допустимые значения для
Content-Type
Заголовок:application/x-www-form-urlencoded
multipart/form-data
text/plain
Если запрос не удовлетворяет всем этим трем критериям, выполняется предпечатная проверка. Это запрос OPTIONS, который делается на сервер до фактического выполнения запроса. Он будет содержать разные Access-Control-XX-XX
заголовки, и сервер должен отвечать на эти заголовки своими собственными заголовками ответа CORS. Вот соответствующие заголовки:
Preflight Request and Response Headers
+-----------------------------------+--------------------------------------+
| REQUEST HEADER | RESPONSE HEADER |
+===================================+======================================+
| Origin | Access-Control-Allow-Origin |
+-----------------------------------+--------------------------------------+
| Access-Control-Request-Headers | Access-Control-Allow-Headers |
+-----------------------------------+--------------------------------------+
| Access-Control-Request-Method | Access-Control-Allow-Methods |
+-----------------------------------+--------------------------------------+
| XHR.withCredentials | Access-Control-Allow-Credentials |
+-----------------------------------+--------------------------------------+
С
Origin
заголовок запроса, значением будет домен исходного сервера и ответAccess-Control-Allow-Header
должен быть либо этот же адрес или*
указать, что все источники разрешены.Если клиент попытается вручную установить заголовки, отсутствующие в приведенном выше списке, браузер установит
Access-Control-Request-Headers
заголовок, со значением, являющимся списком всех заголовков, которые пытается установить клиент. Сервер должен ответить обратно сAccess-Control-Allow-Headers
заголовок ответа, со значением, являющимся списком заголовков, которые он позволяет.Браузер также установит
Access-Control-Request-Method
заголовок запроса, значение которого является HTTP-методом запроса. Сервер должен ответить сAccess-Control-Allow-Methods
заголовок ответа, со значением, являющимся списком методов, которые он допускает.Если клиент использует
XHR.withCredentials
то сервер должен ответитьAccess-Control-Allow-Credentials
заголовок ответа со значениемtrue
, Узнайте больше здесь.
Итак, со всем сказанным, вот лучшая реализация. Несмотря на то, что это лучше, чем приведенная выше реализация, она все же уступает RESTEasy, с которой я связан, поскольку эта реализация по-прежнему допускает все источники. Но этот фильтр лучше выполняет требования спецификации CORS, чем вышеупомянутый фильтр, который просто добавляет заголовки ответа CORS ко всем запросам. Обратите внимание, что вам также может понадобиться изменить Access-Control-Allow-Headers
соответствовать заголовкам, которые разрешит ваше приложение; Вы можете добавить или удалить некоторые заголовки из списка в этом примере.
@Provider
@PreMatching
public class CorsFilter implements ContainerRequestFilter, ContainerResponseFilter {
/**
* Method for ContainerRequestFilter.
*/
@Override
public void filter(ContainerRequestContext request) throws IOException {
// If it's a preflight request, we abort the request with
// a 200 status, and the CORS headers are added in the
// response filter method below.
if (isPreflightRequest(request)) {
request.abortWith(Response.ok().build());
return;
}
}
/**
* A preflight request is an OPTIONS request
* with an Origin header.
*/
private static boolean isPreflightRequest(ContainerRequestContext request) {
return request.getHeaderString("Origin") != null
&& request.getMethod().equalsIgnoreCase("OPTIONS");
}
/**
* Method for ContainerResponseFilter.
*/
@Override
public void filter(ContainerRequestContext request, ContainerResponseContext response)
throws IOException {
// if there is no Origin header, then it is not a
// cross origin request. We don't do anything.
if (request.getHeaderString("Origin") == null) {
return;
}
// If it is a preflight request, then we add all
// the CORS headers here.
if (isPreflightRequest(request)) {
response.getHeaders().add("Access-Control-Allow-Credentials", "true");
response.getHeaders().add("Access-Control-Allow-Methods",
"GET, POST, PUT, DELETE, OPTIONS, HEAD");
response.getHeaders().add("Access-Control-Allow-Headers",
// Whatever other non-standard/safe headers (see list above)
// you want the client to be able to send to the server,
// put it in this list. And remove the ones you don't want.
"X-Requested-With, Authorization, " +
"Accept-Version, Content-MD5, CSRF-Token");
}
// Cross origin requests can be either simple requests
// or preflight request. We need to add this header
// to both type of requests. Only preflight requests
// need the previously added headers.
response.getHeaders().add("Access-Control-Allow-Origin", "*");
}
}
Чтобы узнать больше о CORS, я предлагаю прочитать MDN-документы по обмену ресурсами между источниками (CORS)
Удалить аннотацию "@CrossOriginResourceSharing(allowAllOrigins = true)
"
Затем верните ответ, как показано ниже:
return Response.ok()
.entity(jsonResponse)
.header("Access-Control-Allow-Origin", "*")
.build();
Но jsonResponse
следует заменить на объект POJO!
Другой ответ может быть строго правильным, но вводящим в заблуждение. Недостатком является то, что вы можете смешивать фильтры из разных источников вместе. Даже если Джерси и не может предоставить фильтр CORS (я не проверял этот факт, но я верю другому ответу на этот вопрос), вы можете использовать собственный фильтр CORS от Tomcat.
Я успешно использую его с Джерси. У меня есть собственная реализация фильтра базовой аутентификации, например, вместе с CORS. Лучше всего то, что фильтр CORS настроен в веб-XML, а не в коде.
Пискилле ответ правильный. Но я получаю эту ошибку при обновлении веб-страницы (она работает только при первой загрузке):
The 'Access-Control-Allow-Origin' header contains multiple values '*, *', but only one is allowed. Origin 'http://127.0.0.1:8080' is therefore not allowed access.
Поэтому вместо использования метода add для добавления заголовков для ответа я использовал метод put. Это мой класс
public class MCORSFilter implements ContainerResponseFilter {
public static final String ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin";
public static final String ACCESS_CONTROL_ALLOW_ORIGIN_VALUE = "*";
private static final String ACCESS_CONTROL_ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials";
private static final String ACCESS_CONTROL_ALLOW_CREDENTIALS_VALUE = "true";
public static final String ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers";
public static final String ACCESS_CONTROL_ALLOW_HEADERS_VALUE = "Cache-Control, Pragma, Origin, Authorization, Content-Type, X-Requested-With, Accept";
public static final String ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods";
public static final String ACCESS_CONTROL_ALLOW_METHODS_VALUE = "GET, POST, PUT, DELETE, OPTIONS, HEAD";
public static final String[] ALL_HEADERs = {
ACCESS_CONTROL_ALLOW_ORIGIN,
ACCESS_CONTROL_ALLOW_CREDENTIALS,
ACCESS_CONTROL_ALLOW_HEADERS,
ACCESS_CONTROL_ALLOW_METHODS
};
public static final String[] ALL_HEADER_VALUEs = {
ACCESS_CONTROL_ALLOW_ORIGIN_VALUE,
ACCESS_CONTROL_ALLOW_CREDENTIALS_VALUE,
ACCESS_CONTROL_ALLOW_HEADERS_VALUE,
ACCESS_CONTROL_ALLOW_METHODS_VALUE
};
@Override
public ContainerResponse filter(ContainerRequest request, ContainerResponse response) {
for (int i = 0; i < ALL_HEADERs.length; i++) {
ArrayList<Object> value = new ArrayList<>();
value.add(ALL_HEADER_VALUEs[i]);
response.getHttpHeaders().put(ALL_HEADERs[i], value); //using put method
}
return response;
}
}
И добавьте этот класс в init-param в web.xml
<init-param>
<param-name>com.sun.jersey.spi.container.ContainerResponseFilters</param-name>
<param-value>com.yourpackage.MCORSFilter</param-value>
</init-param>
Чтобы решить эту проблему для моего проекта, я воспользовался ответом Michael и пришел к следующему:
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<executions>
<execution>
<id>run-embedded</id>
<goals>
<goal>run</goal>
</goals>
<phase>pre-integration-test</phase>
<configuration>
<port>${maven.tomcat.port}</port>
<useSeparateTomcatClassLoader>true</useSeparateTomcatClassLoader>
<contextFile>${project.basedir}/tomcat/context.xml</contextFile>
<!--enable CORS for development purposes only. The web.xml file specified is a copy of
the auto generated web.xml with the additional CORS filter added -->
<tomcatWebXml>${maven.tomcat.web-xml.file}</tomcatWebXml>
</configuration>
</execution>
</executions>
</plugin>
Фильтр CORS является основным примером фильтра с сайта tomcat.
Редактировать:
Переменная maven.tomcat.web-xml.file является определенным pom свойством для проекта и содержит путь к файлу web.xml (расположен в моем проекте)
для нескольких исходных доменов:
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.ext.Provider;
@Provider
public class CorsFilter implements ContainerResponseFilter {
@Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) {
final String domain = generateAllowOrigin(requestContext);
responseContext.getHeaders().add("Access-Control-Allow-Origin", domain);
responseContext.getHeaders().add("Access-Control-Allow-Credentials", "true");
responseContext.getHeaders().add("Access-Control-Allow-Headers", "origin, content-type, accept, authorization");
responseContext.getHeaders().add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD");
}
private static String generateAllowOrigin(ContainerRequestContext requestContext) {
String caller = requestContext.getHeaderString("Origin");
final String domain;
if (caller != null && caller.contains("localhost:4141")) {
domain = "http://localhost:4141";
} else {
domain = "https://example.com";
}
return domain;
}
}
Используя JAX-RS, вы можете просто добавить аннотацию @CrossOrigin(origin = yourURL)
на ваш контроллер ресурса. В вашем случае будет @CrossOrigin(origin = "http://localhost:8080")
но вы также можете использовать @CrossOrigin(origin = "*")
разрешить любой запрос, чтобы пройти через ваш веб-сервис.
Вы можете проверить это для получения дополнительной информации.