Oromë
Oromë

Reputation: 319

JAX-RS Access-Control-Allow-Origin header not present, but it was added in ResponseFilter

I'm trying to connect to my REST endpoint built using JAX-RS, but I get the response that the header 'Access-Control-Allow-Origin' is not present. Trying to GET a resource with Angular2.

What I've tried

I tried the solution presented in https://stackoverflow.com/a/30450944.

My response filter:

@Provider
@PreMatching
public class ResponseFilter implements ContainerResponseFilter {

    @Override
    public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {
        responseContext.getHeaders().add("Access-Control-Allow-Origin", "*");
        responseContext.getHeaders().add("Access-Control-Allow-Headers", "origin, content-type, accept, authorization");
        responseContext.getHeaders().add("Access-Control-Allow-Credentials", "true");
        responseContext.getHeaders().add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD");
    }
}

And my web.xml servlet mapping:

    <servlet>
        <servlet-name>Faces Servlet</servlet-name>
        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
        <init-param>
            <param-name>jersey.config.server.provider.classnames</param-name>
            <param-value>com.api.ResponseFilter</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

Update 1

Updated the

responseContext.getHeaders().add(...);

to

responseContext.getHeaders.putSingle(...);

but I'm still getting the same error. While testing in postman, I am getting the correct headers back:

Access-Control-Allow-Credentials →true
Access-Control-Allow-Headers →origin, content-type, accept, authorization
Access-Control-Allow-Methods →GET, POST, PUT, DELETE, OPTIONS, HEAD
Access-Control-Allow-Origin →*

Update 2

In my JAXRSConfig, which extends Application, I've manually added all resources possibly necessary:

@ApplicationPath("api")
public class JAXRSConfig extends Application {

    @Override
    public Set<Class<?>> getClasses() {
        Set<Class<?>> resources = new HashSet<>();
        addRestResourceClasses(resources);
        return resources;
    }

    private void addRestResourceClasses(Set<Class<?>> resources) {
        resources.add(AdminResource.class);
        resources.add(RequestFilter.class);
        resources.add(ResponseFilter.class);
        resources.add(TweetResource.class);
        resources.add(UserResource.class);
    }
}

Via the use of breakpoints I have noticed that my ResponseFilter does get invoked when I send a GET request via Postman, but it does not get invoked when I send a GET request via my angular application.

My angular code to send a request:

let headers = new Headers({'Authorization': authToken});
    headers.append('Content-Type', 'application/json');

    let options = new RequestOptions({ headers: headers});

    return this.http.get(http://localhost:8080/api/object/user)
      .map((res:Response) => res.json())
      .catch((error:any) => Observable.throw(error.json().error || 'Server error'));

Update 3

I did realize I did not actually add the defined headers to the request; so I've updated my angular code to:

let authToken = "Basic " + btoa("henk:asdf");//temporary until I learn how to allow a user to log in, in angular
    let headers = new Headers();
    headers.append('Content-Type', 'application/json');
    headers.append('Authorization', authToken);

    //let options = new RequestOptions({ headers: headers});

    return this.http.get(this.myUrl, {headers: headers})
      .map((res:Response) => res.json())
      .catch((error:any) => Observable.throw(error.json().error || 'Server error'));

Update 4

I've since learned this could be due to the pre-flight check Chrome (and other browsers) perform, via a request-method called OPTIONS. I've added a request filter to my api as follows:

@Provider
@PreMatching
public class RequestFilter implements ContainerRequestFilter {
    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {
        if (requestContext.getRequest().getMethod().equals("OPTIONS")) {
            requestContext.abortWith(Response.status(Response.Status.OK).header("Access-Control-Allow-Origin", "http://localhost:4200").build());
        }
    }
}

But to no avail. With a break point on the if-statement I've seen that this method does not get called in a GET request from angular, but it does get called from a request from Postman.

I've also tried a different CORS filter according to https://stackoverflow.com/a/19902666/6795661. But this did not work either.

Update 5

As per peeskillet I've tried creating a class that implements javax.servlet.Filter, to add the required header(s) to a response:

public class ContainerResponseFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletResponse res = (HttpServletResponse) response;
        res.addHeader("Access-Control-Allow-Origin", "http://localhost:4200");
        res.addHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT");
        res.addHeader("Access-Control-Allow-Headers", "Content-Type");
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {

    }
}

With the corresponding mapping in my web.xml:

    <filter>
        <filter-name>corsfilter</filter-name>
        <filter-class>com.api.ContainerResponseFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>corsfilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

And I've added ContainerResponseFilter as resource in my Application configuration. Unfortunately I am still getting the error that the 'Access-Control-Allow-Origin' header is not present.

Upvotes: 1

Views: 3654

Answers (1)

Orom&#235;
Orom&#235;

Reputation: 319

As it turns out, the problem was with the authentication. My application server was trying to authenticate the request, but since a CORS check is done before the authentication I just got an unauthorized response, which did not include the required header.

Upvotes: 1

Related Questions