Thursday, February 26, 2009

Wicket / Swarm / Spring Security How-to

This post explains how to integrate Wicket and Spring Security. It is based on an older article in the Wicket Stuff wiki (not written by me), which covered the now deprecated Acegi. I've rewritten my own prototype, using recent versions of the frameworks. The complete source is available here.

About the frameworks

  • Apache Wicket is an open-source web application framework for the Java platform. This example uses version 1.4-rc2.

  • Wicket-Security is an extension of Wicket. It consists in: WASP (Wicket Abstract Security Platform), an API that allows individual Wicket components (pages, links, form widgets, etc.) to do their own authentication and/or authorization checks; and SWARM, the default implementation of WASP. This example uses version 1.4-SNAPSHOT.

  • Spring Security (formerly Acegi) is a framework to secure enterprise applications developed using the Spring framework. This example uses version 2.0.4, which depends on Spring 2.0.8.

Why use Spring Security AND Swarm?


Assuming your web application uses both Wicket and Spring, their respective security frameworks complement each other nicely:
  • Swarm provides fine-grained authorizations for your Wicket pages and components; for example, if you want a link to be active only when the user has certain privileges. But if you need to secure non-Wicket web resources (for instance, web services), you can use Spring Security's URL-based filtering.

  • Swarm has an API to perform authentication, but does not provide authentication modules out of the box. On the other hand, Spring Security has a number of Authentication Providers for common authentication sources: DAO-based, LDAP directory, OpenId... (see chapter III of the online manual).

  • Spring Security has powerful mechanisms to secure the business side: annotation-based method autorizations and domain object security.

About the example


The example application aims at illustrating various aspects of security, while staying as simple as possible:
  • it defines two roles (ROLE_ADMIN and ROLE_USER), and two users (admin, who has both roles, and user, who only has ROLE_USER).

  • authentication is performed by a Spring Security Authentication Provider.

  • a successful login redirects to a page displaying the user's name and roles. This page also has an administration panel only displayed for, er... administrators.

  • finally, we use a business service. As it returns a very strategical piece of information, its use is reserved to administrators. The administration panel displays the result of a call to the service.

Getting started: configuring Spring Security


We bootstrap the Wicket application with the standard Maven 2 Archetype, as described in QuickStart.

Our Spring Security configuration is directly inspired by the framework's tutorial sample. Apart from declaring the dependencies in the POM, we add a few elements to WEB-INF/web.xml:
  • the listener to load the Spring Security context:
    <context-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>/WEB-INF/applicationContext-security.xml</param-value>
    </context-param>
    ...
    <listener>
      <listener-class>
        org.springframework.web.context.ContextLoaderListener
      </listener-class>
    </listener>

  • and the Spring security filter (the mapping must appear before the Wicket filter's):
    <filter>
      <filter-name>springSecurityFilterChain</filter-name>
      <filter-class>
        org.springframework.web.filter.DelegatingFilterProxy
      </filter-class>
    </filter>
    ...
    <filter-mapping>
      <filter-name>springSecurityFilterChain</filter-name>
      <url-pattern>/*</url-pattern>
    </filter-mapping>

In WEB-INF/applicationContext-security.xml, we configure the Spring context used for security. This is done in a very concise and expressive manner thanks to Spring's namespace configuration feature:
  • the snippet below will create the beans necessary for the security filter. Note that we tell Spring Security not to secure any URL, since this will be taken care of by Swarm. Of course, if we had to protect web resources other than those served by Wicket, we would need a way to distinguish those URLs (different prefix / extension...) and a finer-grained configuration here.
    <http auto-config="true">
      <intercept-url pattern="/**"
        access="IS_AUTHENTICATED_ANONYMOUSLY" />
    </http>

  • for simplicity's sake, we use the in-memory authentication provider, which lets us define users and roles directly in the configuration:
      <authentication-provider>
        <user-service>
          <user name="admin" password="admin"
            authorities="ROLE_ADMIN, ROLE_USER" />
          <user name="user" password="user" authorities="ROLE_USER" />
        </user-service>
      </authentication-provider>

  • finally, the following line enables @Secured annotation support:
    <global-method-security secured-annotations="enabled" />

We can immediately take advantage of that last point in our business service (which, by the way, is a dummy implementation):
public class AdminService {
 @Secured("ROLE_ADMIN")
 public int getResult() {
  return 42;
 }
}

Making our Wicket Application a Swarm application


As explained in Getting started with Swarm, the easiest way to do this is by extending SwarmWebApplication:
public class WicketSpringSecApplication extends SwarmWebApplication
This requires implementing three new methods. getHiveKey()and setupHive() are detailed in the page linked above, so I'll let you refer to it. The third one simply specifies our login page:
public Class<LoginPage> getLoginPage() {
  return LoginPage.class;
}
The login page contains a form, which onSubmit() method will call the Wasp API for authentication (more on this later), and redirect to the home page if it is successful.

Connecting Swarm authentication to the Spring Security provider


Time to bridge the gap between the two security frameworks. First of all, a bit of terminology:
  • with Swarm, you pass a throw-away instance of a subclass ofLoginContext to WaspSession.login(), and get a Subject in return.

  • with Spring Security, you pass an AbstractAuthenticationToken to an AuthenticationManager, and get an Authentication object in return.
We need a couple of adapter classes to make all this work together. They can be found in the package com.example.wicketspringsec.security.spring:
  • SpringSecuritySubject is a Subject implementation that can be constructed from an Authentication. In other words, it translates Spring Security's response to something Wasp understands. I won't detail the code here, it's just a matter of converting the list of roles (GrantedAuthoritys to Subjects). If you look at the source right now, you'll see that I introduced a custom NamedSubject interface; that's just an implementation detail, to which I'll come back later.

  • SpringSecurityLoginContext is a Swarm LoginContext that delegates authentication to Spring Security. The core logic is resumed in these 3 lines of the login() method (I'll explain how we get the authenticationManager instance in just a minute):
    Authentication authResult = authenticationManager.authenticate(token);
    [...]
    NamedSubject wicketSubject = new SpringSecuritySubject(authResult);
    [...]
    return wicketSubject;

  • to be completely generic, we designed SpringSecurityLoginContext to be constructed from an AbstractAuthenticationToken. As our sample application uses form authentication, we define a simple subclass that takes just a login and a password: SpringSecurityUsernamePasswordLoginContext.
So how do we get hold of the AuthenticationManager reference needed to perform authentication? Since we used namespace configuration in WEB-INF/applicationContext-security.xml, the Spring bean's ID not directly visible. Fortunately, the namespace provides us an element to create an alias to the bean:
<authentication-manager alias="authenticationManager" />
To make this bean easily accessible from our custom security classes, we're going to inject it in the Wicket Application object. We start by making this object a Spring bean. To keep this separate from the security configuration, we do this in a new file WEB-INF/applicationContext.xml (which needs to be added to the contextConfigLocation parameter in WEB-INF/web.xml):
<bean id="wicketApplication"
   class="com.example.wicketspringsec.ui.WicketSpringSecApplication"/>
For Wicket to find this bean, we need to go once again to WEB-INF/web.xml
and change the declaration of the Wicket filter to use SpringWebApplicationFactory:
<filter>
  <filter-name>wicket-springsec</filter-name>
  <filter-class>org.apache.wicket.protocol.http.WicketFilter</filter-class>
  <init-param>
    <param-name>applicationFactoryClassName</param-name>
    <param-value>org.apache.wicket.spring.SpringWebApplicationFactory</param-value>
  </init-param>
</filter>

Finally, we inject authenticationManager in wicketApplication(after defining the appropriate getter and setter in the Java class):
<bean id="wicketApplication"
  class="com.example.wicketspringsec.ui.WicketSpringSecApplication">
  <property name="authenticationManager" ref="authenticationManager" />
</bean>
Back to our custom SpringSecurityLoginContext; we can now get the reference of the authentication manager through the application:
AuthenticationManager authenticationManager = WicketSpringSecApplication
    .get().getAuthenticationManager();
One last detail: since we will also use Spring Security for certain authorizations, it must be notified of user login and logoff. We do this in the method SpringSecurityLoginContext.setAuthentication(Authentication).

That's it! There were a lot of details to handle, but our custom Swarm login context is finally ready. It is invoked from the login page (in LoginPage.LoginForm.onSubmit()):
LoginContext context = new SpringSecurityUsernamePasswordLoginContext(
    username.getModelObject(), password.getModelObject());
try {
  ((WaspSession) getSession()).login(context);
  ...

The user interface


As I said earlier, the main page displays information about the user's name and roles. This information is stored in the typical Wicket way: as a property in a custom WebSession implementation. We call this class WicketSpringSecSession, and in our case it must extend WaspSession (don't forget this, or security won't work anymore). In WicketSpringSecApplication, we override newSession to return the new implementation. In SpringSecurityLoginContext, we update this information after a successful login.

Instead of creating a new object to hold the information, I chose to reuse the Subject, adding username support to it (hence the NamedSubject interface). This is probably a bad practice, as it gives programmatic access to the user's roles from the webapp components. A convenient shortcut in the context of this prototype, but a real application would probably need to do better.

The last part to implement is the administration panel, aptly named AdminPanel. The Java class extends SecurePanel, an authorization-aware version of Panel. It just contains a Label that displays the admin service's result (we inject the service in the Wicket application object like we did for authenticationManager). You may be tempted to call the service directly from the label's constructor:
this.add(new Label("adminServiceResult", String.valueOf(WicketSpringSecApplication.get()
.getAdminService().getResult()));
But that wouldn't work: every time the page is constructed, the panel is also constructed, even if it is not actually rendered. That would mean calling the admin service when a normal users accesses the page, which will throw an exception since the service is secured on the business side. The workaround is to build the label with a separate Model instance, which calls the service in its getObject() method. This method will not be invoked unless the label is actually rendered.

The hive configuration file


The Swarm authorizations are defined in the file WEB-INF/wicket-springsec.hive. We give access to the home page to all users, but filter the administration panel (I had to add line breaks for blogger to display this correctly, refer to the source for the exact syntax):
grant principal org.apache.wicket.security.hive.authorization.SimplePrincipal
"ROLE_USER"
{
  permission ${ComponentPermission} "com.example.wicketspringsec.ui.HomePage", "render";
  permission ${ComponentPermission} "com.example.wicketspringsec.ui.HomePage", "enable";
};

grant principal org.apache.wicket.security.hive.authorization.SimplePrincipal
"ROLE_ADMIN"
{
  permission ${ComponentPermission}
"com.example.wicketspringsec.ui.HomePage:adminPanel","render";
};
On a sidenote, I used the ${ComponentPermission} alias in the example for conciseness, but it doesn't work for me :-( I have to use the full name org.apache.wicket.security.hive.authorization.permissions.ComponentPermission.

Update 2009/03/12: those interested in securing Wicket applications should also be aware that there is an alternative to Wicket-Security, called wicket-auth-roles. This thread will give you a good overview of the status of the two frameworks. Integrating wicket-auth-roles with Spring Security is covered here.
One compelling feature of wicket-auth-roles is the ability to configure authorizations with Java annotations. I find it somehow more elegant than a centralized configuration file. There is an example here.

4 comments:

PaweĊ‚ Szulc said...

nice article, thx

Dane said...

Thank you for this! I've been looking for a clear tutorial on Spring/Wicket security, and this does the job!

bujang_kuantan@yahoo.com said...

thank you for nice article. But how to setup authentication-provider from database user? can give me some example, please..thx

Anonymous said...

Thanks for laying this out, just a note on the aliases in the hive file, read this on teh wicketstuff wiki which explains why the alias for CP wasn't working:


${ComponentPermission} and ${DataPermission} are now registered as alias in SwarmPolicyFileHiveFactory, PolicyFileHiveFactory has no knowledge of them anymore.