lundi 21 décembre 2009

Authentification avec wicket auth-roles

Je me suis récemment plongé dans la problématique de sécurisation des applications web construites avec Wicket. Une fois de plus le framework nous propose une solution simple et élégante : l'extension wicket-auth-roles.

Cette extension propose un mécanisme à base d'annotations qui permet de spécifier les utilisateurs qui auront le droit d'instancier une page. Un formulaire d'authentification basique facilement intégrable est également proposé.

Voyons comment mettre en œuvre cette extension.




Si vous utilisez Maven, ajoutez ces quelques lignes à votre pom.xml :




Nous allons modifier la classe principale de l'application pour la faire hériter de AuthenticatedWebApplication :
public class MyWicketApplication extends AuthenticatedWebApplication {

protected Class getWebSessionClass() {
return MySession.class;
}

protected Class getSignInPageClass() {
return AuthPage.class;
}

public Class getHomePage() {
return WelcomePage.class;
}
}

Dans cette classe nous demandons à Wicket d'utiliser la page AuthPage pour l'authentification et de rediriger l'utilisateur vers la page WelcomePage après qu'il ait rentré ses identifiants.

Nous allons également créer une classe de session personnalisée qui hérite de AuthenticatedWebSession :
public class MySession extends AuthenticatedWebSession {

private static Map<String, Roles> profileRoles = new HashMap<String, Roles>();

//to be injected
private UserService userService;

static{
//ADMIN roles = ADMIN + USER
Roles adminRoles = new Roles();
adminRoles.add("USER");
adminRoles.add("ADMIN");
profileRoles.put("ADMIN", adminRoles);
//USER role
profileRoles.put("USER", new Roles("USER"));
}


private User user;

public User getUser() {
return user;
}

public static MySession get() {
return (MySession) Session.get();
}

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


public Roles getRoles() {
if (isSignedIn()) {
return profileRoles.get(user.getProfile());
}
return null;
}

@Override
public boolean authenticate(String username, String password) {
user = userService().loadByLoginAndPassword(username, password);
return user != null; 
}
}

J'ai créé une classe User qui contient simplement un nom et un profil sous forme de String.
La méthode authenticate() permet de se connecter à la couche service pour authentifier l'utilisateur et instancier l'objet user. La méthode getRoles() permet ensuite de récupérer les rôles correspondant au profil de l'utilisateur. Pour stocker les différentes autorisations des profils, j'ai créé la Map profileRoles qui contient l'ensemble des rôles des différents utilisateurs.
On peut voir par exemple que les utilisateurs de profil ADMIN auront les droits d'administration + les droits d'un utilisateur normal.

Voyons maintenant comment intégrer tout ça dans notre application.
Pour voir la page WelcomePage, il faut nécessairement être identifié en tant que USER :
@AuthorizeInstantiation("USER")
public class WelcomePage extends WebPage{

public WelcomePage() {
Label label = new Label("hello", getString("hello")+ " " + MySession.get().getUser()getName());
add(label);
}
}

Pour voir la page AdministrationPage, il faut nécessairement être identifié en tant qu' ADMIN:
@AuthorizeInstantiation("ADMIN")
public class AdminPage extends WebPage{

public AdminPage () {
//...
}
}

Comme nous l'avons vu plus haut, un utilisateur de profil ADMIN peut voir ces deux types de page.

La classe qui contient le formulaire d'authentification se présente comme ceci :

Java :
public final class AuthPage extends SignInPage{

public AuthPage() {

}

}

HTML:




Comme vous le voyez il n'y a rien à ajouter côté Java, on inclus uniquement un panel dans la page HTML. Le panel signInPanel est fourni par Wicket dans la classe parente (SignInPage), et il contient les champs de saisie "utilisateur"et "mot de passe".
Ce panel d'authentification est internationalisé par défaut en s'appuyant sur la "locale" de la session courante.

Voyons maintenant comment se déconnecter de la session courante. On crée une page qui étend SignOutPage. Ici aussi le code Java ne contient rien, sauf bien sur si on veut rajouter des composants à la page. J'ajoute simplement un lien vers la page d'accueil :



Vous pouvez par exemple ajouter un lien vers cette page dans le pied de page de votre application, en cliquant dessus l'utilisateur sera immédiatement déconnecté.

Pour terminer voyons comment résoudre le cas suivant : une page est visible par les profils USER et ADMIN, cependant un composant de cette page n'est visible que par ADMIN (typiquement un élément du menu).

On a défini dans la classe MySession la méthode get() qui permet de récupérer la session, qui contient les informations de l'utilisateur courant. Pour masquer un composant, on va donc surcharger sa méthode isVisble() en lisant les informations dont on a besoin dans la session :
adminMenus = new MarkupContainer("adminMenus"){

@Override
public boolean isVisible() {   
return MySession.get().getRoles().hasRole("ADMIN");
}

};

Et voilà on a terminé! J'espère que ces astuces vous seront utiles!