Monday, November 13, 2006
Adding Generic Authorization to a Wicket Application
Unfortunately the wicket example page for sign-in is very single-application-centric. It uses a Session object created via overriding the
WebApplication
's getSessionFactory()
method and then it performs authentication via methods in that Session
object. This works if you only have one application on your web site, but it falls down pretty quickly if you want to do web-site-wide authentication. I want my users' logins to work for all applications once they've signed in once. I accomplish this now by sticking a User
object in the session once a login is completed successfully.Unfortunately, Wicket doesn't have a notion of a thing higher in its hierarchy than an
WebApplication
. There are WebApplication
s which are generally made up of one or more WebPage
s which in turn are generally comprised of many WebComponent
s. (I'm simplifying of course.) There is no notion of a global thing that exists outside the set of WebApplication
s. Furthermore, a Session
object is not a javax.servlet.Session, but rather a Wicket invention that abstracts away things like getting the HttpSession
from the request and putting things in it. These Session
objects are, again, very WebApplication
-specific; you create them by coding up a subclass and returning it via a session factory.So how do we get something to live in the
HttpSession
outside of the influence and control of any single application but still usable from those applications? The answer lies in this rather ugly piece of code:
HttpSession session = ((WebRequest)(WebRequestCycle.get().getRequest()))
.getHttpServletRequest().getSession();
Even though the Wicket framework tries to abstract away many things and handle a lot of menial plumbing for you, it doesn't completely tie your hands in the process. If you need to stick something in the user's HTTP Session, you still can... but beware-- once you step outside the framework, you're on your own for things like type checking, null handling, serialization, etc.
Because Wicket finds
WebPage
s via their class, it's quite easy to create a new package and put the class and HTML markup needed for the signin form in it. I started by taking these directly out of the Wicket example.The next step was to create an implementation of
IAuthorizationStrategy
(there's that stupid I
prefix again). This is the way authorization is wired into an Application: getSecuritySettings().setAuthorizationStrategy(
instance of IAuthorizationStrategy);
That goes in the
WebApplication
's init()
method. In the Wicket example they create this on-the-fly with an anonymous inner class. I think anonymous inner classes are hideous. What, was it too hard for you to click "New --> Class" in your IDE? Do you have to pay for your disk storage by the bit? No tabs in your editor? Honestly, make a damn concrete class. It won't kill you, and it makes your code far easier to read.I created a class called
SigninAuthorizationStrategy
. I didn't stick the abbreviation "impl" in the name, because, like prefixing interface names with I
, that behaviour is stupid and pointless. You know by the fact that the class declaration says "implements
" that it's a bloody implementation.As Wicket adds
Components
to your WebApplication
, it calls your public boolean isInstantiationAuthorized(Class componentClass)
method, passing in the class of the component that it's adding. This happens recursively, so you'll get a call for the WebPage
, then every WebComponent
on the web page. We don't want to have to authorize every single component on the page, so we pass in the class of components we do want to authorize and ignore the others:
private Class> componentTypeToAuthorize;
public SigninAuthorizationStrategy(Class componentType) {
this.componentTypeToAuthorize = componentType;
}
public boolean isInstantiationAuthorized(Class componentClass) {
if (!(componentTypeToAuthorize.isAssignableFrom(componentClass))) {
return true;
}
We return true because we are assuming that we're doing authorization at a high level of containment; that is to say, we're authorizing at the page level, and if the page is authorized, then its contained components are assumed to be authorized as well. There's no point in spending the cycles authorizing them too. The code goes on to say:
UserBean userBean = Utils.getUserBean();
if (userBean != null && userBean.isAuthenticated()) {
return true;
}
throw new RestartResponseAtInterceptPageException(SignIn.class);
}
Utils.getUserBean()
is just a convenience method to handle that ugly HttpSession
bookkeeping we saw earlier. The rest is pretty straightforward: if there's a UserBean
in the HttpSession
and it has been properly authenticated, then we authorize this component (in this case a WebPage
). Otherwise, we throw the quite-verbosely-named RestartResponseAtInterceptPageException
passing it the class name of the page we want to use as the intercepting page.SignIn.class
in turn handles the web form for signing in, authenticating via the UserService
and adding the resulting UserBean
to the user's session. Once this is complete, Wicket automatically resumes control where it left off via this call:
if (!continueToOriginalDestination())
{
setResponsePage(WebRequestCycle.get().getApplication().getHomePage());
}
If for some reason we're unable to continue to the original destination, we divert to the application's home page instead. This should probably not ever happen.
You can find the full source on the Tally-Ho project homepage. Look in net.spatula.news.ui.signin and net.spatula.news.article.create.
Labels: software
Subscribe to Posts [Atom]