Matthew Fleming (Migrated from SEC-1007) said:
If multiple requests to secured resources occur concurrently, the CAS component does not handle the last redirect properly. Here’s the behavior:
1. Let’s say that three concurrent requests to different secured resources come in for a user that has already authenticated to a CAS server.
2. Each request will hit the spring security layer and not have an authenticated user in the session.
3. Each request will call the CAS login url like this: https://cas-server/cas/login?service=blah
4. Since the user has authenticated to CAS the response from each request will be a different service ticket.
5. The spring security layer then correctly calls the validation url with the service ticket.
6. CAS server returns the authenticated user in each request (in our example three responses come back)
7. The spring security layer then needs to get the original request made in step #1 (when alwaysUseDefaultTargetUrl is false).
8. The SavedRequestAwareWrapper is consulted and a single redirect URL is returned from the user’s HttpSession.
The problem is that the SavedRequestAwareWrapper uses the user’s session with a single key to store the URL. This means that there can only be one URL per user for ticket validation. A race condition determines which URL is ultimately redirected to.
We’ll need to create a better way to track the original secured request in order to solve this.
Matthew Fleming said:
So the end result of all of this is that in the example above, all three requests end up ultimately making the same last request. If you think AJAX here, you’ll see the problem. Three different data calls to secured resources all returning the first request’s response. In any event the incorrect last request shouldn’t be made.
Luke Taylor said:
I don’t think this is a critcal bug – it is more a limitation of the use of SavedRequest and there is a limitation on what’s possible with on-demand authentication.
It should be possible for your application to authenticate the user before you decide to use ajax.
Scott, can you comment on the possible changes we could make with CAS to allow CAS to supply the redirect URL. Perhaps we could allow this as an option, though it may cause problems with POST data etc.
“It should be possible for your application to authenticate the user before you decide to use ajax”
This is partially true. In our application the html pages (and static content) are served only from apache and not from the application server (tomcat). I could of course have tomcat serve up the static content and make the html pages secure resources and not run into this bug. However, even if the html page is secured, we could still hit this bug. If the user’s http session times out in the application and the user hits a button on the page that refreshes a couple of tables created using AJAX calls, the same bug will happen. I guess the point is that the increase in AJAX adoption by enterprise applications will increase the incidence rate of this kind of thing.
Scott Battaglia said:
Is this a CAS component issue or is this an issue further up the Spring Security Chain (since a bunch of stuff relies on saving to the session)?
Scott and I have discussed this a bit. I think for things like form login, it’s reasonable to assume that you can only have authentication on-demand (i.e. prompted by accessing a protected resource) for a single request. I guess the difference with CAS is that the developer may know (somehow?) that the user has aready logged in to CAS when they access a particular app, even though Spring Security doesn’t yet consider them to be authenticated. However, you are getting into all sorts of potential issues with submitting multiple requests when you have no session – likewise if your application times out and you lose the session and then attempt to just continue where you left off.
If you submit multiple requests, how will you guarantee that they have the same session? You will probably end up creating multiple sessions at once. It’s true that more applications are using Ajax these days and this introduces a lot of new challenges but I think it’s up to the client-side to deal with this kind of issue more intelligently, rather than assuming that the server should just be able to deal with things like session timeouts transparently. The server-side isn’t really in a position to serialize requests in a generic way.
So I don’t really think this we should consider this to be a bug. If you have specific suggestions on possible code changes then let us know.
I’m not sure if two sessions are created initially but at the end of the chain there is only one (there’s only one cookie). If multiple sessions were kept for each XHR then things would work properly from a user’s standpoint (the response would actually be valid) when ticket validation happens. The whole problem is that in the single HttpSession we only can keep one redirect URL for all of the requests.
The bug is that when alwaysUseDefaultTargetUrl == false, Spring Security is supposed to make the original request again after authentication. In the case of CAS, it is isn’t; with other non-redirecty kinds of authentication it does.
“If you submit multiple requests, how will you guarantee that they have the same session? You will probably end up creating multiple sessions at once… The server-side isn’t really in a position to serialize requests in a generic way”
I think this is the right description of the problem. In fact what does happen is that when the 3 requests in my example get submitted to the back end, three sessions get created. The instant we have more than one session, tracking across http redirects becomes impossible.
What we’ve done is to make sure that our getLoggedInUser() AJAX call is always executed synchronously prior to any other AJAX calls. If the response is good from the getLoggedInUser() call, all of the other AJAX calls are released asynchronously at that point. If the getLoggedInUser() call returns the login page, we redirect the browser window to the login page. If getLoggedInUser() returns an error or can’t parse the response, we show the error page.
Thanks for posting your solution, Matthew. I’m closing the issue now ‘cos I think it’s clear that this kind of thing is best handled client-side.