如何在JAX-RS Web服务中启用跨域请求?

80

我开发了一组RESTful Web服务。 由于错误 No 'Access-Control-Allow-Origin' header is present on the requested resource.,我不能从远程客户端调用这些方法。

这些服务在本地主机上运行得很完美。是否有任何服务器端的更改或配置可以解决此问题,例如启用跨域请求?

我正在使用WildFly 8,JavaEE 7。


2
您可以在此处了解有关CORS的更多信息:http://www.html5rocks.com/en/tutorials/cors/ 您可以在此处了解如何向JAX-RS添加CORS支持:http://cxf.apache.org/docs/jax-rs-cors.html - monsur
这很有用,但我正在寻找一种不需要修改客户端代码的解决方案。 - Naili
1
您的问题要求对服务器端进行更改。CORS 的大部分更改都在服务器端完成。客户端只需要进行最小的更改,如果有的话。 - monsur
10个回答

119

我也想知道同样的问题,所以经过一番研究,我发现最简单的方法就是使用JAX-RS ContainerResponseFilter 来添加相关的CORS头部。这样做就不需要用CXF替换整个Web服务堆栈(Wildfly在某种形式上使用CXF,但看起来它仅仅用于JAX-WS而非JAX-RS)。

无论如何,如果您使用此过滤器,则会将标头添加到每个REST Web服务中。

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运行时使用该过滤器,没有这个注解就不会发生任何事情。

我从Jersey示例中学习到了使用ContainerResponseFilter的方法。


8
是的,我使用了你在这里发布的相同解决方案。但我认为允许每个跨域连接可能会造成安全问题。 - Naili
1
是的,如果您的REST Web服务使用cookie来保护它们,那么这是正确的。但是,如果您只是尝试创建一些公共REST Web服务,那么就没有安全问题了。您在问题中没有提到是否只需要一个域(例如services.yourdomain)工作,还是每个域都需要。 - Joel Pearson
1
@Joel Pearson 同意。我是通过谷歌搜索“在jax-rs中启用CORS”找到这个问题的。只是想对其他类似情况的人有所帮助。尽管如此,被接受的答案似乎没有提供任何特定于wildfly或jee7的建议。也许我错过了什么?这是在jetty+jersey上为我工作的解决方案,感谢您的发布。 - Ashok
2
"Access-Control-Allow-Credentials: true" 这一行与 "Access-Control-Allow-Origin", "*" 有冲突,因为这可能会导致安全问题,或者(至少在 Chrome 中)被拒绝。如果您想允许凭据,则需要指定特定的来源,而不是通配符。 - Stijn de Witt
1
首先获取请求来源:String origin = requestContext.getHeaderString("origin");,然后(当 origin !== null 时),使用该来源代替通配符:cres.getHeaders().add("Access-Control-Allow-Origin", origin);。如果您的 API 使用 cookie 进行身份验证(例如在使用容器管理的身份验证时),请在设置 CORS 标头之前将来源与允许的来源白名单进行比较。 - Stijn de Witt
显示剩余13条评论

22

我遇到了类似的问题,并尝试使用 @Alex Petty 的解决方案,但除了需要在我的类中设置每个 JAX-RS 终端的 CORS 标头之外,还要注意:

@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端点,该端点将为类中任何其他OPTIONS请求返回CORS标头,从而捕获以下所有类型的端点:

@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();
}

只有在这样做之后,我才能够正确地使用我的JAX-RS API端点来自其他域或主机的jQuery Ajax客户端。


我遇到了一个奇怪的问题。在Google Chrome和Mozilla中,由我设置的响应头(.header("SOME_HEADER","SOME_HEADER"))无法在Swagger UI中呈现,但在IE 11中可以。我已经检查过这个响应头是否传递到浏览器(在开发者工具的控制台响应头中),但是在Swagger UI中却没有呈现出来。 - Debaprasad Jana
那不正确。您必须使用过滤器。当您想基于特定条件过滤和/或修改请求时,请使用过滤器。当您想要控制、预处理和/或后处理请求时,请使用Servlet。http://docs.oracle.com/javaee/6/tutorial/doc/bnagb.html - max_dev
我刚刚添加了这个 **.header("Access-Control-Allow-Origin", "*")**,然后它就起作用了。 - Pini Cheyni

13
我发现一种更简单的(仅适用于RestEasy)方法,在不使用过滤器的情况下在Wildfly上启用CORS,并且您可以在资源级别控制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();
}

谢谢。我无法获得 'javax.ws.rs.container' 类(为什么?是因为其他 JAX-RS 版本?),你的解决方案完美地解决了这个问题。 - Guildenstern70
这种方法只适用于GET请求,当尝试使用客户端的POST请求时会出现相同的错误,我仍在努力找出如何解决这个问题,因为我的“起源”将随时变化。 - Francisco Souza
1
这永远不会起作用,它是错误的。CORS 会在调用 "GET"、"POST" 或其他方法之前先调用 HTTP 方法 “OPTIONS”,头文件条目必须在 "OPTIONS" 方法中定义。如果没有定义,浏览器将不予接受。HTTP 请求过滤是实现它的最佳方式。还有一件事,如果您使用像 Apache 这样的严肃的 HTTP 服务器,条目 “Access-Control-Allow-Origin”, "*" 过于稀疏,浏览器将不允许它。您必须明确添加授权 DNS,例如 "Access-Control-Allow-Origin: my.domain.com"。 - jbrios777
但是如果你有数百个方法...更好的方法是使用过滤器。 - ACV
@Paul Samsotha已经给出了一个完整的、带有适当解释的预飞请求答案,这是链接-https://dev59.com/qF4c5IYBdhLWcg3wUI0N#28067653 - Mahesh

11

我在Wildfly上配置跨域资源共享(CORS) API时,使用了这个库,运气不错:

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

安装非常简单。只需将上述依赖项添加到您的pom中,然后将以下配置添加到web.xml文件的webapp部分即可。

<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>

如果你更喜欢的话,也可以使用属性文件来配置它。这个库运行良好,提供了很多配置灵活性!


我想尝试这个,但是我的日志控制台上出现了这个错误:The 'Access-Control-Allow-Origin' header contains multiple values 'http://127.0.0.1, *', but only one is allowed. 有什么办法可以更改web.xml使其正确吗? - Ayyoub
2
我尝试过这个,但是看不到它添加到头部。 - Jay

9

其他答案对我都无效,但这个有效:

import javax.ws.rs.core.Response;

然后将服务方法的返回类型更改为Response,并将return语句更改为:

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

其中resp是原始响应对象。


这仅在请求简单时有效。也就是说,如果请求为GET或POST并且请求标头简单(仅简单标头为Accept、Accept-Language、Content-Language、Content-Type = application/x-www-form-urlencoded、multipart/form-data、text/plain)。否则,浏览器将在实际请求之前发送OPTIONS请求。 - Ranuka

4
您可以按照以下方式实现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;
 }

}

3

解决方案是在响应中添加一些头信息。在Spring或Spring Boot中有注释,但在旧系统中可能没有注释。您可以通过添加过滤器来解决。

过滤器类:

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>

1

仅仅是为了补充其他回答的内容。 允许使用 * 是有一定危险的。 可以做的是配置一个允许来源的数据库(可以是文件)

然后当请求到达时,您可以执行以下操作:

// 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");
}

以这种方式,你不会允许每个人。

0
我最近遇到了这个问题并解决了它,基本上创建一个实现ContainerResponseFilter的过滤器,并添加以下CORS标头即可:
@Override
    public void filter(ContainerRequestContext containerRequestContext, ContainerResponseContext containerResponseContext) throws IOException {
        containerResponseContext.getHeaders().add(
                "Access-Control-Allow-Origin", "*");
        containerResponseContext.getHeaders().add(
                "Access-Control-Allow-Credentials", "true");
        containerResponseContext.getHeaders().add(
                "Access-Control-Allow-Headers",
                "origin, content-type, accept, authorization");
        containerResponseContext.getHeaders().add(
                "Access-Control-Allow-Methods",
                "GET, POST, PUT, DELETE, OPTIONS, HEAD");
    }

由于您正在使用JAX-RS,因此您的应用程序类扩展了Applicaton类,您只需要覆盖其getSingletons方法:

@Override
public Set<Object> getSingletons() {
        return mySingletons();
    }

你的单例模式可以如下:

public static Set<Object> mySingletons() {
        Set<Object> singletons = new HashSet<>();
        singletons.add(new MyFilter());
        return singletons;
    }

我尝试了上面的答案并在web.xml等方面进行了更改,但它们都没有起作用,所以我想出了这个解决方案,尝试了所有可能的方法。


0

@Joel Pearson的答案很有帮助,但对于像我这样对JAX-RS不熟悉且通过配置web.xml在Tomcat上运行服务的人,在创建类和将其放置在项目中的任何位置时都应该小心。查看您在下指定的Jersey包,并在其中创建此过滤器类。这样我就解决了问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接