(Quick Reference)

21 Security with Wicket - Reference Documentation

Authors: Andrea Del Bene, Martin Grigorov, Carsten Hufe, Christian Kroemer, Daniel Bartl, Paul Borș

Version: 6.x

21 Security with Wicket

Security is one of the most important non-functional requirements we must implement in our applications. This is particularly true for enterprise applications as they usually support multiple concurrent users, and therefore they need to have an access control policy.

In this chapter we will explore the security infrastructure provided by Wicket and we will learn how to use it to implement authentication and authorizations in our web applications.

21.1 Authentication

The first step in implementing a security policy is assigning a trusted identity to our users, which means that we must authenticate them. Web applications usually adopt a form-based authentication with a login form that asks user for a unique username and the relative password:

Wicket supports form-based authentication with session class AuthenticatedWebSession and application class AuthenticatedWebApplication, both placed inside package org.apache.wicket.authroles.authentication.

AuthenticatedWebSession

Class AuthenticatedWebSession comes with the following set of public methods to manage user authentication:

  • authenticate(String username, String password): this is an abstract method that must be implemented by every subclass of AuthenticatedWebSession. It should contain the actual code that checks for user's identity. It returns a boolean value which is true if authentication has succeeded or false otherwise.
  • signIn(String username, String password): this method internally calls authenticate and set the flag signedIn to true if authentication succeeds.
  • isSignedIn():getter method for flag signedIn.
  • signOut(): sets the flag signedIn to false.
  • invalidate(): calls signOut and invalidates session.

Remember that signOut does not discard any session-relative data. If we want to get rid of these data, we must invoke method invalidate instead of signOut.

Another abstract method we must implement when we use AuthenticatedWebSession is getRoles which is inherited from parent class AbstractAuthenticatedWebSession. This method can be ignored for now as it will be discussed later when we will talk about role-based authorization.

AuthenticatedWebApplication

Class AuthenticatedWebApplication provides the following methods to support form-based authentication:

  • getWebSessionClass(): abstract method that returns the session class to use for this application. The returned class must be a subclass of AbstractAuthenticatedWebSession.
  • getSignInPageClass(): abstract method that returns the page to use as sign in page when a user must be authenticated.
  • restartResponseAtSignInPage(): forces the current response to restart at the sign in page. After we have used this method to redirect a user, we can make her/him return to the original page calling Componet's method continueToOriginalDestination().

The other methods implemented inside AuthenticatedWebApplication will be introduced when we will talk about authorizations.

A basic example of authentication

Project BasicAuthenticationExample is a basic example of form-based authentication implemented with classes AuthenticatedWebSession and AuthenticatedWebApplication.

The homepage of the project contains only a link to page AuthenticatedPage which can be accessed only if user is signed in. The code of AuthenticatedPage is this following:

public class AuthenticatedPage extends WebPage {
   @Override
   protected void onConfigure() {
      super.onConfigure();
      AuthenticatedWebApplication app = (AuthenticatedWebApplication)Application.get();
      //if user is not signed in, redirect him to sign in page
      if(!AuthenticatedWebSession.get().isSignedIn())
         app.restartResponseAtSignInPage();
   }

@Override protected void onInitialize() { super.onInitialize(); add(new Link("goToHomePage") {

@Override public void onClick() { setResponsePage(getApplication().getHomePage()); } });

add(new Link("logOut") {

@Override public void onClick() { AuthenticatedWebSession.get().invalidate(); setResponsePage(getApplication().getHomePage()); } }); } }

Page AuthenticatedPage checks inside onConfigure if user is signed in and if not, it redirects her/him to the sign in page with method restartResponseAtSignInPage. The page contains also a link to the homepage and another link that signs out user.

The sign in page is implemented in class SignInPage and contains the form used to authenticate users:

public class SignInPage extends WebPage {
   private String username;
   private String password;

@Override protected void onInitialize() { super.onInitialize();

StatelessForm form = new StatelessForm("form"){ @Override protected void onSubmit() { if(Strings.isEmpty(username)) return;

boolean authResult = AuthenticatedWebSession.get().signIn(username, password); //if authentication succeeds redirect user to the requested page if(authResult) continueToOriginalDestination(); } };

form.setDefaultModel(new CompoundPropertyModel(this));

form.add(new TextField("username")); form.add(new PasswordTextField("password"));

add(form); } }

The form is responsible for handling user authentication inside its method onSubmit. The username and password are passed to AuthenticatedWebSession's method signIn(username, password) and if authentication succeeds, the user is redirected to the original page with method continueToOriginalDestination.

The session class and the application class used in the project are reported here:

Session class:

public class BasicAuthenticationSession extends AuthenticatedWebSession {

public BasicAuthenticationSession(Request request) { super(request); }

@Override public boolean authenticate(String username, String password) { //user is authenticated if both username and password are equal to 'wicketer' return username.equals(password) && username.equals("wicketer"); }

@Override public Roles getRoles() { return null; } }

Application class:

public class WicketApplication extends AuthenticatedWebApplication{    	
	@Override
	public Class<HomePage> getHomePage(){
		return HomePage.class;
	}

@Override protected Class<? extends AbstractAuthenticatedWebSession> getWebSessionClass(){ return BasicAuthenticationSession.class; }

@Override protected Class<? extends WebPage> getSignInPageClass() { return SignInPage.class; } }

The authentication logic inside authenticate has been kept quite trivial in order to make the code as clean as possible. Please note also that session class must have a constructor that accepts an instance of class Request.

Redirecting user to an intermediate page

Method restartResponseAtSignInPage is an example of redirecting user to an intermediate page before allowing him to access to the requested page. This method internally throws exception org.apache.wicket.RestartResponseAtInterceptPageException which saves the URL of the requested page into session metadata and then redirects user to the page passed as constructor parameter (the sign in page).

Component's method redirectToInterceptPage(Page) works in much the same way as restartResponseAtSignInPage but it allows us to specify which page to use as intermediate page:

redirectToInterceptPage(intermediatePage);

Since both restartResponseAtSignInPage and redirectToInterceptPage internally throw an exception, the code placed after them will not be executed.

21.2 Authorizations

The authorization support provided by Wicket is built around the concept of authorization strategy which is represented by interface IAuthorizationStrategy (in package org.apache.wicket.authorization):

public interface IAuthorizationStrategy
{
  //interface methods 
 <T extends IRequestableComponent> boolean isInstantiationAuthorized(Class<T> componentClass);
 boolean isActionAuthorized(Component component, Action action);

//default authorization strategy that allows everything public static final IAuthorizationStrategy ALLOW_ALL = new IAuthorizationStrategy() { @Override public <T extends IRequestableComponent> boolean isInstantiationAuthorized(final Class<T> c) { return true; } @Override public boolean isActionAuthorized(Component c, Action action) { return true; } }; }

This interface defines two methods:

  • isInstantiationAuthorized checks if user is allowed to instantiate a given component.
  • isActionAuthorized checks if user is authorized to perform a given action on a component's instance. The standard actions checked by this method are defined into class Action and are Action.ENABLE and Action.RENDER.

Inside IAuthorizationStrategy we can also find a default implementation of the interface (called ALLOW_ALL) that allows everyone to instantiate every component and perform every possible action on it. This is the default strategy adopted by class Application.

To change the authorization strategy in use we must register the desired implementation into security settings (interface ISecuritySettings) during initialization phase with method setAuthorization Strategy:

//Application class code… 
  @Override
  public void init()
  {
    super.init();
    getSecuritySettings().
	setAuthorizationStrategy(myAuthorizationStrategy);
  }	
//...

If we want to combine the action of two or more authorization strategies we can chain them with strategy CompoundAuthorizationStrategy which implements composite pattern for authorization strategies.

Most of the times we won't need to implement an IAuthorizationStrategy from scratch as Wicket already comes with a set of built-in strategies. In the next paragraphs we will see some of these strategies that can be used to implement an effective and flexible security policy.

SimplePageAuthorizationStrategy

Abstract class SimplePageAuthorizationStrategy (in package org.apache.wicket.authorization.strategies.page) is a strategy that checks user authorizations calling abstract method isAuthorized only for those pages that are subclasses of a given supertype. If isAuthorized returns false, the user is redirected to the sign in page specified as second constructor parameter:

SimplePageAuthorizationStrategy authorizationStrategy = new SimplePageAuthorizationStrategy( 
                                                  PageClassToCheck.class, SignInPage.class)
{
  protected boolean isAuthorized()
  {		                
    //Authentication code…
  }
};

By default SimplePageAuthorizationStrategy checks for permissions only on pages. If we want to change this behavior and check also other kinds of components, we must override method isActionAuthorized and implement our custom logic inside it.

Role-based strategies

At the end of paragraph 20.1 we have introduced AbstractAuthenticatedWebSession's method getRoles which is provided to support role-based authorization returning the set of roles granted to the current user.

In Wicket roles are simple strings like “BASIC_USER” or “ADMIN” (they don't need to be capitalized) and they are handled with class org.apache.wicket.authroles.authorization.strategies.role.Roles. This class extends standard HashSet collection adding some functionalities to check whether the set contains one or more roles. Class Roles already defines roles Roles.USER and Roles.ADMIN.

The session class in the following example returns a custom “SIGNED_IN” role for every authenticated user and it adds an Roles.ADMIN role if username is equal to superuser:

class BasicAuthenticationRolesSession extends AuthenticatedWebSession {
	private String userName;

public BasicAuthenticationRolesSession(Request request) { super(request); }

@Override public boolean authenticate(String username, String password) { boolean authResult= false;

authResult = //some authentication logic...

if(authResult) userName = username;

return authResult; }

@Override public Roles getRoles() { Roles resultRoles = new Roles();

if(isSignedIn()) resultRoles.add("SIGNED_IN");

if(userName.equals("superuser")) resultRoles.add(Roles.ADMIN);

return resultRoles; } }

Roles can be adopted to apply security restrictions on our pages and components. This can be done using one of the two built-in authorization strategies that extend super class AbstractRoleAuthorizationStrategyWicket: MetaDataRoleAuthorizationStrategy and AnnotationsRoleAuthorizationStrategy

The difference between these two strategies is that MetaDataRoleAuthorizationStrategy handles role-based authorizations with Wicket metadata while AnnotationsRoleAuthorizationStrategy uses Java annotations.

Application class AuthenticatedWebApplication already sets MetaDataRoleAuthorizationStrategy and AnnotationsRoleAuthorizationStrategy as its own authorization strategies (it uses a compound strategy as we will see in paragraph 20.2).

The code that we will see in the next examples is for illustrative purpose only. If our application class inherits from AuthenticatedWebApplication we won't need to configure anything to use these two strategies.

Using roles with metadata

Strategy MetaDataRoleAuthorizationStrategy uses application and components metadata to implement role-based authorizations. The class defines a set of static methods authorize that can be used to specify which roles are allowed to instantiate a component and which roles can perform a given action on a component.

The following code snippet reports both application and session classes from project MetaDataRolesStrategyExample and illustrates how to use MetaDataRoleAuthorizationStrategy to allow access to a given page (AdminOnlyPage) only to ADMIN role:

Application class:

public class WicketApplication extends AuthenticatedWebApplication{    		
   @Override
   public Class<? extends WebPage> getHomePage(){
      return HomePage.class;
   }

@Override protected Class<? extends AbstractAuthenticatedWebSession> getWebSessionClass() { return BasicAuthenticationSession.class; }

@Override protected Class<? extends WebPage> getSignInPageClass() { return SignInPage.class; }

@Override public void init(){ getSecuritySettings().setAuthorizationStrategy(new MetaDataRoleAuthorizationStrategy(this)); MetaDataRoleAuthorizationStrategy.authorize(AdminOnlyPage.class, Roles.ADMIN); } }

Session class:

public class BasicAuthenticationSession extends AuthenticatedWebSession {

private String username;

public BasicAuthenticationSession(Request request) { super(request); }

@Override public boolean authenticate(String username, String password) { //user is authenticated if username and password are equal boolean authResult = username.equals(password);

if(authResult) this.username = username;

return authResult; }

public Roles getRoles() { Roles resultRoles = new Roles(); //if user is signed in add the relative role if(isSignedIn()) resultRoles.add("SIGNED_IN"); //if username is equal to 'superuser' add the ADMIN role if(username!= null && username.equals("superuser")) resultRoles.add(Roles.ADMIN);

return resultRoles; }

@Override public void signOut() { super.signOut(); username = null; } }

The code that instantiates MetaDataRoleAuthorizationStrategy and set it as application's strategy is inside application class method init.

Any subclass of AbstractRoleAuthorizationStrategyWicket needs an implementation of interface IRoleCheckingStrategy to be instantiated. For this purpose in the code above we used the application class itself because its base class AuthenticatedWebApplication already implements interface IRoleCheckingStrategy. By default AuthenticatedWebApplication checks for authorizations using the roles returned by the current AbstractAuthenticatedWebSession. As final step inside init we grant the access to page AdminOnlyPage to ADMIN role calling method authorize.

The code from session class has three interesting methods. The first is authenticate which considers as valid credentials every pair of username and password having the same value. The second notable method is getRoles which returns role SIGNED_IN if user is authenticated and it adds role ADMIN if username is equal to superuser. Finally, we have method signOut which has been overridden in order to clean the username field used internally to generate roles.

Now if we run the project and we try to access to AdminOnlyPage from the home page without having the ADMIN role, we will be redirected to the default access-denied page used by Wicket:

The access-denied page can be customized using method setAccessDeniedPage(Class<? extends Page>) of setting interface IApplicationSettings:

//Application class code…
   @Override
   public void init(){   
      getApplicationSettings().setAccessDeniedPage(
			MyCustomAccessDeniedPage.class); 
   }

Just like custom “Page expired” page (see chapter 6.2.5), also custom “Access denied” page must be bookmarkable.

Using roles with annotations

Strategy AnnotationsRoleAuthorizationStrategy relies on two built-in annotations to handle role-based authorizations. These annotations are AuthorizeInstantiation and AuthorizeAction. As their names suggest the first annotation specifies which roles are allowed to instantiate the annotated component while the second must be used to indicate which roles are allowed to perform a specific action on the annotated component.

In the following example we use annotations to make a page accessible only to signed-in users and to enable it only if user has the ADMIN role:

@AuthorizeInstantiation("SIGNED_IN")
@AuthorizeAction(action = "ENABLE", roles = {"ADMIN"})
public class MyPage extends WebPage {
   //Page class code…
}

Remember that when a component is not enabled, user can render it but he can neither click on its links nor interact with its forms.

Example project AnnotationsRolesStrategyExample is a revisited version of MetaDataRolesStrategyExample where we use AnnotationsRoleAuthorizationStrategy as authorization strategy. To ensure that page AdminOnlyPage is accessible only to ADMIN role we have used the following annotation:

@AuthorizeInstantiation("ADMIN")
public class AdminOnlyPage extends WebPage {
    //Page class code…
}

Catching an unauthorized component instantiation

Interface IUnauthorizedComponentInstantiationListener (in package org.apache.wicket.authorization) is provided to give the chance to handle the case in which a user tries to instantiate a component without having the permissions to do it. The method defined inside this interface is onUnauthorizedInstantiation(Component) and it is executed whenever a user attempts to execute an unauthorized instantiation.

This listener must be registered into application's security settings with method setUnauthorized ComponentInstantiationListener defined by setting interface ISecuritySettings. In the following code snippet we register a listener that redirect user to a warning page if he tries to do a not-allowed instantiation:

public class WicketApplication extends AuthenticatedWebApplication{   
     //Application code…
     @Override
     public void init(){    
        getSecuritySettings().setUnauthorizedComponentInstantiationListener(
			new IUnauthorizedComponentInstantiationListener() {

@Override public void onUnauthorizedInstantiation(Component component) { component.setResponsePage(AuthWarningPage.class); } }); } }

In addition to interface IRoleCheckingStrategy, class AuthenticatedWebApplication implements also IUnauthorizedComponentInstantiationListener and registers itself as listener for unauthorized instantiations.

By default AuthenticatedWebApplication redirects users to sign-in page if they are not signed-in and they try to instantiate a restricted component. Otherwise, if users are already signed in but they are not allowed to instantiate a given component, an UnauthorizedInstantiationException will be thrown.

Strategy RoleAuthorizationStrategy

Class RoleAuthorizationStrategy is a compound strategy that combines both MetaDataRoleAuthorizationStrategy and AnnotationsRoleAuthorizationStrategy.

This is the strategy used internally by AuthenticatedWebApplication.

21.3 Using HTTPS protocol

HTTPS is the standard technology adopted on Internet to create a secure communication channel between web applications and their users.

In Wicket we can easily protect our pages with HTTPS mounting a special request mapper called HttpsMapper and using annotation RequireHttps with those pages we want to serve over this protocol. Both these two entities are in package org.apache.wicket.protocol.https.

HttpsMapper wraps an existing mapper and redirects incoming requests to HTTPS if the related response must render a page containing annotation RequireHttps. Most of the times the wrapped mapper will be the root one, just like we saw before for CryptoManager in paragraph 8.6.6.

Another parameter needed to build a HttpsMapper is an instance of class HttpsConfig. This class allows us to specify which ports must be used for HTTPS and HTTP. By default the port numbers used by these two protocols are respectively 443 and 80.

The following code is taken from project HttpsProtocolExample and illustrates how to enable HTTPS in our applications:

//Application class code…
@Override
public void init(){   
   setRootRequestMapper(new HttpsMapper(getRootRequestMapper(), 
                                       new HttpsConfig(8080, 443))); 
}

Now we can use annotation RequireHttps to specify which pages must be served using HTTPS:

@RequireHttps
public class HomePage extends WebPage {
    public HomePage(final PageParameters parameters) {
    	super(parameters);	
    }
}

If we want to protect many pages with HTTPS without adding annotation RequireHttps to each of them, we can annotate a marker interface or a base page class and implement/extend it in any page we want to make secure:

// Marker interface:
@RequireHttps
public interface IMarker{
}

// Base class: @RequireHttps public class BaseClass extends WebPage{ //Page code… }

// Secure page inheriting from BaseClass: public class HttpsPage extends BaseClass{ //Page code… }

// Secure page implementing IMarker: public class HttpsPage implements IMarker{ //Page code… }

21.4 Package Resource Guard

Wicket internally uses an entity called package resource guard to protect package resources from external access. This entity is an implementation of interface org.apache.wicket.markup.html.IPackageResourceGuard.

By default Wicket applications use as package resource guard class SecurePackageResourceGuard, which allows to access only to the following file extensions (grouped by type):

FileExtensions
JavaScript files.js
CSS files.css
HTML pages.html
Textual files.txt
Flash files.swf
Picture files.png, .jpg, .jpeg, .gif, .ico, .cur, .bmp, .svg
Web font files.eot, .ttf, .woff

To modify the set of allowed files formats we can add one or more patterns with method addPattern(String). The rules to write a pattern are the following:

  • patterns start with either a "+" or a "-". In the first case the pattern will add one or more file to the set while starting a pattern with a “-” we exclude all the files matching the given pattern. For example pattern “-web.xml” excludes all web.xml files in all directories.
  • wildcard character “*” is supported as placeholder for zero or more characters. For example pattern “+*.mp4” adds all the mp4 files inside all directories.
  • subdirectories are supported as well. For example pattern “+documents/*.pdf” adds all pdf files under “documents” directory. Character “*” can be used with directories to specify a nesting level. For example “+documents/*/*.pdf” adds all pdf files placed one level below “documents” directory.
  • a double wildcard character “**” indicates zero or more subdirectories. For example pattern “+documents/**/*.pdf” adds all pdf files placed inside “documents” directory or inside any of its subdirectories.

Patterns that allow to access to every file with a given extensions (such as “+*.pdf”) should be always avoided in favour of more restrictive expressions that contain a directory structure:

//Application class code…
@Override
public void init()   
{
      IPackageResourceGuard packageResourceGuard = application.getResourceSettings() 
                                                   .getPackageResourceGuard();
      if (packageResourceGuard instanceof SecurePackageResourceGuard)
      {
         SecurePackageResourceGuard guard = (SecurePackageResourceGuard) packageResourceGuard;
         //Allow to access only to pdf files placed in the “public” directory.
         guard.addPattern("+public/*.pdf");
      }
}

21.5 Summary

In this chapter we have seen the components and the mechanisms that allow us to implement security policies in our Wicket-based applications. Wicket comes with an out of the box support for both authorization and authentication.

The central element of authorization mechanism is the interface IAuthorizationStrategy which decouples our components from any detail about security strategy. The implementations of this interface must decide if a user is allowed to instantiate a given page or component and if she/he can perform a given action on it.

Wicket natively supports role-based authorizations with strategies MetaDataRoleAuthorizationStrategy and AnnotationsRoleAuthorizationStrategy. The difference between these two strategies is that the first offers a programmatic approach for role handling while the second promotes a declarative approach using built-in annotations.

After having explored how Wicket internally implements authentication and authorization, in the last part of the chapter we have learnt how to configure our applications to support HTTPS and how to specify which pages must be served over this protocol.

In the last paragraph we have seen how Wicket protects package resources with a guard entity that allows us to decide which package resources can be accessed from users.