I am working on a project which is using embedded Jetty (unfortunately I just "inherited" the server side of the project and am not very familiar with the use of Jetty and its configuration).
An odd case just popped up - I'll do my best to describe:
A web-based UI (using AngularJS, from a different domain, so CORS is used) sends a POST request to change the state of something on the server. This worked at some point in the past (it was last used probably a month or so ago).
Yesterday this stopped working. Inspecting the REST calls, I saw that an OPTIONS request is first being made. The content type of the POST is application/json, so based on what I have read, this is correct. I am not sure why it was previously not sent - it is possible that the company had its version of Chrome updated recently, and the old version did not send preflight requests, but that's just speculation. In any case, here's what I think is the relevant code in my application for configuring Jetty for CORS:
FilterHolder holder = new FilterHolder(new CrossOriginFilter());
holder.setInitParameter(CrossOriginFilter.ALLOWED_ORIGINS_PARAM, "*");
holder.setInitParameter(CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER, "true");
appHandler.addFilter(holder, "/*", EnumSet.of(DispatcherType.REQUEST));
Everything works fine for POST requests. I can verify this by starting Chrome with the --disable-web-security flag. No OPTIONS request is sent, and the POST works as it should.
My thinking is that since it works for the POST, it's not an authorization or security issue - it's just that Jetty is not configured properly to handle the preflight request (it just returns 401).
I can't find much documentation for embedded Jetty, and which of the CrossOriginFilter constants to use as property keys in the calls to setInitParameter (and furthermore, since the 2nd argument to that method call is a String, I really have no idea how to format the values).
What parameters should I be setting on CrossOriginFilter to property handle OPTIONS requests? And if I have said anything erroneous above or made any false assumptions, please correct me! I've got very limited experience with this.
Documentation for CrossOriginFilter:
http://www.eclipse.org/jetty/documentation/current/cross-origin-filter.html
Javadoc for CrossOriginFilter:
http://download.eclipse.org/jetty/stable-9/apidocs/org/eclipse/jetty/servlets/CrossOriginFilter.html
Actual Source Code: (sometimes this helps people understand too):
https://github.com/eclipse/jetty.project/blob/jetty-9.2.3.v20140905/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CrossOriginFilter.java
In short, you'll likely want to add OPTIONS to the allowed methods.
(Just like the javadoc says)
FilterHolder holder = new FilterHolder(new CrossOriginFilter());
holder.setInitParameter(CrossOriginFilter.ALLOWED_METHODS_PARAM, "GET,POST,HEAD,OPTIONS");
appHandler.addFilter(holder, "/*", EnumSet.of(DispatcherType.REQUEST));
Now, to address another bug you have ...
This does nothing ...
holder.setInitParameter(CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER,
"true");
That is not an init parameter key. (In fact that is a header name constant for the Access-Control-Allow-Credentials) if you want to allow credentials, then do as the javadoc says.
holder.setInitParameter(CrossOriginFilter.ALLOW_CREDENTIALS_PARAM, "true");
I solved this issue by using the following configuration for the FilterHolder:
FilterHolder cors = new FilterHolder(CrossOriginFilter.class);
cors.setInitParameter(CrossOriginFilter.ALLOWED_ORIGINS_PARAM, "*");
cors.setInitParameter(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER, "*");
cors.setInitParameter(CrossOriginFilter.ALLOWED_METHODS_PARAM, "OPTIONS,GET,POST,HEAD");
cors.setInitParameter(CrossOriginFilter.ALLOWED_HEADERS_PARAM, "X-Requested-With,Content-Type,Accept,Origin,Cache-Control");
cors.setInitParameter(CrossOriginFilter.CHAIN_PREFLIGHT_PARAM, "false");
Chrome sends a "Cache-Control" header, if you do not allow this header with your CORS filter, then the OPTIONS request will not be responded to with the correct headers. Most examples of the CrossOriginFilter online do not include this header.
You can optionally set CHAIN_PREFLIGHT_PARAM to false (default is true). If you set it to false, the filter will respond to the request without sending the request to the Servlet. If you would like to handle the OPTIONS request yourself, you do not need to set this param.
Update: I tried your code exactly but I added the filter on the context handler, not on the app. It works this way.
FilterHolder holder = new FilterHolder(new CrossOriginFilter());
holder.setInitParameter(CrossOriginFilter.ALLOWED_ORIGINS_PARAM, "http://localhost:8100");
holder.setInitParameter(CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER, "true");
contextHandler.addFilter(holder, "/*", EnumSet.of(DispatcherType.REQUEST));
Related
I am getting familiar with both Postman and SoapUI. I already have a doubt. When I make a GET call with from the postman-echo service, I get slightly different responses shown to me in Postman and in SoapUI.
In particular, in Postman I get
"postman-token": "1ef2b330-3a46-4681-a304-d72f020cb194"
This field-value pair is not shown by SoapUI.
Can anyone explain me the apparent difference?
The parameter postman-token being added while you send a request from Postman. So, it's a custom parameter, you cannot expect it to be present with other tools.
If you check Postman doc of General settings They have explained what is that param is for:
This is primarily used to bypass a bug in Chrome. If an XmlHttpRequest
is pending and another request is sent with the same parameters then
Chrome returns the same response for both of them. Sending a random
token avoids this issue. This can also help you distinguish between
request on the server side.
You can disable it from Postman settings. Goto Settings > General > Send Postman Token header.
I am using Spring Boot and While I am able to set the cookie on the server, it is available in the response on the browser, however it is not submitted in the further request, and reason seems to be a sub-domain issue. I will detail it with exact issue and content
Requesting Page: http://stgapi.py.com
it makes a API call -> http://secure.stgapi.py.com
Cookies is set with
Domain-> .py.com Also tried with .stgapi.py.com
path-> /
This is visible in the browser, however in the subsequent call to
http://secure.stgapi.py.com the cookies are not submitted
and hence re-login is requested and enters an infinite loop of login and failure.
Any help is much appreciated, entire web says this is how it works, not able to make it work.
After much brainstorming, figured out that we need to set
("Access-Control-Allow-Credentials", "true")
And client(User-Agent) should also send withCredrentials:true
Further ("Access-Control-Allow-Origin") should not be set to "*" and only one value(origin) is supported.
Reason: Being the above example is considered to be CORS request and not the way it looks at the very first place.
So here's the story, I've got a play framework application that uses org.apache.cxf plugin to provide SOAP services. In my routes file, I have the following:
GET /soap/*path org.apache.cxf.transport.play.CxfController.handle(path)
POST /soap/*path org.apache.cxf.transport.play.CxfController.handle(path)
This routes to one of my own functions that turns the path into another request that will hit my usual controllers. We do this by building up on a WSRequestHolder object. We set headers, query parameters, etc.
This used to work quite well in play 2.2 but with the upgrade to 2.3.8, there seems to be an issue. I've traced it to this line:
Promise<WSResponse> responsePromise = request.get();
WSResponse response = responsePromise.get(2000);
When we make the request (when calling response.Promise.get) the call times out regardless of the timeout set. I was testing with a basic login request and it used to respond in less that 200 ms. I've reproduced the request parameters using postman and the request seems to work fine on it's own but when it's being fired from my webservice, it times out.
I maybe have missed something in the upgrade to 2.2 but I'm not even sure what to debug. It clearly doesn't hit the controller, and turning on play logs at the DEBUG level doesn't even see the request.
Any help would be appreciated.
Update:
I have tested it in dev and prod mode. Both seem to fail in the same place.
I figured it out. The issue was that, during our redirection of the request, we were re-adding the Content-Length header twice, once as the length and once as zero (in an attempt to force the regeneration of the length). Turns out this works in play 2.2 but causes it to hang in 2.3. Making sure to only add the Content-Length header once prevents the request from hanging. Dev/Prod mode tested and working.
I've been looking on the web regarding CORS, and I wanted to confirm if whatever I made of it is, what it actually is.
Mentioned below is a totally fictional scenario.
I'll take an example of a normal website. Say my html page has a form that takes a text field name. On submitting it, it sends the form data to myPage.php. Now, what happens internally is that, the server sends the request to www.mydomain.com/mydirectory/myPage.php along with the text fields. Now, the server sees that the request was fired off from the same domain/port/protocol
(Question 1. How does server know about all these details. Where does it extract all these details froms?)
Nonetheless, since the request is originated from same domain, it server the php script and returns whatever is required off it.
Now, for the sake of argument, let's say I don't want to manually fill the data in text field, but instead I want to do it programmatically. What I do is, I create a html page with javascript and fire off a POST request along with the parameters (i.e. values of textField). Now since my request is not from any domain as such, the server disregards the service to my request. and I get cross domain error?
Similarly, I could have written a Java program also, that makes use of HTTPClient/Post request and do the same thing.
Question 2 : Is this what the problem is?
Now, what CORS provide us is, that the server will say that 'anyone can access myPage.php'.
From enable cors.org it says that
For simple CORS requests, the server only needs to add the following header to its response:
Access-Control-Allow-Origin: *
Now, what exactly is the client going to do with this header. As in, the client anyway wanted to make call to the resources on server right? It should be upto server to just configure itself with whether it wants to accept or not, and act accordingly.
Question 3 : What's the use of sending a header back to client (who has already made a request to the server)?
And finally, what I don't get is that, say I am building some RESTful services for my android app. Now, say I have one POST service www.mydomain.com/rest/services/myPost. I've got my Tomcat server hosting these services on my local machine.
In my android app, I just call this service, and get the result back (if any). Where exactly did I use CORS in this case. Does this fall under a different category of server calls? If yes, then how exactly.
Furthermore, I checked Enable Cors for Tomcat and it says that I can add a filter in my web.xml of my dynamic web project, and then it will start accepting it.
Question 4 : Is that what is enabling the calls from my android device to my webservices?
Thanks
First of all, the cross domain check is performed by the browser, not the server. When the JavaScript makes an XmlHttpRequest to a server other than its origin, if the browser supports CORS it will initialize a CORS process. Or else, the request will result in an error (unless user has deliberately reduced browser security)
When the server encounters Origin HTTP header, server will decide if it is in the list of allowed domains. If it is not in the list, the request will fail (i.e. server will send an error response).
For number 3 and 4, I think you should ask separate questions. Otherwise this question will become too broad. And I think it will quickly get close if you do not remove it.
For an explanation of CORS, please see this answer from programmers: https://softwareengineering.stackexchange.com/a/253043/139479
NOTE: CORS is more of a convention. It does not guarantee security. You can write a malicious browser that disregards the same domain policy. And it will execute JavaScript fetched from any site. You can also create HTTP headers with arbitrary Origin headers, and get information from any third party server that implements CORS. CORS only works if you trust your browser.
For question 3, you need to understand the relationship between the two sites and the client's browser. As Krumia alluded to in their answer, it's more of a convention between the three participants in the request.
I recently posted an article which goes into a bit more detail about how CORS handshakes are designed to work.
Well I am not a security expert but I hope, I can answer this question in one line.
If CORS is enabled then server will just ask browser if you are calling the request from [xyz.com]? If browser say yes it will show the result and if browser says no it is from [abc.com] it will throw error.
So CORS is dependent on browser. And that's why browsers send a preflight request before actual request.
In my case I just added
.authorizeRequests().antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
to my WebSecurityConfiguration file issue is resolved
I build up very simple test plan.
Login: POST, a session cookie is returned.
Get the state: GET, a user state is returned.
Create a resource: POST, JSON body is supplied for the resource.
So my 'Test Plan' looks like:
Test Plan
Thread Group
HTTP Request Defaults
HTTP Cookie Manager
Login (HTTP Request Sampler: POST)
Get State (HTTP Request Sampler: GET)
Create Resource (HTTP Request Sampler: POST)
The cookie generated by 'Login' is added to 'Get State' correctly.
But 'Create Resource' has NO cookie. I changed their order but it doesn't help.
I used the default options firstly and changed some options but it also doesn't help.
Is it a bug of JMeter? or just POST http request is not able to have cookie?
Please give me any advice.
[SOLVED]
I noticed that it is related to the path, not the method.
You'd like to look at the domain of the cookie as well as the path.
I mean, the path and the domain of a cookie could be defined in the server side through Set-Cookie header.
Another solution is to set CookieManager.check.cookies=false in jmeter.properties usually sitting besides the jmeter startup script in bin.
JMeter for some reasons thinks that you can't set the path=/something in a cookie if you are on http:/somesite/somethingelse. That is the path has to match the path your currently on.
I've never seen a browser enforce this limitation if it actually exists. I've seen and written several sites that use this technique to set a secure cookie and then forward someone say to /admin.
I wish this option was at least in the GUI so I didn't have to change the properties file. I think BlazeMeter is smart enough to turn off checking where flood.io is not. If it were up to me I'd just remove the code that checks this entirely. Why make the load tester any harder then it needs to be.
I had this turned on in my Spring Boot server which was causing the issue with CookieManager in jMeter:
server.servlet.session.cookie.secure=true
Removing this made the cookies flow ! Of course this is for localhost. For Production you may need this turned on.