如何使用 JAX-RS 和 Jersey 处理 CORS

我正在开发一个 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();
}

到目前为止,我得到的错误没有“访问控制-允许-起源”头是在请求的资源。因此,原产地‘ http://localhost:8080’不能进入。」

请协助我。

谢谢 佛陀普尼斯

103139 次浏览

注意: 请务必阅读底部的更新。最初的答案包括 CORS 筛选器的“惰性”实现

对于 Jersey,要处理 CORS,只需使用 ContainerResponseFilter。Jersey 1. x 和2. x 的 ContainerResponseFilter略有不同。既然你还没有提到你使用的是哪个版本,我就把它们都贴出来。确保你用的是正确的。

泽西2. x

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",
"CSRF-Token, X-Requested-By, Authorization, Content-Type");
response.getHeaders().add("Access-Control-Allow-Credentials", "true");
response.getHeaders().add("Access-Control-Allow-Methods",
"GET, POST, PUT, DELETE, OPTIONS, HEAD");
}
}

如果您使用包扫描来发现提供程序和资源,那么 @Provider注释应该负责您的配置。如果没有,那么您将需要显式地将它注册到 ResourceConfigApplication子类中。

使用 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.x

import com.sun.jersey.spi.container.ContainerRequest;
import com.sun.jersey.spi.container.ContainerResponse;
import com.sun.jersey.spi.container.ContainerResponseFilter;


@Provider
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",
"CSRF-Token, X-Requested-By, Authorization, Content-Type");
response.getHttpHeaders().add("Access-Control-Allow-Credentials", "true");
response.getHttpHeaders().add("Access-Control-Allow-Methods",
"GET, POST, PUT, DELETE, OPTIONS, HEAD");


return response;
}
}

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 过滤器,您可以查看 静止 CorsFilter的源代码


更新

所以我决定添加一个更正确的实现。上面的实现是惰性的,它将所有 CORS 头添加到所有请求中。另一个错误是,因为它只是一个 回应过滤器,所以请求仍然处于处理状态。这意味着当飞行前请求(OPTION 请求)到来时,将不会实现 OPTION 方法,因此我们将得到一个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

如果请求不满足所有这三个条件,则发出一个 PreFlight 请求。这是发送给服务器的 OPTION 请求,发送给实际请求的 副院长请求。它将包含不同的 Access-Control-XX-XX报头,服务器应该使用自己的 CORS 响应报头来响应这些报头。下面是匹配的标题:

请求头 响应头
起源 访问-控制-允许-起源
访问控制请求标头 访问控制允许标题
访问-控制-请求-方法 访问-控制-允许-方法
带证件的 XHR 访问-控制-允许-凭据
  • 对于 Origin请求头,值将是原始服务器域,响应 Access-Control-Allow-Origin应该是这个相同的地址或 *,以指定允许所有起源。

  • 如果客户端试图手动设置上面列表中没有的任何头,那么浏览器将设置 Access-Control-Request-Headers头,其值是客户端试图设置的所有头的列表。服务器应该返回一个 Access-Control-Allow-Headers响应头,值是它允许的头列表。

  • 浏览器还将设置 Access-Control-Request-Method请求标头,其值为请求的 HTTP 方法。服务器应该使用 Access-Control-Allow-Methods响应头进行响应,其值是它允许的方法的列表。

  • 如果客户机使用 XHR.withCredentials,那么服务器应该使用值为 trueAccess-Control-Allow-Credentials响应头进行响应。你可在此浏览更多资料.

综上所述,这里有一个更好的实现。即使这是 好多了比上述实现,它仍然劣于 休息一分我链接,因为这个实现仍然允许所有的来源。但是这个过滤器在遵守 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, Content-Type");
}


// 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,我建议阅读 跨来源资源共享(CORS)上的 MDN 文档

另一个答案可能是严格正确的,但具有误导性。缺少的部分是,您可以将来自不同来源的过滤器混合在一起。即使 Jersey 可能不提供 CORS 过滤器(我没有检查过这个事实,但我相信另一个答案) ,您也可以使用 公猫自己的 CORS 过滤器

我在新泽西用得很成功。例如,我有自己的 Basic Authentication filter 实现,以及 CORS。最好的是,CORS 过滤器是在 Web XML 中配置的,而不是在代码中。

为了解决这个问题,我使用了 Micheal 的的答案,得出了以下结论:

    <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 过滤器是来自 公猫网站。的基本示例过滤器

编辑 :
Web-xml. file变量是项目的 pom 定义属性,它包含 web.xml 文件的路径(位于我的项目中)

Peeskillet 的答案是正确的。但是我在刷新网页的时候得到了这个错误(它只能在第一次加载的时候工作) :

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

并将此类添加到 web.xml 中的 init-param

<init-param>
<param-name>com.sun.jersey.spi.container.ContainerResponseFilters</param-name>
<param-value>com.yourpackage.MCORSFilter</param-value>
</init-param>

删除注释“ @CrossOriginResourceSharing(allowAllOrigins = true)

然后回复如下:

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

但是 jsonResponse应该替换为 POJO 对象!