Как включить междоменные запросы в веб-сервисах JAX-RS?

Я разработал набор успокаивающих веб-сервисов. Я не мог вызвать ни один из этих методов с удаленных клиентов из-за ошибки No 'Access-Control-Allow-Origin' header is present on the requested resource.

Сервисы отлично работают на локальном хосте. Есть ли какие-либо изменения или настройки на серверной стороне для решения проблемы. т. е. включить междоменные запросы.

Я использую WildFly 8, JavaEE 7.


person Naili    schedule 03.05.2014    source источник
comment
Подробнее о CORS можно узнать здесь: html5rocks.com/en/tutorials/cors. Вы можете узнать, как добавить поддержку CORS в JAX-RS, здесь: cxf.apache. org/docs/jax-rs-cors.html   -  person monsur    schedule 04.05.2014
comment
Это полезно, но я искал решение без изменения кода клиента.   -  person Naili    schedule 04.05.2014
comment
Ваш вопрос требует изменений на стороне сервера. Большинство изменений для CORS выполняются на стороне сервера. В клиенте необходимы лишь минимальные изменения, если таковые имеются.   -  person monsur    schedule 04.05.2014


Ответы (9)


Мне было интересно то же самое, поэтому после небольшого исследования я обнаружил, что самым простым способом было просто использовать JAX-RS ContainerResponseFilter для добавления соответствующих заголовков CORS. Таким образом, вам не нужно заменять весь стек веб-сервисов на CXF (Wildfly использует CXF в какой-то форме, но не похоже, что он использует его для JAX-RS, возможно, только для JAX-WS).

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

package com.yourdomain.package;

import java.io.IOException;

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(final ContainerRequestContext requestContext,
                      final ContainerResponseContext cres) throws IOException {
      cres.getHeaders().add("Access-Control-Allow-Origin", "*");
      cres.getHeaders().add("Access-Control-Allow-Headers", "origin, content-type, accept, authorization");
      cres.getHeaders().add("Access-Control-Allow-Credentials", "true");
      cres.getHeaders().add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD");
      cres.getHeaders().add("Access-Control-Max-Age", "1209600");
   }

}

Затем, когда я тестировал curl, в ответе были заголовки CORS:

$ curl -D - "http://localhost:8080/rest/test"
HTTP/1.1 200 OK
X-Powered-By: Undertow 1
Access-Control-Allow-Headers: origin, content-type, accept, authorization
Server: Wildfly 8
Date: Tue, 13 May 2014 12:30:00 GMT
Connection: keep-alive
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Transfer-Encoding: chunked
Content-Type: application/json
Access-Control-Max-Age: 1209600
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS, HEAD

Насколько я понимаю, это аннотация @Provider, которая указывает среде выполнения JAX-RS использовать фильтр, без аннотации ничего не происходит.

Я получил идею об использовании ContainerResponseFilter из Пример футболки.

person Joel Pearson    schedule 13.05.2014
comment
да, я использовал то же решение, которое вы разместили здесь. Но я считаю, что мы создаем проблему безопасности, разрешая каждое междоменное соединение. - person Naili; 13.05.2014
comment
Да, это правда, если ваши веб-службы REST используют файлы cookie для их защиты. Но если вы просто пытаетесь создать некоторые общедоступные веб-службы REST, тогда проблем с безопасностью нет. В своем вопросе вы не упомянули, нужен ли вам только 1 домен для работы, например, services.yourdomain или каждый домен. - person Joel Pearson; 14.05.2014
comment
Если вы планируете добавить только конкретный хост вместо *, не забудьте также добавить порт - person ACV; 08.04.2015
comment
@JoelPearson Не могли бы вы рассказать, как настроить этот фильтр? Должен ли я сделать это в web.xml? - person Jay; 09.09.2015
comment
@Jay, он использует аннотацию @ Provider, поэтому, если у вас включен CDI для вашего уха / войны, дополнительная настройка не требуется. - person Joel Pearson; 11.09.2015
comment
+1 за аннотацию @Provider. Я тоже смотрел примеры в Интернете, но почему-то пропустил эту важную часть! - person bradimus; 02.10.2015
comment
Я запускаю службы на причале с контейнером сервлетов из трикотажа. В этом случае одного только @Provider было недостаточно. Класс CORSFilter должен был быть зарегистрирован как параметр инициализации сервлета трикотажа следующим образом: jerseyServlet.setInitParameter( "jersey.config.server.provider.classnames", MyService.class.getCanonicalName() + ", org.glassfish.jersey.media.multipart.MultiPartFeature, " + CORSFilter.class.getCanonicalName()) - person Ashok; 01.12.2015
comment
@Ashok, этот вопрос был о Wildfly 8 и JEE 7, а не о причале / майке, поэтому, если у вас есть другие вопросы, вам следует создать другой вопрос, так как это решение не будет иметь отношения к вам. - person Joel Pearson; 02.12.2015
comment
@ Джоэл Пирсон Согласен. Я столкнулся с этим вопросом через поиск в Google для включения CORS в jax-rs. Просто подумал, что это может быть полезно другим, кто попадает на эту страницу аналогичным образом. Тем не менее, принятый ответ, похоже, не предлагает ничего конкретного для wildfly или jee7. Может я ссылку потерял? Это было решение, которое сработало для меня на причале + джерси, и спасибо за публикацию. - person Ashok; 02.12.2015
comment
@Ashok правда, вероятно, это не JEE 7, похоже, что аннотация Provider была доступна в JEE 6: docs.oracle.com/javaee/6/api/index.html?javax/ws/rs/ext/. тот факт, что вам нужен CDI, чтобы он работал как есть. Но вполне возможно, что он будет работать в любом контейнере JEE 6, поскольку он не ссылается напрямую на API-интерфейсы Wildfly. Интересно, что вы все еще можете заставить это работать в JAX-RS вне JEE. Интересная информация, спасибо. - person Joel Pearson; 03.12.2015
comment
Access-Control-Allow-Credentials: true эта строка на самом деле конфликтует с "Access-Control-Allow-Origin", "*", поскольку это либо проблема безопасности, либо (по крайней мере, в Chrome) просто отказано. Если вы хотите разрешить учетные данные, вам нужно указать конкретное происхождение, а не подстановочный знак. - person Stijn de Witt; 02.02.2016
comment
Сначала получите источник запроса: String origin = requestContext.getHeaderString("origin");, затем (когда origin !== null) используйте этот источник как подстановочный знак: cres.getHeaders().add("Access-Control-Allow-Origin", origin);. Если ваш API использует файлы cookie для аутентификации (например, при использовании аутентификации, управляемой контейнером), перед настройкой заголовков CORS проверьте источник по белому списку разрешенных источников. - person Stijn de Witt; 15.02.2016
comment
Большое спасибо, ребята, тем, кто задается вопросом, когда поместить этот код, вам просто нужен этот код в вашем пакете org.yourdomain.rest, если вы используете wildfly 10. - person netto; 17.10.2016
comment
У нас была Проблема, что провайдер не был зарегистрирован по умолчанию, и помог следующий вопрос: stackoverflow.com/questions/32328012/ - person boutta; 23.11.2017
comment
@Naili, проблема безопасности возникает только в том случае, если ваше приложение ограничивает доступ на основе IP-номера (что является очень плохой практикой), только в этом случае вредоносный сайт может получить доступ к данным, которые он не может получить иначе. во всех других случаях, поскольку учетные данные не разрешены (как отмечает Стейн де Витт), проблем с безопасностью нет. - person Reto Gmür; 27.02.2018
comment
Это сработало для меня при развертывании против JBoss EAP 7.1, только если я явно добавил экземпляр CORSFilter в синглтоны JaxRsApplication. @Provider сам по себе мне не помог. - person Marcus Junius Brutus; 29.01.2020
comment
Могу ли я удалить/отключить заголовки ответов по умолчанию, такие как Server: Wildfly 8? - person Ahmad Nadeem; 11.05.2020
comment
Этого недостаточно. Если есть проверка подлинности, вы также должны убедиться, что она не применяется к запросам OPTIONS, предпочтительно путем добавления обработчика @OPTIONS @Path("{path : .*}") для каждого nemesisfixx ответа. - person Jan Hudec; 14.08.2020

Я столкнулся с аналогичной проблемой и попытался использовать решение @Alex Petty, но помимо установки заголовков CORS на каждой конечной точке JAX-RS в моем классе, как таковой:

@GET
@Produces(MediaType.APPLICATION_JSON)
public Response getMemberList() {
    List<Member> memberList = memberDao.listMembers();
    members.addAll(memberList);
    return Response
            .status(200)
            .header("Access-Control-Allow-Origin", "*")
            .header("Access-Control-Allow-Headers", "origin, content-type, accept, authorization")
            .header("Access-Control-Allow-Credentials", "true")
            .header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD")
            .header("Access-Control-Max-Age", "1209600")
            .entity(memberList)
            .build();
}

Мне пришлось дополнительно определить универсальную конечную точку OPTIONS, которая возвращала бы заголовки CORS для любого другого запроса OPTIONS в классе и, таким образом, перехватывала бы все конечные точки сортировки:

@OPTIONS
@Path("{path : .*}")
public Response options() {
    return Response.ok("")
            .header("Access-Control-Allow-Origin", "*")
            .header("Access-Control-Allow-Headers", "origin, content-type, accept, authorization")
            .header("Access-Control-Allow-Credentials", "true")
            .header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD")
            .header("Access-Control-Max-Age", "1209600")
            .build();
}

Только после этого я смогу правильно использовать свои конечные точки API JAX-RS от клиентов Jquery Ajax на других доменах или хостах.

person nemesisfixx    schedule 11.03.2015
comment
У меня странная проблема. Заголовок ответа (.header(SOME_HEADER,SOME_HEADER), установленный мной, не отображается в пользовательском интерфейсе swagger в Google Chrome, Mozilla, но в IE 11. Я проверил, что этот заголовок ответа поступает в браузер (в заголовке ответа консоли инструмента разработчика ), но то же самое не отображается в пользовательском интерфейсе Swagger. - person Debaprasad Jana; 28.12.2015
comment
Это неправильно. Вы должны использовать фильтры. Используйте фильтр, если вы хотите отфильтровать и/или изменить запросы на основе определенных условий. Используйте сервлет, если вы хотите контролировать, предварительно и/или постобрабатывать запросы. docs.oracle.com/javaee/6/tutorial/doc/bnagb. html - person max_dev; 11.06.2016
comment
Я только что добавил этот .header(Access-Control-Allow-Origin, *), и это сработало - person Pini Cheyni; 06.05.2017

Я нашел еще более простой (специфичный для RestEasy) способ включить CORS в Wildfly без использования фильтра и где вы можете управлять конфигурацией заголовка ответа API на уровне ресурсов.

Например:

@GET
@Produces(MediaType.APPLICATION_JSON)
public Response getMemberList() {
    List<Member> memberList = memberDao.listMembers();
    members.addAll(memberList);
    return Response
            .status(200)
            .header("Access-Control-Allow-Origin", "*")
            .header("Access-Control-Allow-Headers", "origin, content-type, accept, authorization")
            .header("Access-Control-Allow-Credentials", "true")
            .header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD")
            .header("Access-Control-Max-Age", "1209600")
            .entity(memberList)
            .build();
}
person Alex Petty    schedule 08.08.2014
comment
Спасибо. Мне не удалось получить классы 'javax.ws.rs.container' (почему? другая версия JAX-RS?), и ваше решение сработало просто отлично. - person Guildenstern70; 12.09.2014
comment
Этот способ работает только для GET, при попытке POST (от клиента) возникает та же ошибка, я все еще пытаюсь выяснить, как решить эту проблему, потому что мой источник будет все время меняться. - person Francisco Souza; 10.09.2015
comment
Это никогда не сработает, это неправильно. CORS сначала вызывает метод HTTP OPTIONS, прежде чем вызывать GET, POST или что-то еще. Записи заголовка должны быть определены в методе OPTIONS. Если это не так, браузер не примет его. Фильтрация HTTP-запросов — лучший способ сделать это. Еще одна вещь, если вы используете серьезный HTTP-сервер, такой как Apache, запись Access-Control-Allow-Origin, * должна быть разреженной, и браузер не позволит этого. Вы должны специально добавить авторизующий DNS, например. Access-Control-Allow-Origin: my.domain.com. - person jbrios777; 10.08.2016
comment
Но если у вас есть сотни методов... лучшим подходом было бы использование фильтра - person ACV; 30.08.2016
comment
Полный ответ с надлежащим объяснением предполетных запросов был дан @Paul Samsotha, и вот ссылка - stackoverflow.com/a/ 28067653/3257644 - person Mahesh; 17.02.2020

Мне повезло настроить совместное использование ресурсов между источниками (CORS) для моего API (на Wildfly) с помощью этой библиотеки:

<dependency>
<groupId>com.thetransactioncompany</groupId>
<artifactId>cors-filter</artifactId>
<version>2.1</version>
</dependency>

Это очень легко настроить. Просто добавьте указанную выше зависимость в свой pom, а затем добавьте следующую конфигурацию в раздел webapp вашего файла web.xml.

<filter>
    <filter-name>CORS</filter-name>
    <filter-class>com.thetransactioncompany.cors.CORSFilter</filter-class>

    <init-param>
        <param-name>cors.allowGenericHttpRequests</param-name>
        <param-value>true</param-value>
    </init-param>

    <init-param>
        <param-name>cors.allowOrigin</param-name>
        <param-value>*</param-value>
    </init-param>

    <init-param>
        <param-name>cors.allowSubdomains</param-name>
        <param-value>false</param-value>
    </init-param>

    <init-param>
        <param-name>cors.supportedMethods</param-name>
        <param-value>GET, HEAD, POST, DELETE, OPTIONS</param-value>
    </init-param>

    <init-param>
        <param-name>cors.supportedHeaders</param-name>
        <param-value>*</param-value>
    </init-param>

    <init-param>
        <param-name>cors.supportsCredentials</param-name>
        <param-value>true</param-value>
    </init-param>

    <init-param>
        <param-name>cors.maxAge</param-name>
        <param-value>3600</param-value>
    </init-param>

</filter>

<filter-mapping>
    <!-- CORS Filter mapping -->
    <filter-name>CORS</filter-name>
    <url-pattern>*</url-pattern>
</filter-mapping>

Вы также можете настроить его с помощью файла свойств, если хотите. Эта библиотека работает как шарм и дает вам большую гибкость конфигурации!

person Alex Petty    schedule 27.06.2014
comment
Я хотел попробовать это, но у меня есть эта ошибка в моей консоли журнала: The 'Access-Control-Allow-Origin' header contains multiple values 'http://127.0.0.1, *', but only one is allowed. Идея, что нужно изменить в файле web.xml, чтобы сделать его правильным? - person Ayyoub; 22.07.2015
comment
Я пробовал это, но не вижу, чтобы это было добавлено в заголовок. - person Jay; 09.09.2015

Ни один из других ответов не работал у меня, но это сработало:

import javax.ws.rs.core.Response;

Затем измените тип возвращаемого значения метода службы на Response и измените оператор return на:

return Response.ok(resp).header("Access-Control-Allow-Origin", "*").build();

Где resp — исходный объект ответа.

person Alex R    schedule 18.07.2015
comment
Это работает только тогда, когда это простой запрос. То есть, если запрос GET или POST, а заголовки запроса простые (единственные простые заголовки: Accept, Accept-Language, Content-Language, Content-Type= application/x-www-form-urlencoded, multipart/form-data, текст/обычный). В противном случае браузер отправит запрос OPTIONS до фактического запроса. - person Ranuka; 03.12.2016

Вы также можете реализовать javax.ws.rs.core.Feature, как показано ниже, для реализации CORS.

import javax.ws.rs.core.Feature;
import javax.ws.rs.core.FeatureContext;
import javax.ws.rs.ext.Provider;
import org.jboss.resteasy.plugins.interceptors.CorsFilter;

@Provider
 public class CorsFeature implements Feature {

  @Override
  public boolean configure(FeatureContext context) {
    CorsFilter corsFilter = new CorsFilter();
    corsFilter.getAllowedOrigins().add("*");
    context.register(corsFilter);
    return true;
 }

}
person Ranuka    schedule 06.12.2016

Решение для этого добавляет некоторый заголовок в ответ. В весенней или весенней загрузке есть аннотации, но в более старой системе аннотаций может и не быть. Вы можете решить, добавив фильтр.

Класс фильтра:

package com.koushik.restapis.intercepter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;


public class RestCorsFilter implements Filter {

    @Override
    public void destroy() {
    enter code here
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
    
        HttpServletResponse resp = (HttpServletResponse) response;
        resp.addHeader("Access-Control-Allow-Origin", "*");
        resp.addHeader("Access-Control-Allow-Headers", "*");
        resp.addHeader("Access-Control-Allow-Methods", "*");
        
        chain.doFilter(request, resp);
        
    }

    @Override
    public void init(FilterConfig arg0) throws ServletException {
        
    }

}

web.xml

  <filter>
    <filter-name>RestCorsFilter</filter-name>
    <filter-class>com.koushik.restapis.RestCorsFilter</filter-class>
  </filter>
    <filter-mapping>
    <filter-name>RestCorsFilter</filter-name>
    <url-pattern>/apis/*</url-pattern>
  </filter-mapping>
person 404Koushik    schedule 05.09.2020

Ответ @Joel Pearson помог, но для тех, кто плохо знаком с JAX-RS, таких как я, и запускает службу на tomcat путем настройки web.xml, следует быть осторожным при создании класса и размещении его в любом месте проекта. См. указанный вами пакет для джерси и создайте там этот класс фильтра. Таким образом, это сработало для меня.

person Novice_JS    schedule 14.09.2018

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

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

// this will return you the origin 
String[] referers = requestContext.getHeaders().get("referer")

// then search in your DB if the origin is allowed
if (referers != null && referers.lenght == 1 && isAllowedOriging(referers[0])) {
        containerResponseContext.getHeaders().add("Access-Control-Allow-Origin", referers[0]);
        containerResponseContext.getHeaders().add("Access-Control-Allow-Headers", "origin, content-type, accept, authorization, <HERE PUT YOUR DEDICATED HEADERS>);
        containerResponseContext.getHeaders().add("Access-Control-Allow-Credentials", "true");
        containerResponseContext.getHeaders().add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD");
        containerResponseContext.getHeaders().add("Access-Control-Max-Age", "1209600");
}

Таким образом, вы не позволите всем.

person Geoffrey    schedule 05.06.2017