19 Security with Wicket - Reference Documentation
Authors: Andrea Del Bene, Carsten Hufe, Christian Kroemer, Daniel Bartl
Version: 1.0.0.BUILD-SNAPSHOT
Table of Contents
19 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.19.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 AbstractAuthenticatedWeb Session.
- 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().
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() { 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()); } }); } }
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); } }
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; } }
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; } }
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 redirectToIntercept Page internally throw an exception, the code placed after them will not be executed.
19.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; } }; }
- 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.
//Application class code… @Override public void init() { super.init(); getSecuritySettings().setAuthorizationStrategy(myAuthorizationStrategy); } //...
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… } };
Role-based strategies
At the end of paragraph 18.1.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; } }
Application class AuthenticatedWebApplication already sets MetaData RoleAuthorizationStrategy and AnnotationsRoleAuthorization Strategy as its own authorization strategies (it uses a compound strategy as we will see in paragraph 18.2.4).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); } }
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; } }
//Application class code…
@Override
public void init(){
getApplicationSettings().setAccessDeniedPage(MyCustomAccessDeniedPage.class);
}
Using roles with annotations
Strategy AnnotationsRoleAuthorizationStrategy relies on two built-in annotations to handle role-based authorizations. These annotations are AuthorizeInstantiation and Authorize Action. 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… }
@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); } }); } }
Strategy RoleAuthorizationStrategy
Class RoleAuthorizationStrategy is a compound strategy that combines both MetaData RoleAuthorizationStrategy and AnnotationsRoleAuthorizationStrategy.This is the strategy used internally by AuthenticatedWebApplication.19.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))); }
@RequireHttps public class HomePage extends WebPage { public HomePage(final PageParameters parameters) { super(parameters); } }
// 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… }
19.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 SecurePackageResource Guard, which allows to access only to the following file extensions (grouped by type):File | Extensions |
---|---|
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 |
- 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 patter “+documents/**/*.pdf” adds all pdf files placed inside “documents” directory or inside any of its subdirectories.
//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"); } }