5

I have two servers (Apache and JBoss AS7) and I need to provide access to all http methods to a client. All these request must be sent via ajax. Example of the client code:

$.ajax({
      type: "get",
      url: "http://localhost:9080/myproject/services/mobile/list",
      crossDomain: true,
      cache: false,
      dataType: "json",
      success: function(response) {
        console.log(response);
      },
      error: function (jqXHR, textStatus, errorThrown) {
        console.log(textStatus);
        console.log(jqXHR.responseText);
        console.log(errorThrown);
        }
    });

In JBoss AS7 I'm using RESTEasy, implementing CORS as follows:

@Path("/mobile")
@Provider
@ServerInterceptor
public class GroupMobile implements MessageBodyWriterInterceptor {

@Inject
private GroupDAO groupDAO;

@GET
@Path("/list")
@Produces(MediaType.APPLICATION_JSON)
public List<Group> getGroups() {
    return groupDAO.listAll();
}

@Override
public void write(MessageBodyWriterContext context) throws IOException,
        WebApplicationException {
    context.getHeaders().add("Access-Control-Allow-Origin", "*");
    context.proceed();
}

@OPTIONS
@Path("/{path:.*}")
public Response handleCORSRequest(
        @HeaderParam("Access-Control-Request-Method") final String requestMethod,
        @HeaderParam("Access-Control-Request-Headers") final String requestHeaders) {
    final ResponseBuilder retValue = Response.ok();

    if (requestHeaders != null)
        retValue.header("Access-Control-Allow-Headers", requestHeaders);

    if (requestMethod != null)
        retValue.header("Access-Control-Allow-Methods", requestMethod);

    retValue.header("Access-Control-Allow-Origin", "*");

    return retValue.build();
}
}

web.xml and beans.xml are empty files. When I access MyIP:8080 (Apache), I get the error message:

XMLHttpRequest cannot load http://localhost:9080/myproject/services/mobile/list?_=1359480354190. Origin http://MyIP:8080 is not allowed by Access-Control-Allow-Origin.

Does anybody know what is wrong?

Leo
  • 93
  • 1
  • 2
  • 9
  • [this article](http://blog.usul.org/cors-compliant-rest-api-with-jersey-and-containerresponsefilter/) helped me. i'm using jersey though. – Janus Troelsen Feb 25 '13 at 15:39

5 Answers5

11

The newest resteasy (3.0.9-Final) include a utility class org.jboss.resteasy.plugins.interceptors.CorsFilter.

You can add the CorsFilter object into Application's singleton objects set, or add it into ProviderFactory in ResteasyDeployment directly.

The following is the sample application class:

import java.util.HashSet;
import java.util.Set;
import javax.ws.rs.ApplicationPath;

import org.jboss.resteasy.plugins.interceptors.CorsFilter;


@ApplicationPath("/api")
public class RestApplication extends javax.ws.rs.core.Application {
    Set<Object> singletons;

    @Override
    public Set<Class<?>> getClasses() {
        HashSet<Class<?>> clazzes = new HashSet<>();
        clazzes.add(VersionService.class);
        return clazzes;
    }

    @Override
    public Set<Object> getSingletons() {
        if (singletons == null) {
            CorsFilter corsFilter = new CorsFilter();
            corsFilter.getAllowedOrigins().add("*");

            singletons = new LinkedHashSet<Object>();
            singletons.add(corsFilter);
        }
        return singletons;
    }
}
ElOjcar
  • 301
  • 2
  • 4
  • 12
sca.
  • 365
  • 4
  • 14
  • Doesn't work for me. When I add the getSingletons() method to my application, I can't call any of my rest endpoints, they stop working at all. After I remove the getSingletons method, I can call the endpoints again. – Zhenya Jul 18 '16 at 01:38
  • 1
    Here is the solution to the problem that I described in my previous comment: http://stackoverflow.com/questions/29388937/problems-resteasy-3-09-corsfilter/29390508#29390508 – Zhenya Jul 18 '16 at 01:59
  • I had the same problem and Tomcat ignores a CorsFilter class annotated, I had to add the cors filter in the application and add it in the singleton like you do. Thanks – Andreas Panagiotidis Mar 10 '17 at 14:29
1

The problem you are having is your are trying to do cross-site scripting. You accessed the page at http://MyIP:8080 and so the browser is preventing you from accessing resources outside that domain. This is very browser specific and browser based work arounds will all be different (you can disable security in Chrome globally, and on a per site basis in IE).

If you load the page as http://localhost:8080, it should then allow you access the query. Alternatively, you can implement a proxy which will forward the request.

Mark Robinson
  • 3,135
  • 1
  • 22
  • 37
  • Thanks for the quickly reply Mark! How can I implement a proxy? If I use another AS (p.e. Tomcat) with CXF instead of RESTEasy, it works without proxy. – Leo Jan 29 '13 at 18:28
  • It is entirely possible that CXF implements a proxy for doing things like that. Alternatively, tomcat has proxies here http://wiki.apache.org/tomcat/ServletProxy A better solution is to fix the address so that you're not doing any cross site scripting. Setting up a proxy is a pain and also a security hole if done badly. – Mark Robinson Jan 29 '13 at 18:38
  • I agree! But I have to do it to work with that architecture: clients accessing Apache doing cross-site scripting with JBoss AS7 (using JAX-RS/RESTEasy). Do you have some references on how I can implement a proxy? – Leo Jan 29 '13 at 18:48
  • Hang on. The user explicitly says that he is using CORS. So the cross-origin request should work. Do you have an example of the headers that are send in the request/response? – monsur Jan 29 '13 at 18:58
  • Request: HTTP/1.1 200 OK Server: Apache-Coyote/1.1 Content-Type: application/json Transfer-Encoding: chunked (How can I put this in code format?) – Leo Jan 29 '13 at 19:15
  • Response: `GET /myproject/services/mobile/list?_=1359486362990 HTTP/1.1 Host: localhost:9080 User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; pt-BR; rv:1.9.2.28) Gecko/20120306 Firefox/3.6.28 ( .NET CLR 3.5.30729; .NET4.0E) Accept: application/json, text/javascript, */*; q=0.01 Accept-Language: pt-br,pt;q=0.8,en-us;q=0.5,en;q=0.3 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 115 Connection: keep-alive Referer: http://MyIP:8080/ Origin: http://MyIP:8080 ` – Leo Jan 29 '13 at 19:21
  • Sorry but the last one is the Request. The first one is the Response. – Leo Jan 29 '13 at 19:28
  • I make it work using JSONP like [this](http://www.solutionsfit.com/2010/04/18/serving-up-jsonp-from-your-jax-rs-web-services/). Thanks for all replies. – Leo Jan 30 '13 at 12:24
1

Sounds like the issue is related to https://issues.jboss.org/browse/RESTEASY-878. You may not be able to catch CORS preflight requests with MessageBodyWriterInterceptor. Try using servlet filters (@WebFilter) instead.

Tadayoshi Sato
  • 1,401
  • 11
  • 18
1

Well, I have implemented a small solution, first I do a interceptor in my project Web I created a class called "CORSInterceptor" the class is of the way.

import org.jboss.resteasy.annotations.interception.ServerInterceptor;
import org.jboss.resteasy.core.ResourceMethod;
import org.jboss.resteasy.core.ServerResponse;
import org.jboss.resteasy.spi.Failure;
import org.jboss.resteasy.spi.HttpRequest;
import org.jboss.resteasy.spi.interception.MessageBodyWriterContext;
import org.jboss.resteasy.spi.interception.MessageBodyWriterInterceptor;
import org.jboss.resteasy.spi.interception.PreProcessInterceptor;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.ext.Provider;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

@Provider
@ServerInterceptor
public class CorsInterceptor implements PreProcessInterceptor, MessageBodyWriterInterceptor {

/**
     * The Origin header set by the browser at each request.
     */
    private static final String ORIGIN = "Origin";


 /**
     * The Access-Control-Allow-Origin header indicates which origin a resource it is specified for can be
     * shared with. ABNF: Access-Control-Allow-Origin = "Access-Control-Allow-Origin" ":" source origin string | "*"
     */
    private static final String ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin";


   //
    private static final ThreadLocal<String> REQUEST_ORIGIN = new ThreadLocal<String>();
    //
    private final Set<String> allowedOrigins;


 public CorsInterceptor(){
   this.allowedOrigins = new HashSet<String>();
   this.allowedOrigins.add("*");
 }

   @Override
    public ServerResponse preProcess(HttpRequest request, ResourceMethod method) throws Failure, WebApplicationException {
        if (!allowedOrigins.isEmpty()) {
            REQUEST_ORIGIN.set(request.getHttpHeaders().getRequestHeaders().getFirst(ORIGIN));
        }
        return null;
    }


@Override
public void write(MessageBodyWriterContext context) throws IOException, WebApplicationException {
    if (!allowedOrigins.isEmpty() && (allowedOrigins.contains(REQUEST_ORIGIN.get()) || allowedOrigins.contains("*"))) {
        context.getHeaders().add(ACCESS_CONTROL_ALLOW_ORIGIN, REQUEST_ORIGIN.get());
      }
    context.proceed();
    }
}

In the File Web.xml, I add it

<context-param>
    <param-name>resteasy.providers</param-name>
    <param-value><package>.CorsInterceptor</param-value>
</context-param>

package: Ubication of the class.

Request JQuery that I have used.

$.ajax({
    type: 'GET',
    dataType: "json", 

    crossDomain : true,

    cache:false, 
    url: "http://localhost:12005/ProyectoWebServices/ws/servicioTest",
    success: function (responseData, textStatus, jqXHR) {
       alert("Successfull: "+responseData);
    },
    error: function (responseData, textStatus, errorThrown) {
        alert("Failed: "+responseData);
    }
});

it worked fine for me. I hope that it can help you.

Andres
  • 36
  • 2
0

I faced the same issue recently and adding my solution which worked for me:

Following is what I did :

  • I created a class extending javax.ws.rs.core.Application and added a Cors Filter to it.

To the CORS filter, I added corsFilter.getAllowedOrigins().add("http://localhost:4200");.

Basically, you should add the URL which you want to allow Cross-Origin Resource Sharing. Ans you can also use "*" instead of any specific URL to allow any URL.

public class RestApplication
    extends Application
{
    private Set<Object> singletons = new HashSet<Object>();

    public MessageApplication()
    {
        singletons.add(new CalculatorService()); //CalculatorService is your specific service you want to add/use.
        CorsFilter corsFilter = new CorsFilter();
        // To allow all origins for CORS add following, otherwise add only specific urls.
        // corsFilter.getAllowedOrigins().add("*");
        System.out.println("To only allow restrcited urls ");
        corsFilter.getAllowedOrigins().add("http://localhost:4200");
        singletons = new LinkedHashSet<Object>();
        singletons.add(corsFilter);
    }

    @Override
    public Set<Object> getSingletons()
    {
        return singletons;
    }
}
  • And here is my web.xml:
<web-app id="WebApp_ID" version="2.4"
    xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee 
    http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
    <display-name>Restful Web Application</display-name>

    <!-- Auto scan rest service -->
    <context-param>
        <param-name>resteasy.scan</param-name>
        <param-value>true</param-value>
    </context-param>

    <context-param>
        <param-name>resteasy.servlet.mapping.prefix</param-name>
        <param-value>/rest</param-value>
    </context-param>

    <listener>
        <listener-class>
            org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap
        </listener-class>
    </listener>

    <servlet>
        <servlet-name>resteasy-servlet</servlet-name>
        <servlet-class>
            org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher
        </servlet-class>
        <init-param>
            <param-name>javax.ws.rs.Application</param-name>
            <param-value>com.app.RestApplication</param-value>
        </init-param>
    </servlet>

    <servlet-mapping>
        <servlet-name>resteasy-servlet</servlet-name>
        <url-pattern>/rest/*</url-pattern>
    </servlet-mapping>

</web-app>

There are some custom changes which you might not require or change like :

    <servlet-mapping>
        <servlet-name>resteasy-servlet</servlet-name>
        <url-pattern>/rest/*</url-pattern>
    </servlet-mapping>

OR

    <context-param>
        <param-name>resteasy.scan</param-name>
        <param-value>true</param-value>
    </context-param>

OR

    <context-param>
        <param-name>resteasy.servlet.mapping.prefix</param-name>
        <param-value>/rest</param-value>
    </context-param>

The most important code which I was missing when I was getting this issue was, I was not adding my class extending javax.ws.rs.Application i.e RestApplication to the init-param of <servlet-name>resteasy-servlet</servlet-name>

   <init-param>
        <param-name>javax.ws.rs.Application</param-name>
        <param-value>com.app.RestApplication</param-value>
    </init-param>

And therefore my Filter was not able to execute and thus the application was not allowing CORS from the URL specified.

PS: I am using RestEasy version: 3.12.1.Final

Deep
  • 929
  • 2
  • 16
  • 32