I've uncovered something strange while using resteasy-jaxrs in a Cors enabled jBoss server. Here's the setup:
Our server has thetransactioncompany.com's CorsFilter (v1.3.2) enabled as a servlet filter to enable CORS and add the appropriate CORS headers to the HttpServletResponse object.
The server itself is using resteasy-jaxrs (v2.3.2.Final) to serve JSON endpoints to power our app, obviously running on a separate domain.
The issue is that if one of my endpoint methods generates any type of exception (NPE, UnauthorizedException, InternalServerErrorException), as a part of preparing the response, RestEasy makes the call
(HttpServletResponse)response.reset()
which clears out my CORS headers. This causes Chrome to rightly act as if the request should be canceled. This is really inconvenient for my front end dev who needs those error codes.
Two questions:
Why would RestEasy want to clear those headers?
Has anyone else run across this and have any workarounds?
I am actually surprised to see that you have encountered this behavior since I've never seen it, and I certainly have generated plenty of exceptions in my day.
Regardless, consider writing a custom ExceptionMapper as described here. You can populate the response with your CORS headers and return it from toResponse along with the appropriate error information.
I ran into this issue too, and I had a workaround by using a response wrapper
class CorsHeaderResponseWrapper extends HttpServletResponseWrapper{
public CorsHeaderResponseWrapper(HttpServletResponse resp) {
super(resp);
setCorsHeader();
}
void setCorsHeader(){
HttpServletResponse resp = (HttpServletResponse)getResponse();
//set cors header here
}
public void reset(){
super.reset();
//set again if anyone reset it
setCorsHeader();
}
}
when calling doFilter
chain.doFilter(req, new CorsHeaderResponseWrapper(resp));
Related
We have a embedded Jetty 10.0.12 server, configure everything programmably (no web.xml) and already have a few servlets registered. We want to add a new servlet for an internal API. I have already done this. We now want to secure it. Security would be pretty simple: if the request did not come from within the server, reject it. This is good enough because we employ other security standards in the other servlets. I know where to start: create and register a filter:
public class InternalFilter implements Filter {
#Override
public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
// TODO: Check if request is internal.
// I.e., came from another registered servlet in the same JVM.
// If it is internal, then `chain.doFilter`.
}
}
I do not know how to proceed from here.
I'll start by assuming that "internal" means you are using either RequestDispatcher.include() or RequestDispatcher.forward().
If so, then you can check the HttpServletRequest.getDispatcherType() value.
Value
Meaning
DispatcherType.FORWARD
Request arrived from a call to RequestDispatcher.forward().
DispatcherType.INCLUDE
Request arrived from a call to RequestDispatcher.include().
DispatcherType.REQUEST
Request arrived from the beginning of the server handling tree.
DispatcherType.ASYNC
Request arrived from call to HttpServletRequest.startAsync()
DispatcherType.ERROR
Request arrived from error handling (either an unhandled exception, or from a call to HttpServletResponse.sendError()
I want to log the contents of a given incoming request header as early as possible.
I know about approaches like CommonsRequestLoggingFilter or a logging HandlerInterceptor, however these seem to only log after Spring has executed a lot of other code, such as the Spring Security filter chain.
I want to log before Spring has done any of that, as early as possible based on a single requirement: the log message needs to be able to extract a header from the HTTP request.
Is there a way to do this?
I have found a way to do this using the embedded Tomcat. Since this receives the request before Spring does, you can capture the entire dispatched request from here.
public class CustomLoggerValve extends ValveBase {
private static final Logger logger = LoggerFactory.getLogger(CustomLoggerValve.class);
#Override
public void invoke(Request request, Response response) throws IOException, ServletException {
try {
MDC.put("requestId", UUID.randomUUID().toString());
logger.info("Received request");
getNext().invoke(request, response);
} finally {
MDC.remove("requestId");
}
}
}
Since I'm using Spring without Spring Boot, I can just add this to my Tomcat directly:
Tomcat tomcat = // ... your tomcat setup
tomcat.getService().getContainer().getPipeline().addValve(new CustomLoggerValve());
I haven't tried, but it looks like you could add this quite easily in Spring Boot too.
Presumably a similar approach would work with embedded Jetty/other JVM web servers.
we have are using spring mvc with #RestController annotations on our controllers, and we're handling authorization in the controller. we use the same code to set the allowed methods in responses to CORS pre-flight request. to achieve this, we have:
<init-param>
<param-name>dispatchOptionsRequest</param-name>
<param-value>true</param-value>
</init-param>
in the configuration of the dispatcher servlet, and then we have:
#RequestMapping(value="/some/collections", method=RequestMethod.OPTIONS)
public void collectionOptions(
HttpServletRequest req,
HttpServletResponse res) {
List<RequestMethod> methods = new ArrayList<>();
// check actual permissions, add the appropriate methods
CORS.setAllowedMethodHeaders(res,methods);
}
we also have an interceptor that do basic checks on CORS pre-flight to see if the origin can possibly have any permissions at all.
we do it like this mostly because permissions for some requests actually depend on #RequestParams, i.e.:
OPTIONS /api/collections?userId=122
might be allowed if you have administrative privileges OR you actually the user with the ID 122. also, we have API keys, so
OPTIONS /api/collections?userId=122&apiKey=ABC
might be OK for one origin, but not for another one.
this works fine, but spring 4.2 now decides wether or not it handles an OPTIONS request, via a call to:
CorsUtils.isCorsRequest(request);
in AbstractHandlerMapping and then returns
HandlerInterceptor[] interceptors = chain.getInterceptors();
chain = new HandlerExecutionChain(new PreFlightHandler(config), interceptors);
instead of the HandlerMethod ...
what we would need is some way to tell spring to let the controller handle OPTIONS requests, no matter what preflight request handlers are present.
We don't seem to be able to find a point where we can EITHER tell the built-in CORS handling to be quiet or to configure some subclass somewhere that would allow us to bypass the newly added code in:
AbstractHandlerMapping.getHandler(HSR request)
Is this in any way possible? Wouldn't it be nice for a feature like this to be quiet until I actively enable it (through WebMvcConfigurerAdapter or via those #CrossOrigin annotations)?
-------- EDIT -------------
the HTTP standard says the following about the OPTIONS method:
The OPTIONS method represents a request for information about the communication options available on the request/response chain identified by the Request-URI. This method allows the client to determine the options and/or requirements associated with a resource, or the capabilities of a server, without implying a resource action or initiating a resource retrieval.
thinking beyong just CORS, i think that intercepting a CORS options call although a corresponding method is mapped on the controller is not the right way to go. yes, CORS is one thing you can do with an OPTIONS call. but it is by no means the only one.
if there is nothing mapped and if a handler method is mapped with a different request method and the #CrossOrigin annotation, the assumptions that i want the built in CORS support to be triggered would be fine, but i do not think that any request that has the origin header set should automatically ONLY go to the CORS handlers.
I just convinced Spring 4.3 to pass CORS preflights to my controller by adding a custom handler mapping:
public class CorsNoopHandlerMapping extends RequestMappingHandlerMapping {
public CorsNoopHandlerMapping() {
setOrder(0); // Make it override the default handler mapping.
}
#Override
protected HandlerExecutionChain getCorsHandlerExecutionChain(HttpServletRequest request,
HandlerExecutionChain chain, CorsConfiguration config) {
return chain; // Return the same chain it uses for everything else.
}
}
Note: you'll still need to tell Spring to dispatch OPTIONS requests to your controller to begin with - that is, set dispatchOptionsRequest to true in dispatcherServlet like it says in this question.
WHY IT WORKS
Sébastien's above answer suggests using your own CorsProcessor. As far as I can tell, this will still not use your controller as the handler; it will just pass a different CorsProcessor to its own CORS handler.
By default, it looks like the method AbstractHandlerMapping#getCorsHandlerExecutionChain will throw out your controller when it detects a preflight. Instead of using your controller as the handler, it instantiates a new PreFlightHandler and uses that instead. See Spring source code. This is the problematic line:
chain = new HandlerExecutionChain(new PreFlightHandler(config), interceptors);
What it's doing here is rebuilding the execution chain with a PreFlightHandler instead of your controller. This is not what we want, so we can override it to return the input chain.
As suggested by zeroflagl, I also think that this is not a good idea to mix access control and CORS. And you should keep in mind that only preflight CORS requests are OPTIONS ones.
If you need to customize Spring CORS handling, you can use AbstractHandlerMapping#setCorsProcessor() to provide your own implementation that could eventually extend DefaultCorsProcessor.
Please notice that by default, CORS processing is enabled but no remote origin is allowed so customizing CorsProcessor is rarely needed. More info on this blog post.
Your access control check could be done as a regular HandlerInterceptor since a successful CORS preflight request will be followed by an actual request.
In short - I would like to add such service endpoints to my servlet that can only be called from localhost. The restriction should be coded in the servlet itself, i.e it should not depend on Tomcat/Apache to be configured in a certain way. At the same time, there are many other, existing endpoints that should be reachable externally.
Longer description - I am creating an HTTP API that 3rd parties can implement to integrate with my application. I am also supplying a default implementation, bundled together with my app, that customers with simple requirements can use, without having to implement anything.
The endpoints of my default implementation should be reachable only for my app, which happens to be the same servlet as the one supplying the implementation, i.e it runs on the same host. So for security reasons (the API is security related), I want my implementation to be usable only for my app, which in the first round means restricting access to localhost for a set of HTTP endpoints.
At the same time, I don't want to rely on customers setting up their container/proxy properly, but do the restriction in my servlet, so that there are no changes required for existing installations.
So far the only idea I had was to check the requestor's IP addess in a servlet filter - so I am wondering if there is a better, more sophisticated way.
I think you should add Web Filter to your application and check your url in doFilter method. Check request.getRemoteAddr() and endpoint link you can put in urlPattern.
Like this:
#WebFilter(urlPatterns = "/*")
public class RequestDefaultFilter implements Filter {
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
if (isForbidden(request, response))
return;
else
chain.doFilter(request, response);
}
}
isForbidden implementation is up to you. In response you just send 403 error code for example.
You can check make same check in servlet and send in response 403 error.
My setup is as follows
A main application servlet accessible under /myApp/mainServlet/
A little "hand made" soap proxy that adds security headers (usernames, passwords) to soap calls coming from a client
A Flex client that talks to the main servlet (through a BlazeDS interface), and sends some soap calls to a third party through this soap proxy
The flex client has a session id which is set when it first talks to the main servlet and it returns a HTTP header such as "Set-Cookie: "JSESSION: something; Path=/myApp". This cookie is then sent the the server to inform of which session the client is associated to.
The problem is that the little soap proxy also returns a cookie with a session id (for each call made through it) - and the Flex client then uses these cookies when talking to the main servlet. These other session ids are unknown to it, and then of course nothing works ...
I do not want a session cookie to be returned from the soap proxy, and I have verified that the problem would be solved by doing so by telling an Apache front-end to strip all "Set-Cookie" headers coming from the soap proxy. Unfortunately (due to some setup restrictions), this is not a way I can go in production, and so I will need to fix it programmatically.
How can I make the servlet not try to set any session ids? I believe I have seen ways of telling Jetty (the app server) not to send sessions ids, but that would also affect the main servlet's ability to do so as well, and is also not portable.
The proxy servlet is a very basic Spring Controller (just implementing the interface), so basically just a bare bone servlet.
Removing the cookie can be done with res.setHeader("Set-Cookie", null);
Edit: It is good to know, that this removes all cookies, since they are all set in the same header.
I recommend that you don't do it in your servlet, a Filter is better, because it's less intrusive, something like:
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain)
throws IOException, ServletException
{
HttpServletResponse res = (HttpServletResponse) response;
try
{
chain.doFilter(request, res);
}
finally
{
res.setHeader("Set-Cookie", null);
}
}
This solution is inspired by this article at randomcoder.