0

I have a Java Website using Spring's MVC pattern. The frontend views are JSP files which use Java-Script and jQuery in numerous cases to allow for some smooth design, I guess.

Now, I realised a very specific bug that i wanted to get rid off the last few days but I cannot seem to understand how the given factors are causing it. It is really weird.

What causes the bug

  • On any JSP that contains ajax-Javascript idle until Session-Timeout is reached
  • Trigger the timeout by calling one of the ajax-Scripts (doesnt matter which, only matters that is is ajax)
  • An automatic redirect to the login-page happens, as the server realises that the session was timed out by the time the request was done
  • I logg back into the site and go back to where i previously had called the ajax-script
  • I try to run that ajax-Script again and now am returned a wrong view. I basically have my mainpage returned instead of the usual desired view

I found where in my code this is caused. Each ajax-Script basically makes a POST or GET request to the server using a specific URL. And then i have a typcial Controller on the server that catches this request and procceses it (retrieving necessary data and then return the view supplied with Model-Data).

In all of those Controllers it is first checked whether or not a user is currently logged in. This is because basically all of the ajax-actions retrieve user-specific data and it wouldnt make sense to do so if there is no user logged in anymore. Apparently this check fails, as the result of this check failing explains why i get my mainpage returned. Instead of returning the right view with the right data i am returned a RedirectView onto "/login.html".

The reason i am now shown the Login-Page but my mainPage instead is most likely explained by the fact that the Controller listning to "/login.html" actually realises that a user IS infact logged in and thus returns my mainPage instead of the loginPage.

Note that each and every other controller other than the controller that is responsible for the ajax-Request that triggered the session-timeout continues working as expected! No bugs or weird behaviour with any other ajax or non-ajax Request!

Meaning that only the ajax-Script that had caused the session-timeout is not working. Every other ajax-Script is still working perfectly fine!

Logging back in and out has no effect, the ajax-Script continues to be bugged. In most cases i have to restart the browser in order to make it work again.

Weird stuff that i have found out using FF/IE network-tools

  1. Every ajax-call and ONLY the ajax-calls are sent by the browser using 2 different Cookies!
  2. Each of those Cookies contains a different JSESSIONID
  3. When observing working ajax-Calls the SECOND Cookie sent in the Request-Header equals the Set-Cookie property returned by the Response-Header and the first doesnt!
  4. In non-working calls the FIRST cookie matches and the second doesnt!
  5. IE(9) doesnt have any problem at all. I found out that he sends every single request using 2 Cookies, not only the ajax ones! The bug described above never appears with IE.

To 1.): I found a Filter in my Sourecode. It implements Java's Filter-Class and is apparently ran through by every request sent to the server. It basically only does one thing, which is called rewriteCookieToHeader. This function checks if (response.containsHeader("SET-COOKIE") ) and if that is true it rewrites that property using the current SessionID and also adding the two flags HttpOnly and Secure

This is what apparently causes the appearance of a second cookie. If i comment this out the bug described does not appear anymore! As well as if i change the if(cond) to if(!cond).

However, i feel like this function has a security-purpose. Its used against some Session-Hijacking apparently, as well as i guess that those two flags are a necessary security measurement.

But it makes no sense at ALL that this function causes the appearance of a second cookie! From the HttpServletResponse documentation it says that : " setting SET-COOKIE when it is already set will cause the old value to be replaced with the new one ".

This and the fact that the new cookie that randomly appears in the requests never equals any of the SET-COOKIE values! It just magically appears and it is mentioned nowhere by the server or the function that apparently causes it in any way!

So on the one side i can remove the bug by removing rewriteCookieToHeader() but on the other side i just dont understand how this function could possibly cause what is causes!

I realise that this is a very specific bug and it goes deep into my implementation.

I just hope that someone might get a hint by what i have described and could suggest some further things that i could test out!

Edit: Here are request and response header-example for working (first two) and non-working (last two) ajax-call.

Working call

REQUEST
Host: localhost:9002
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:35.0) Gecko/20100101     Firefox/35.0
Accept: */ *
Accept-Language: de,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Referer: https://localhost:9002/history.html
Content-Length: 22
Cookie: JSESSIONID=EAB07711A78F1086E0FE55963AD973D3;     JSESSIONID=EAB07711A78F1086E0FE55963AD973D3
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
RESPONSE
Content-Encoding: gzip
Content-Language: de
Content-Length: 611
Content-Type: text/html;charset=UTF-8
Date: Mon, 02 Mar 2015 10:05:35 GMT
Server: Apache-Coyote/1.1
Set-Cookie: JSESSIONID=EAB07711A78F1086E0FE55963AD973D3

Non-working call

REQUEST
Host: localhost:9002
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:35.0) Gecko/20100101     Firefox/35.0
Accept: */ *
Accept-Language: de,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Referer: https://localhost:9002/history.html
Content-Length: 22
Cookie: JSESSIONID=705C77CBBE678624BAD25DD97E7D0DF8;     JSESSIONID=E67B1E2486D5410CC1A73B30D4E78206
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache

RESPONSE
Content-Language: de
Content-Length: 0
Date: Mon, 02 Mar 2015 10:17:14 GMT
Location: https://localhost:9002/login.html
Server: Apache-Coyote/1.1
Set-Cookie: JSESSIONID=705C77CBBE678624BAD25DD97E7D0DF8
Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
Mercious
  • 378
  • 3
  • 25
  • It would help if you can add the request headers of ajax calls that you think are failing – Arkantos Mar 01 '15 at 13:59
  • I can do that tomorrow as i will have access to the whole thing again. What are you looking for in them though? I have checked them and they are just like any other, except for the fact that they contain two cookies instead of one. The URL is the same, the request parameters are the same and the response is a 302 which can be explained by my server returning such RedirectCView. And i missing an information that you could possibly look for? – Mercious Mar 01 '15 at 14:09
  • ok how exactly are they setting the `Set-Cookie` response header back in response in your filter ? Are they using `setHeader()` or `addHeader()` ? – Arkantos Mar 01 '15 at 14:21
  • That could have been a thing indeed yea, but the Filter is actually using setHeader() which should be correct. – Mercious Mar 01 '15 at 14:28
  • Ok perhaps a snapshot of those request and response headers will give a better insight :) – Arkantos Mar 01 '15 at 14:33
  • Alright, i will post them tomorrow, thank you :) – Mercious Mar 01 '15 at 14:44
  • I have added an example request and response header for both cases now. As you can see, everything looks normal, doesnt it? – Mercious Mar 02 '15 at 10:20
  • Like you said, the second Ajax call is returning 302 response but Ajax requests cannot handle 302 responses, if it's a normal `HttpRequest`, browser will use the `Location` header in response and fires a new request for that URL, but `XMLHttpRequests` aren't capable of doing that :) – Arkantos Mar 02 '15 at 13:55
  • I understand, however the server should not be sending a 302 in any case. There is no reason for it to return a 302, the resource hasnt moved, its wrong behaviour. – Mercious Mar 02 '15 at 22:47
  • Server cannot commit a 302 on its own, somewhere in the code there must be an explicit call to `response.sendRedirect(url)`, check on that – Arkantos Mar 03 '15 at 04:25
  • There is, yes. But it is miss behaviour and it is somehow caused by the two cookies thing. The reason the server returns 302 is because he thinks the user is currently logged out and thus returns a RedirectView to login.html. But the user is not logged out, he is correctly logged in and the server realises it correctly for every call except that single ajax-call that previously triggered the timeout. – Mercious Mar 03 '15 at 08:14
  • Ok in the non-working call, the JSESSSIONID values are different, do you know why that is happening ? – Arkantos Mar 03 '15 at 08:32
  • Nope, i have absolutely no clue where the second JSESSIONID is coming from. It could only possible come from the Server setting SET-COOKIE and its value to "JSESSIONID=", right? But i have checked my server logs and those new JSESSIONID values that magically appear never equal any of those mentioned in SET-COOKIE by my server. Yet the rewriteCookieToHeader() function is the function causing it. If i remove it, i have no problems. But it doesnt make sense that this function causes this!! – Mercious Mar 03 '15 at 10:33
  • Are they calling addCookie() on response in rewriteCookieToHeader() ? Is it possible to post that method or atleast the part which you think might be causing the issue ? – Arkantos Mar 03 '15 at 10:58
  • Meh i cant format this here into proper code. But the code is a 4liner: RetrieveSessionId(); RetrieveContextPath(); response.setHeader("SET-COOKIE", "JSESSIONID=" + sessionId + "; Path=" + contextPath + "; Secure; HttpOnly" ); and then a log-entry. – Mercious Mar 03 '15 at 16:45
  • Well I guess you need to track how and when the cookie value changed. These are `HttpOnly` cookies so you can only find them in Headers. Just capture all the Network calls and see request & response headers. One of them should have a Set-Cookie with a different JSESSIONID – Arkantos Mar 03 '15 at 17:13
  • I guess i will try to find what i can ;) I still need to examine what exactly happens during session-timeout, however its hard to do because i always get automatically redirected, which means whatever networktool i run is also refreshed. I guess i will have to use an external (not browser plugin) one to see. – Mercious Mar 03 '15 at 22:02
  • You can use HttpFox in FireFox or enable preserve log option in Network in Chrome Dev Tools. Both will save network calls between page refreshes – Arkantos Mar 04 '15 at 03:24
  • Alright, cool. Using said tools i actually gained new insight. Let me walk you through, weird stuff: Navigate through website until on Page with Ajax. Each Request is with Cookie A, each Response is with Cookie A. Then, call Ajax, Req(A), Resp(A). Call it again, Req(AA), Resp(A) [Yes, Req contains A cookie twice]. Call Ajax that triggers session-timeout, Req(AA), Resp(B). Redirected to login, Req(A), Resp(C). Login with Data, Req(C), Resp(C). Navigate to ajax. each Req is with C. Call ajax, **Req is with CB as Cookies** Resp is still with C. Understood that? – Mercious Mar 04 '15 at 08:44
  • Sorry, the last Response is with a new Cookie D again. So it seems that everytime i trigger a session-timeout with ajax, the server returns a new cookie. Which makes sense, because there is a new session. The browser adapts to this for every normal request, but when i get back to that ajax call that previously caused the timeout the browser suddendly brings up the cookie from before the session-timeout! How is this possible? – Mercious Mar 04 '15 at 08:53
  • One thing I find unusual is that in every response, they're setting `Set-Cookie` header. We usually do it only once in login success response, there after any subsequent calls will have the same Cookie in request headers. Also it's not a good idea to send a new Cookie-B even in session-timeout response, which is again replaced with a new Cookie-C after login – Arkantos Mar 04 '15 at 08:56
  • One more thing to notice here is 302-redirect response. If you get 302 response in a normal HttpRequest, browser looks for `Location` header in response, fires a new call to that new URL and discards all the `HttpOnly` cookies used till now. But Ajax calls cannot handle 302 response so it's still sending old cookies as it failed to notice 302 and clear all cookies so far. Makes sense ? – Arkantos Mar 04 '15 at 09:06
  • I dont really know why we set SET-COOKIE with every respone, as far as i heared it serves a security-measure or something? And honestly, i dont know what exactly handles the Session-Timeout in the code. The project is based on Hybris, so i strongly expect that this is internal implementation by the Hybris-Software. I will see if i can find anything, but i doubt it. I understand, yes. Are you sure about that though? I mean, my browser does redirect to Location of the Header sent back? But yes, maybe that is part of the problem, that the ajax-call doesnt clear cookies after 302 – Mercious Mar 04 '15 at 09:14
  • Can ajax not handle any kind of redirect-response or only 302 in specific? – Mercious Mar 04 '15 at 09:15
  • I'm not sure about other responses but it cannot definitely handle 302 – Arkantos Mar 04 '15 at 09:35
  • Have a look at [this](http://stackoverflow.com/questions/15996779/cannot-handle-302-redirect-in-ajax-and-why#15996968) for a 302 workaround for Ajax calls :) – Arkantos Mar 04 '15 at 09:41
  • Jep, i just found that myself, thank you :D I will try and see if i can implement this. You know, i still think Hybris implemented the Session-timeout handeling, i only have acess to Filters, and as far as i can see those dont check for session timeouts. Thanks a lot for your help, i will report back after i have implemented what you suggested and see if it helped the case or not. I think even if it doesnt solve the problem it still should be implemented because its the proper way of handeling session timeouts with ajax calls, i guess! – Mercious Mar 04 '15 at 09:48
  • The answer in that link specifies exactly, how to handle it. In case of Ajax calls, jQuery sends an additional custom header `X-Requested-With` in request, if it's there, if the user is not authenticated, instead of sending 302 you can send a 401 response with a new URL in response and in your Ajax callback, you can read that url and update window.location with that or use History API. You can do all these response changes in a Filter :) – Arkantos Mar 04 '15 at 09:52
  • Yes, i understand, but my problem is not that i dont know HOW to respond to it correctly, i dont know WHERE. I still have to find the responsible code in the project that handles Session-Timeouts. I never did those and at the moment it looks like they are handled auotmatically. As soon as i found the code that checks if a session is timed out i can implement what you suggested. – Mercious Mar 04 '15 at 10:20

0 Answers0