I am using Spring Security because it is excellent, and whilst I am
aware that there are many other solutions available this is not the
focus here, so I'll just call it Security from here on.
Security offers protection of URLs and also some more fancy AOP style
protection like protection of specific functions. To keep things
simple here lets just look at protection of URLs.
1. Without Ajax - Protecting URLs that relate to pages
Before the days of Ajax where a URL effectively corresponds to a state
you can see how the model becomes straight forward. Adam accesses /
home.html, it loads and Adam gets to read a bunch of content. At this
point Adam is considered to be a Guest. Adam is curious about a link
called "Administration", when he clicks it he is sending a Request.
That Request has some header info embedded in it (which the user never
sees but you can see if with Firebug or whatever). Sadly Adam is not
signed in so Security redirects him to a login page, but remembers
everything about that original Request. Adam suddenly remembers he
does have a user name and password so he logs in. Security processes
his login and then redirects him to the exact equivalent of what would
have been the original Request. Adam is happy because his work flow
continued seamlessly despite being asked to login.
(Ok, Spring Security can be configured to replay the original Request
upon successful login (default), or it can be forced to send you to a
particular default login success page. If you use the latter then
Adam's work flow will not be seamless because his original request for
the "Administration" page has been forgotten. Though sometimes you
might need a set up like this?)
Now Ajax comes along, and in particular I'm going to talk about GWT
here but the principles apply.
2.a. With Ajax - but still protecting URLs that relate to pages
If we use Security to protect the URL of container page (in GWT that's
the page that hosts the bootstrap javascript code for your GWT app)
then, just like normal if Adam visits /home.html he will be able to
read the content on that page. The "Administration" link has been
modified to take you to /admin.html which is in fact the host page the
GWT administrator module. Adam clicks "Administration" which Security
knows requires the Admin role so he is redirected to the login page.
Adam logs in and whether Security is configured to play back the exact
Request or whether you specifically move the browser along to /
admin.html, at this point doesn't matter. Adam is now looking at a
fully loaded GWT application. Lets say also that Security is told to
protect every GWT RPC call. Well Adam is logged in so he is allowed
access to the server services via GWT RPC and all goes smoothly...
until Adam leaves his computer for an hour and comes back to continue
his work. When the next server call that occurs is met by Security who
says, since your session has expired you are not logged in, so I'm
going to redirect you to the login page. XmlHttpRequest obeys the 30x
response code which is telling it to actually load the resource at /
login.html. In GWT this amounts to an InvocationException where the
error message content is your /login.html page markup. That's no good!
The XHR cannot interpret this as your browser would, so what to do?
The answer is pretty straightforward here. Design your /login.html
page to contain a text token that you can search for. So for example
in my /login.html code I have places an html comment right at the top
like this
<!-- SECURITY-LOGIN-PAGE -->
so when the browser hits the /login.html page it just looks like a
regular login page. But when the RPC call encounters this
InvocationTarget exception I have an onFailure handler that looks like
this:
public void onFailure(Throwable t) {
if (t instanceof InvocationException){
if (t.getMessage().split("<!--\\s*SECURITY-LOGIN-PAGE\\s*--
>").length > 1){
onLoginRequested();
} else {...}
} else {...}
}
My onLoginRequested() method could be any of a few styles. The most
obvious is to have it call a native method that looks like this:
private native void redirectToLogin() /*-{
$wnd.location.replace("/login.html");
}-*/;
Now at this point we have to get clear on the behaviour of Security
with regard to where it redirects the browser after successful login.
In this case you would have to specify that it ALWAYS redirects you
to /admin.html so that when Adam signs in again he is taken back into
the Administration app. But sadly his page state (history tokens) is
lost and he's back on the front screen of your app and is now required
to put the app back into the state it was before (i.e. Adam may have
had some popup open, some menu item open, etc. Now he's back on the
clean front page).
If you're wondering what would have happen if we didn't tell Security
to force the successfully logged in user back to /admin.html, but
instead allowed Security to replay the orignal Request, wouldn't we
end up back in the app with page state preserved? No! Remember that
the Request that causes us to be taken to /login.html in the first
place was the RPC call. So if Security were to do Adam the favour of
redirecting his browser to reflect the original Request then the
browser would be attempting to display the contents of the RPC call's
response! eww... not what we wanted. That's why the solution here is
to force Security to redirect any and all successful logins to /
admin.html. Disadvantage is Adam's workflow is interrupted.
2.b. With Ajax - iframe style login (in place login rather than the
browser being redirected to /login.html)
Ok, so how about this: When Adam first clicks "Administration" he is
taken to the /login.html page, upon successful login he is redirected
to /admin.html. Now, as above, if he waits an hour and the tries to
perform and RPC call he receives the /login.html page via the RPC
response, as above, but instead of doing $wnd.location.replace("/
login.html"); and sending the browser off to the login page, we simply
show a nice, pretty, GWT popup that contains an iframe that points to /
login.html. In the background Adam is still looking at the GWT
Administration app and Adam's workflow (page state) is preserved but
he must answer the login iframe before he can continue. Implementation
is up to you but I would suggest that you would have the iframe load
up /login.html and then upon clicking login two things happen, the
iframe sends the login request to Security who authenticates, and at
the same time the popup containing the iframe is removed from the DOM.
Adam can then continue with what he was doing before. Still you can't
take advantage of asking Security to replay the original request
because that would be like telling the iframe to navigate to the page
that corresponds to the original RPC call (same problem as above,
makes no sense). Ok firstly I must say I can see some issues with this
approach, like telling the Login button to tell the GWT application in
the parent frame to close the popup?! Unwieldy in my opinion. Also
note that if login fails what should happen is that the popup iframe
shows up again to ask you to log in again. But does this happen upon
unsuccessful login, or does the popup silently disappear and then only
when Adam tries to execute an action that requires the RPC call again
then we get the popup iframe login once more? Seems like this approach
carries the advantage of preserving application state but still uses
the pre-Ajax still approach.
3. With Ajax - protecting only RPC calls, but not protecting host page
So anyone can visit /admin.html and the GWT app will load (the GWT is
just Javascript so who cares if the app can load, it's the data/RPC
call that we want to protect, isn't it?).
Ok so the app loads and looks like it's ready to use, but as you
invoke an action that requires an RPC call Security says, Adam, you're
not logged in. Because this example is totally Ajaxified we will still
allow Security to redirect out requests to a login URL, but this time
our login page does not contain a valid HTML login form, it just
contains a token like PLEASE-LOGIN. To keep things clear lets say that
we save this file as /login.response. So Security redirects the RPC
call to /login.response. As before, GWT is unaware the redirect taking
place, but, as before, in our onFailure method we look for
InvocationExceptions and then check the exception's .getMessage()
string and test if equals PLEASE-LOGIN. If it does then we program a
function in GWT to show a nice, pretty, GWT popup that comprises a
form. The form elements are a username input box, password input box
and login submit button. The form action will be Security's access
check URL (by default in Spring Security this will be "/
j_spring_security_check"). If login is successful then Security would
redirect the browser to /admin.html, oops! That's not what we wanted.
What we really wanted is a way for that popup form to be submitted,
hidden and Adam is able to seamlessly continue with his work flow.
I have used this approach in an large intranet application before and
here is the result. Adam hits the /admin.html URL and the GWT app
loads. He then clicks login and the popup form appears (ok, this is
Adam hitting login, but equally he could try an action and it would
fail and the login popup would show). Now, it was quite a nuisance for
Adam that when his session expires and he logs in using the popped up
form he is taken back to the apps front page. So, not to be put off by
the failure of this approach, I built in a ping system, so the GWT app
periodically (every 5 minutes) pings the server via a very simple RPC
call but to tell it "hey, still here, don't lose my session!". The
problem with this is that we are solving the problem by overcoming the
session time out problem. That's ok but not really what we want - not
to mention some purists would say that this is an abuse of the session
timeout function. I'm not sure where I sit on that argument, because
session timeout does two things - 1. ensure that if you walk away from
your computer and then later a rogue colleague sits are you computer,
the system protects itself by having timed out and requiring login
again, assuming an hour elapsed. 2. allows the server a way to garbage
collect old sessions so it's memory isn't taken up by session
information from last week/month/year. If 2. is all you care about
then the server ping approach works fine.
4. With Ajax - logging in using an RPC call
This would be the silver bullet, wouldn't it? I doubt (though maybe I
am wrong) that you could employ the traditional style of protection/
login as mentioned in 2. to protect your host page "/admin.html" and
then if successfully logged in you gain access to the host page and
all of the RPC calls as well, but when your session expires you get
the nice, pretty, GWT popup that then does its second login via an RPC
call?! This would require that the URL of your RPC login service would
have to have public access, but all your business services would need
Admin access.
The advantages here are
- admin app is protected
- RPC calls are protected
- if session times out and you attempt an RPC call then your
onFailure code would know to show you the login popup
- if you successfully login then the popup hides and you may continue
with your work. Page state/work flow was never interrupted. At worst
you'll need to repeat your last action, which may be clicking "Save"
again, or whatever, I don't know if we could get the RPC call Request
to replay automatically, your thoughts?
If this is possible then please share. If it is not, then I would
still like to explore the authentication over RPC technique, and would
be happy to forego having the /admin.html page protected. Like I said,
in most apps it is the interaction with the server that must be
protected, it is not crucial to protect your Javascript application
code. (unless it is ;) ).
I hope this helps someone, and in return I hope someone can help me
with approach number 4. Note that approach number 4 will require some
server side set up, so will require some Spring-specific modifications/
changes of default behaviour.
Happy coding,
Paul S
I now have a set up that resembles Scenario 4.
In my current set up Spring Security is still configured to respond
with a valid html form at /login.html (with my <!-- SECURITY-LOGIN-
PAGE --> token hidden in it). So both page requests and XHR Ajax
requests are subject to Spring Security's declarative set up. Whether
I arrive at the host page and am not authenticated, or if I
authenticate and then after my session has timed out I try to call an
RPC service, I will be met with the /login.html contents as the
response. Ok, so the only trick now is that in my GWT app if I get an
InvocationException that equates to Spring Security trying to redirect
me to the /login.html page then I simply show a nice looking GWT popup
that looks like this:
Sorry, your session seems to have expired
Username: __________
Password: __________
Login
And when the user clicks Login an RPC call to the server is shot off
that looks a bit like this:
public interface LoginService extends RemoteService {
boolean login(String username, String password);
}
Note that the boolean it returns isn't the real business here. The
real business is that we now have an authenticated session on the
server. The boolean returned just tells our GWT app to hide the popup
(if true) or keep showing it and tell the user to try log in again (if
false). That's all.
On the server I then have a service implementation that performs the
login very similarly to what Kent has done:
@Override
public boolean login(String username, String password) {
log.debug("Authentication attempt: " + username + " " + password);
try {
Authentication request = new
UsernamePasswordAuthenticationToken(
username, password);
Authentication result =
authenticationManager.authenticate(request);
SecurityContextHolder.getContext().setAuthentication(result);
} catch (AuthenticationException e) {
log.info("Authentication failed: " + e.getMessage());
return false;
}
log.info("Authentication success: " +
SecurityContextHolder.getContext()
.getAuthentication());
return true;
}
... where authenticationManager is injected by Spring. To show you
exactly where I am getting authenticationManager from, here's my
context configuration:
<authentication-manager alias="authenticationManager">
<authentication-provider>
<password-encoder hash="md5" />
<jdbc-user-service data-source-ref="dataSource" />
</authentication-provider>
</authentication-manager>
I am still undecided whether it is right to have two logins,
1. The initial login (traditional web login that moves me from /
home.html --> /login.html -->(login success)--> /admin.html)
2. A secondary login that happens over RPC that provides a quick,
convenient login via an Ajax popup, such that it doesn't interrupt
your workflow
The second is exactly what I wanted all along! Is the first redundant?
Or is it just as well to have it. i.e. 1. protects your web app code
(well the host page), 2. protects the good bits (RPC calls!!!)
Like this:
<intercept-url pattern="/**/login.rpc" access="permitAll" />
<intercept-url pattern="/**/*.rpc" access="isAuthenticated()" />
On Feb 19, 5:28 pm, Paul S <paulsschw...@gmail.com> wrote:
> I got some ideas from Kenthttp://stackoverflow.com/questions/1013032/programmatic-use-of-spring...
> who in turn got some ideas from Spring Security 3 Technical Overviewhttp://static.springsource.org/spring-security/site/docs/3.0.x/refere...
- See Wah's code allows you to have protection for your RPC calls
- My code allows you to have protection for regular URLs (e.g. /
admin.html), and RPC URLs, and method invocations
- See Wah's code requires you to authenticate yourself via an RPC call
- My code allows you to authenticate yourself via a regular /
login.html page, but then once you're in the GWT app, if your session
expires you are offered an Ajax style login (like See Wah's).
I guess I'm homing in on what I would consider to be my choice of
approach. Keep those thousands of responses coming, folks! ;)