簡體   English   中英

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

[英]How to handle CORS using JAX-RS with Jersey

我正在開發一個 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();
}

截至目前,我收到錯誤請求的資源上不存在“Access-Control-Allow-Origin”標頭。 因此,不允許訪問 Origin ' http://localhost:8080 '。”

請幫我解決這個問題。

感謝和問候 佛陀普尼

注意:請務必閱讀底部的更新。 原始答案包括 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;
    }
}

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 標頭添加到所有請求中。 另一個錯誤是它只是一個響應過濾器,請求仍在處理。 這意味着當 preflight 請求進來時,它是一個 OPTIONS 請求,不會有 OPTIONS 方法實現,所以我們會得到 405 響應,這是不正確的。

這是它應該如何工作。 所以有兩種類型的 CORS 請求:簡單請求和預檢請求 對於簡單的請求,瀏覽器將發送實際請求並添加Origin請求標頭。 瀏覽器期望響應具有Access-Control-Allow-Origin標頭,表示Access-Control-Allow-Origin來自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 響應標頭響應這些標頭。 以下是匹配的標頭:

請求頭 響應頭
起源 訪問控制允許來源
訪問控制請求頭 訪問控制允許標題
訪問控制請求方法 訪問控制允許方法
XHR.withCredentials 訪問控制允許憑據
  • 對於Origin請求標頭,該值將是源服務器域,並且響應Access-Control-Allow-Origin應該是相同的地址或*以指定允許所有源。

  • 如果客戶端嘗試手動設置不在上述列表中的任何標頭,則瀏覽器將設置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, 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 文檔

刪除注釋“ @CrossOriginResourceSharing(allowAllOrigins = true)

然后返回響應如下:

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

但是jsonResponse應該替換為 POJO 對象!

另一個答案可能嚴格正確,但具有誤導性。 缺少的部分是您可以將來自不同來源的過濾器混合在一起。 即使認為 Jersey 可能不提供 CORS 過濾器(這不是我檢查過的事實,但我相信其他答案),您可以使用tomcat 自己的 CORS 過濾器

我在澤西島成功地使用了它。 例如,我有自己的基本身份驗證過濾器實現,以及 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 過濾器是來自tomcat 站點的基本示例過濾器

編輯
maven.tomcat.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>

使用JAX-RS,您可以簡單地將注釋@CrossOrigin(origin = yourURL)到資源控制器。 您的情況是@CrossOrigin(origin = "http://localhost:8080")但是您也可以使用@CrossOrigin(origin = "*")來允許任何請求通過您的Web服務。
您可以檢查以獲取更多信息。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM