Monday, November 13, 2006

Adding Generic Authorization to a Wicket Application

Before I could get to the point of actually adding an article to the database through my web form, I needed to have an authenticated user. Wicket provides a way to interject another page into the normal flow of an application; this can be done to show an ad prior to the desired page, and it can also be used to require authentication before proceeding.

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 WebApplications which are generally made up of one or more WebPages which in turn are generally comprised of many WebComponents. (I'm simplifying of course.) There is no notion of a global thing that exists outside the set of WebApplications. 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 WebPages 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:


Comments: Post a Comment





<< Home

This page is powered by Blogger. Isn't yours?

Subscribe to Posts [Atom]