user2773716
user2773716

Reputation: 875

How to handle CORS using JAX-RS with Jersey

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:

@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.”

Please assist me with this.

Thanks & Regards Buddha Puneeth

Upvotes: 83

Views: 111882

Answers (7)

Whome
Whome

Reputation: 10400

I am using Jersey 3 and CORS+preflight shall be available only on certain rest methods. Using a simple filter as stated in earlier answers such as class CORSFilter implements ContainerRequestFilter,ContainerResponseFilter | register(my.app.CORSFilter.class) would install cors on all OPTIONS+GET+POST+WHATEVER methods.

I did not want that but have just specific OPTIONS+GET methods and remaining resource methods without any cors reply headers. Few challenges

  • Jersey handles the OPTIONS(preflight) requests unless we annotate OPTIONS+GET+POST+DELETE accordingly.
  • Inserting a global cors filter is not desirable if conditional logic is needed.
  • how to enable a global CORS filter in a development box with a simple flag.
  • How to make this easy enough .. my answer is below.

This solution does need an additional @CORS @OPTIONS @PATH function to handle preflight and @CORS @GET @PATH function to handle the real app logic.

Methods must use the identical function signature but OPTIONS handler does not need any implementation.

public class MyApplication extends ResourceConfig {
  public MyApplication() {
     // true: global CORS in a devbox, false: only annotated methods
     my.app.rest.CORSResponseFilter.isCORS = (from envvar or constant flag);
     ...
     register(my.app.rest.CORSResponseFilter.CORSFilterDynamic.class);
     register(my.app.rest.MyResourceRest.class); 
     register(my.app.rest.MySomethingRest.class); 
     register(my.app.rest.MyElseRest.class); 
     register(my.app.rest.api.PublicRest.class); 
     ...
  }
}

@Path("/") @Singleton
public class PublicRest {
    @CORS @OPTIONS @Path("/myresource/{p1}/{p2}/actionX")
    public final Response actionX_options(
            @PathParam("p1") String p1,
            @PathParam("p2") long p2
            ) {
        return null; // CORSFilter aborts a preflight so this is never invoked.
    }   

    // additional CORS OPTIONS methods 
}

@Path("") @Singleton
public class MyResourceRest {
    @CORS
    @GET @Path("/myresource/{p1}/{p2}/actionX")
    @Produces({"application/json;charset=UTF-8"})
    public final Response actionX(
            @PathParam("p1") String p1,
            @PathParam("p2") long p2
            ) {
        // .. do something and provide obj retval entity ..
        return Response.status(200).type("application/json;charset=UTF-8").entity(obj).build();
    }   

}

public class CORSResponseFilter {
    // set by MyApplication based on envvar or config file.
    // true: all methods have CORS in a dev env.
    public static boolean isCORS=false;

    // Use annotation on functions that always need CORS headers
    @NameBinding @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD, ElementType.TYPE})
    public static @interface CORS{ }
    
    public static class CORSFilterDynamic implements DynamicFeature {
        private CORSFilter corsFilter = new CORSFilter(); // use singleton filter
        @Override public void configure(ResourceInfo resInfo, FeatureContext context) {                 
            if (isCORS) {
                // all methods have an implicit CORS
                context.register(corsFilter);
            } else if (resInfo.getResourceMethod().isAnnotationPresent(CORS.class)) {
                // annotated CORS methods
                context.register(corsFilter);
            }
        }
    }
    
    @Provider @Priority(Priorities.AUTHENTICATION-100)
    private static class CORSFilter implements ContainerRequestFilter,ContainerResponseFilter {
        private boolean isPreflight(ContainerRequestContext creq) {
            return creq.getHeaderString("Origin") != null
                && creq.getMethod().equalsIgnoreCase("OPTIONS");
        }
        
        private void setCORS(ContainerResponseContext cres, String origin) {
            if (origin==null) return;       
            cres.getHeaders().add("Access-Control-Allow-Origin", origin);
            cres.getHeaders().add("Access-Control-Allow-Headers", "Origin, Access-Control-Allow-Methods, Access-Control-Allow-Headers, Access-Control-Allow-Origin, X-Requested-With, Content-Type, Accept, Accept-Encoding, Authorization");
            //cres.getHeaders().add("Access-Control-Expose-Headers", "server,range,date,content-range,content-length,content-type");
            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", "1440");
        }
        
        @Override public void filter(ContainerRequestContext creq) throws IOException {
            if(isPreflight(creq))
                 creq.abortWith(Response.ok().build()); // goes to ResponseFilter.filter(creq,cres) without invoking the rest resource function. 
        }

        @Override public void filter(ContainerRequestContext creq, ContainerResponseContext cres) throws IOException {
            setCORS(cres, creq.getHeaderString("Origin") );
        }
    }

}

Upvotes: 0

Falchio
Falchio

Reputation: 606

for multiple origin domains:

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(ContainerRequestContext requestContext, ContainerResponseContext responseContext) {

        final String domain = generateAllowOrigin(requestContext);

        responseContext.getHeaders().add("Access-Control-Allow-Origin", domain);
        responseContext.getHeaders().add("Access-Control-Allow-Credentials", "true");
        responseContext.getHeaders().add("Access-Control-Allow-Headers", "origin, content-type, accept, authorization");
        responseContext.getHeaders().add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD");
    }

    private static String generateAllowOrigin(ContainerRequestContext requestContext) {
        String caller = requestContext.getHeaderString("Origin");
        final String domain;
        if (caller != null && caller.contains("localhost:4141")) {
            domain = "http://localhost:4141";
        } else {
            domain = "https://example.com";
        }
        return domain;
    }
}

Upvotes: 0

Paul Samsotha
Paul Samsotha

Reputation: 208994

Note: Make sure to read the UPDATE at the bottom. The original answer includes a "lazy" implementation of the CORS filter

With Jersey, to handle CORS, you can just use a ContainerResponseFilter. The ContainerResponseFilter for Jersey 1.x and 2.x are a bit different. Since you haven't mentioned which version you're using, I'll post both. Make sure you use the correct one.

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

If you use package scanning to discover providers and resources, the @Provider annotation should take care of the configuration for you. If not, then you will need to explicitly register it with the ResourceConfig or the Application subclass.

Sample code to explicitly register filter with the 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 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 configuration, you can use

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

Or ResourceConfig you can do

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

Or package scanning with the @Provider annotation.


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


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 response filter, 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.

Here's how it should work. 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 Origin request header. The browser expects for the response to have the Access-Control-Allow-Origin header, saying that the origin from the Origin header is allowed. In order for it to be considered a "simple request", it must meet the following criteria:

  • Be one of the following method:
    • GET
    • HEAD
    • POST
  • Apart from headers automatically set by the browser, the request may only contain the following manually set headers:
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type
    • DPR
    • Save-Data
    • Viewport-Width
    • Width
  • The only allowed values for the Content-Type header are:
    • 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, prior to the actual request being made. It will contain different Access-Control-XX-XX headers, and the server should respond to those headers with its own CORS response headers. Here are the matching 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 Origin request header, the value will be the origin server domain, and the response Access-Control-Allow-Origin should 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-Headers header, 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-Headers response header, with the value being a list of headers it allows.

  • The browser will also set the Access-Control-Request-Method request header, with the value being the HTTP method of the request. The server should respond with the Access-Control-Allow-Methods response 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-Credentials response header, with a value of true. Read more here.

So with all that said, here is a better implementation. Even though this is better than the above implementation, it is still inferior to the RESTEasy one I 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-Headers to match the headers that your application will allow; you may want o either add or remove some headers from the list in this example.

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

Upvotes: 191

Aupr
Aupr

Reputation: 755

Remove annotation "@CrossOriginResourceSharing(allowAllOrigins = true)"

Then Return Response like below:

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

But the jsonResponse should replace with a POJO Object!

Upvotes: 10

minhlong293
minhlong293

Reputation: 61

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

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

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

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

Upvotes: 2

Dark Star1
Dark Star1

Reputation: 7403

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

    <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.file variable is a pom defined property for the project and it contains the path to the web.xml file (located within my project)

Upvotes: 1

Michael
Michael

Reputation: 1905

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.

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.

Upvotes: 6

Related Questions