'Custom HTTP Header blocks Jersey CORS Filter
I am using a Jersey2 response filter to deal with CORS requests from the browser. At this point it pretty much looks like the one from Paul Samsotha in this question How to handle CORS using JAX-RS with Jersey
@Provider
public class CORSFilter implements ContainerRequestFilter, ContainerResponseFilter {
@Override
public void filter(ContainerRequestContext containerRequestContext, ContainerResponseContext containerResponseContext)
throws IOException {
if (containerRequestContext.getHeaderString("Origin") == null) {
return;
}
if (isPreflightRequest(containerRequestContext)) {
containerResponseContext.getHeaders()
.add("Access-Control-Allow-Credentials", "true");
containerResponseContext.getHeaders()
.add("Access-Control-Allow-Headers", "origin, content-type, accept, authorization");
containerResponseContext.getHeaders()
.add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD");
}
containerResponseContext.getHeaders()
.add("Access-Control-Allow-Origin", "*");
}
@Override
public void filter(ContainerRequestContext containerRequestContext) throws IOException {
if (isPreflightRequest(containerRequestContext)) {
containerRequestContext.abortWith(Response.ok().build());
return;
}
}
private static boolean isPreflightRequest(ContainerRequestContext request) {
return request.getHeaderString("Origin") != null
&& request.getMethod().equalsIgnoreCase("OPTIONS");
}
}
Now this works perfectly fine, until I add my own header (intended for authorization purposes) on the Frontend like so
axios.defaults.headers.common["xy"] = "example content";
On the server side I have a simple DELETE method as an example
@Path("/example")
@DELETE
public void example(@Context HttpHeaders headers) {
String headerContent = headers.getHeaderString("xy");
...do stuff...
}
The moment this header is added, the filter is no longer executed for the main call. It is still executed for the preflight, but no longer for the DELETE call, which triggers a CORS-Error in the browser.
Testing it from Postman works perfectly fine with the exact same header. I fire the call, the filter triggers and everything works. But the moment the browser runs it, the filter is no longer triggered.
I have tested this also for PUT, same result. I have tested it with vanilla JS XMLHTTPRequest, same result. Through remote debugging I know that the filter simply is not executed. When I remove the header, I still have OPTIONS and DELETE call, but the filter is executed for both calls. The moment I add the header again, it is only executed for the OPTIONS call (which does not have the header), but not for the DELETE call.
This runs on a Tomcat 9, with Jersey 2.27 and Java 11.
So my question is, why the header seemingly kills the execution of the filter?
Solution 1:[1]
You need to understand how the CORS protocol works. For simple CORS requests, there is no preflight, and non-simple CORS requests, a preflight request is made. The preflight request is an OPTIONS request made prior to the actual request, asking the server if the request is allowed. This request is made with specific headers that the server should respond to with its own specific response headers. I mentioned all of this in the post you linked to. I even explained what each header is for.
The header in particular that is your problem is the preflight request header Access-Control-Request-Headers
. The browser will send this header (in the preflight request) and the value will list all of the headers the client is trying to set. The server should respond with a Access-Control-Allow-Headers
header that lists all of the headers that are allowed to be sent from the client. Any header that you want to allow the client to send should be listed in the value to this response header. For example
Access-Control-Allow-Headers: Authorization, xy
I also added a comment in the code telling you that you may need to change the list
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");
Please take time to review the entire post and read some of the resources that I linked to so you can fully understand how the CORS protocol works.
Solution 2:[2]
It seems that you have added multiple conditions to apply the filter.
try disabling the following condition: (containerRequestContext.getHeaderString("Origin") == null)
and isPreflightRequest(containerRequestContext)
.
for example:
@Override
public void filter(ContainerRequestContext containerRequestContext, ContainerResponseContext containerResponseContext)
throws IOException {
// disable this condition.
//if (containerRequestContext.getHeaderString("Origin") == null) {
// return;
//}
// disable this condition
// it seems problemetic
// if (isPreflightRequest(containerRequestContext)) {
containerResponseContext.getHeaders()
.add("Access-Control-Allow-Credentials", "true");
containerResponseContext.getHeaders()
.add("Access-Control-Allow-Headers", "origin, content-type, accept, authorization");
containerResponseContext.getHeaders()
.add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD");
//}
containerResponseContext.getHeaders()
.add("Access-Control-Allow-Origin", "*");
}
Solution 3:[3]
you need to add cutsomer header xy to "Access-Control-Allow-Headers" list. This worked for me like a charma
containerResponseContext.getHeaders()
.add("Access-Control-Allow-Headers", "origin, content-type, accept, authorization, xy");
Custome header are blocked during the preflight request. you can refer to detailed answer here:How to handle CORS using JAX-RS with Jersey
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | MD Ruhul Amin |
Solution 3 | Prateek Kapoor |