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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/28065963/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 05:33:29  来源:igfitidea点击:

How to handle CORS using JAX-RS with Jersey

javarestjerseyjax-rscors

提问by user2773716

I'm developing a java script client application, in server-side I need to handle CORS, all the services I had written in JAX-RS with JERSEY. My code:

我正在开发一个 java 脚本客户端应用程序,在服务器端我需要处理 CORS,我用 JERSEY 在 JAX-RS 中编写的所有服务。我的代码:

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

As of now, i'm getting error No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8080' is therefore not allowed access.”

截至目前,我收到错误请求的资源上不存在“Access-Control-Allow-Origin”标头。因此,不允许访问Origin ' http://localhost:8080'。”

Please assist me with this.

请帮我解决这个问题。

Thanks & Regards Buddha Puneeth

感谢和问候 佛陀普尼

回答by Paul Samsotha

Note: Make sure to read the UPDATE at the bottom

注意:请务必阅读底部的更新

@CrossOriginResourceSharingis a CXF annotation, so it won't work with Jersey.

@CrossOriginResourceSharing是 CXF 注释,因此它不适用于 Jersey。

With Jersey, to handle CORS, I normally just use a ContainerResponseFilter. The ContainerResponseFilterfor Jersey 1 and 2 are a bit different. Since you haven't mentioned which version you're using, I'll post both.

对于 Jersey,为了处理 CORS,我通常只使用ContainerResponseFilter. 所述ContainerResponseFilter用于泽西1和2是有点不同。由于您还没有提到您使用的是哪个版本,我将同时发布两个版本。

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

If you use package scanning to discover providers and resources, the @Providerannotation should take care of the configuration for you. If not, then you will need to explicitly register it with the ResourceConfigor the Applicationsubclass.

如果您使用包扫描来发现提供者和资源,则@Provider注释应该为您处理配置。如果没有,那么您将需要向ResourceConfigApplication子类显式注册它。

Sample code to explicitly register filter with the ResourceConfig:

显式注册过滤器的示例代码ResourceConfig

final ResourceConfig resourceConfig = new ResourceConfig();
resourceConfig.register(new CORSFilter());
final final URI uri = ...;
final HttpServer httpServer = GrizzlyHttpServerFactory.createHttpServer(uri, resourceConfig);

For Jersey 2.x, if you are having problems registering this filter, here are a couple resources that might help

对于 Jersey 2.x,如果您在注册此过滤器时遇到问题,这里有一些资源可能会有所帮助

Jersey 1

泽西岛 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 configuration, you can use

web.xml 配置,可以使用

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

Or ResourceConfigyou can do

或者ResourceConfig你可以做

resourceConfig.getContainerResponseFilters().add(new CORSFilter());

Or package scanning with the @Providerannotation.

或者带@Provider注释的包扫描。



EDIT

编辑

Please note that the above example can be improved. You will need to know more about how CORS works. Please see here. For one, you will get the headers for all responses. This may not be desirable. You may just need to handle the preflight (or OPTIONS). If you want to see a better implemented CORS filter, you can check out the source code for the RESTeasy CorsFilter

请注意,上面的例子可以改进。您将需要更多地了解 CORS 的工作原理。请看这里。一方面,您将获得所有响应的标题。这可能是不可取的。您可能只需要处理预检(或选项)。如果你想看到一个更好实现的 CORS 过滤器,你可以查看RESTeasy的源代码CorsFilter



UPDATE

更新

So I decided to add a more correct implementation. The above implementation is lazy and adds all the CORS headers to all requests. The other mistake is that being that it is only a responsefilter, the request is still processes. This means that when the preflight request comes in, which is an OPTIONS request, there will be no OPTIONS method implemented, so we will get a 405 response, which is incorrect.

所以我决定添加一个更正确的实现。上面的实现是惰性的,并将所有 CORS 标头添加到所有请求中。另一个错误是它只是一个响应过滤器,请求仍在处理。这意味着当 preflight 请求进来时,它是一个 OPTIONS 请求,不会有 OPTIONS 方法实现,所以我们会得到 405 响应,这是不正确的。

Here's how it shouldwork. So there are two types of CORS requests: simple requests and preflight requests. For a simple request, the browser will send the actual request and add the Originrequest header. The browser expects for the response to have the Access-Control-Allow-Originheader, saying that the origin from the Originheader is allowed. In order for it to be considered a "simple request", it must meet the following criteria:

这是它应该如何工作。所以有两种类型的 CORS 请求:简单请求和预检请求。对于简单的请求,浏览器将发送实际请求并添加Origin请求头。浏览器期望响应具有Access-Control-Allow-Origin标头,表示Origin允许来自标头的来源。为了使其被视为“简单请求”,它必须满足以下标准:

  • Be one of the following method:
    • GET
    • HEAD
    • POST
  • Apart from headers automatically set by the browser, the request may only contain the following manuallyset headers:
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type
    • DPR
    • Save-Data
    • Viewport-Width
    • Width
  • The only allowed values for the Content-Typeheader are:
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain
  • 是以下方法之一:
    • 得到
    • 邮政
  • 除了浏览器自动设置的标头外,请求可能只包含以下手动设置的标头:
    • 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

If the request doesn't meet all of these three criteria, a Preflight request is made. This is an OPTIONS request that is made to the server, priorto the actual request being made. It will contain different Access-Control-XX-XXheaders, and the server should respond to those headers with its own CORS response headers. Here are the matching headers:

如果请求不满足所有这三个标准,则会发出预检请求。这是在发出实际请求之前向服务器发出的 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    |
+-----------------------------------+--------------------------------------+
  • With the Originrequest header, the value will be the origin server domain, and the response Access-Control-Allow-Headershould be either this same address or *to specify that all origins are allowed.

  • If the client tries to manually set any headers not in the above list, then the browser will set the Access-Control-Request-Headersheader, with the value being a list of all the headers the client is trying to set. The server should respond back with a Access-Control-Allow-Headersresponse header, with the value being a list of headers it allows.

  • The browser will also set the Access-Control-Request-Methodrequest header, with the value being the HTTP method of the request. The server should respond with the Access-Control-Allow-Methodsresponse header, with the value being a list of the methods it allows.

  • If the client uses the XHR.withCredentials, then the server should respond with the Access-Control-Allow-Credentialsresponse header, with a value of true. Read more here.

  • 对于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在这里阅读更多

So with all that said, here is a better implementation. Even though this is betterthan the above implementation, it is still inferior to the RESTEasy oneI linked to, as this implementation still allows all origins. But this filter does a better job of adhering to the CORS spec than the above filter which just adds the CORS response headers to all request. Note that you may also need to modify the Access-Control-Allow-Headersto match the headers that your application will allow; you may want o either add or remove some headers from the list in this example.

综上所述,这里有一个更好的实现。尽管这比上面的实现更好,但它仍然不如我链接到的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, 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", "*");
    }
}

To learn more about CORS, I suggest reading the MDN docs on Cross-Origin Resource Sharing (CORS)

要了解有关 CORS 的更多信息,我建议阅读有关跨域资源共享 (CORS)的 MDN 文档

回答by Michael

The other answer might be strictly correct, but misleading. The missing part is that you can mix filters from different sources together. Even thought Jersey might not provide CORS filter (not a fact I checked but I trust the other answer on that), you can use tomcat's own CORS filter.

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

I am using it successfully with Jersey. I have my own implementation of Basic Authentication filter, for example, together with CORS. Best of all, CORS filter is configured in web XML, not in code.

我在泽西岛成功地使用了它。例如,我有自己的基本身份验证过滤器实现,以及 CORS。最重要的是,CORS 过滤器是在 Web XML 中配置的,而不是在代码中。

回答by Dark Star1

To solve this for my project I used Micheal'sanswer and arrived at this:

为了为我的项目解决这个问题,我使用了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>

The CORS filter being the basic example filter from the tomcat site.

Edit:
The maven.tomcat.web-xml.filevariable is a pom defined property for the project and it contains the path to the web.xml file (located within my project)

CORS 过滤器是来自tomcat 站点的基本示例过滤器

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

回答by minhlong293

peeskillet's answer is correct. But I get this error when refresh the web page (it is working only on first load):

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.

So instead of using add method to add headers for response, I using put method. This is my class

因此,我没有使用 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;
    }
}

And add this class to init-param in web.xml

并将此类添加到 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>

回答by Aupr

Remove annotation "@CrossOriginResourceSharing(allowAllOrigins = true)"

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

Then Return Response like below:

然后返回响应如下:

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

But the jsonResponseshould replace with a POJO Object!

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