Beaucoup d’applications Web nécessitent une partie privée et sécurisée. Pour cela il faut donc une page d’authentification et le code qui va derrière pour la gérer. Ce billet va donc vous permettre de développer un système d’authentification simple, basé sur les composants mis en place par Seam à cet effet, que vous pourrez complexifier par la suite.
On va donc utiliser deux composants Seam à cet égard : Identity et Credentials. Ce que vous devez retenir c’est que :
- Credentials contient les données de l’utilisateur.
- Identity contient les méthodes pour l’authentification et connaître l’état de l’utilisateur (loggé ou non etc …)
Ces deux composants sont stockées en session, ce qui leur donne une visibilité large dans l’application.
Configuration
Tout d’abord il faut mettre en place un tout petit peu de configuration.
Premièrement dans le fichier components.xml de votre projet voici les lignes nécessaires pour utiliser les composants Seam liés à la sécurité et à l’authentification.
<drools:rule-base name="securityRules">
<drools:rule-files>
<value>/security.drl</value>
</drools:rule-files>
</drools:rule-base>
<!-- <security:rule-based-permission-resolver security-rules="#{securityRules}"/> -->
<security:identity authenticate-method="#{authenticator.authenticate}" remember-me="true"/>
La première partie : drools:rule-base name="securityRules" correspond à la déclaration à Drools du fichier de configuration Security.drl, il est utilisé si vous utilisez des règles type Drools pour l’authentification, autrement laissez le presque vide :
package Permissions;
import java.security.Principal;
import org.jboss.seam.security.permission.PermissionCheck;
import org.jboss.seam.security.Role;
On peut voir que security:rule-based-permission-resolver est commenté car nous n’utiliserons pas drools ici pour gérer la sécurité.
Enfin la dernière ligne est la plus intéressante : elle permet de déclarer la méthode d’authentification au composant Identity de Seam. On devra donc ici avoir un Objet nommé authenticator (annoté @Name("authenticator"))
Voilà pour la configuration.
JSF
Passons maintenant aux choses sérieuses : le code. Commençons par le côte vue : la jsf. Voici ce que ça donne (soit dit en passant il s’agit de la page générée par le seam gen.)
<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:s="http://jboss.com/products/seam/taglib"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:rich="http://richfaces.org/rich"
template="layout/template.xhtml">
<ui:define name="body">
<h:form id="login">
<rich:panel>
<f:facet name="header">Login</f:facet>
<p>Veuillez vous identifier.</p>
<div class="dialog">
<h:panelGrid columns="2" rowClasses="prop" columnClasses="name,value">
<h:outputLabel for="username">Nom d'utilisateur:</h:outputLabel>
<h:inputText id="username" value="#{credentials.username}"/>
<h:outputLabel for="password">Password</h:outputLabel>
<h:inputSecret id="password" value="#{credentials.password}"/>
</h:panelGrid>
</div>
</rich:panel>
<div class="actionButtons">
<h:commandButton value="Login" action="#{identity.login}"/>
</div>
</h:form>
</ui:define>
</ui:composition>
Ici il n’est pas nécessaire de rentrer dans les détails … Il s’agit d’un formulaire tout à fait classique avec richfaces histoire de le rendre un peu joli. Simplement nous mappons notre champ login a credentials.username et notre champ password à credentials.password. Puis le bouton de submit déclenche l’action identity.login que nous avons - rappellez vous- configuré dans le components.xml.
Et n’oublions pas : il faut définir une “règle de redirection”, lorsqu’on va submiter le formulaire, il faut savoir comment rediriger l’utilisateur en fonction du retour de la fonction. Ici on décide de mettre une règle dans le cas ou identity.login retourne true, c’est le cas ou l’utilisateur est loggé et on le redirige sur une page de notre choix. On ne met aucune règle sur le cas “false” ce qui permet à la page de login d’être rechargée tant que l’utilisateur n’est pas loggé. On créer donc un fichier login.page.xml si votre page de login s’appelle login.xhtml
Ejb Entity
Passons maintenant au code côté serveur.
On va donc avoir besoins de deux ejbs entity : l’utilisateur et son rôle, et d’un composant service l’authenticator.
On va commencer par déclarer l’EJB Entity annexe : le Role. En effet Seam permet de très simplement mettre en place une gestion des rôles dans les applications, ici on va simplement l’implémenter pour que ce soit en place, mais nous ne rentrerons pas dans les détails de l’utilisation des rôles. Voici un exemple de rôle : ( j’ai enlevè les getters et setters, ils sont bien entendu nécessaires.)
/**
* @author leakim
* Chaque role doit être unique c'est pourquoi on ajoute la contrainte d'intégrité sur le rolename.
* */
@Name("role")
@Entity
@Table(name = "Role", uniqueConstraints = @UniqueConstraint(columnNames = "rolename"))
public class Role implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
/** id. */
@Id @GeneratedValue
private Long roleId;
/** the role rolename;. */
@RoleName // Cette annotation permet de declarer a Seam que cet attribut sera le nom du role.
private String rolename;
}
Voilà jusqu’ici rien de compliqué, passons à un peu plus sérieux maintenant l’utilisateur : Comme vous pourrez le voir Seam fournit un ensemble d’annotations pour permettre de déclarer/mapper vos attributs d’objets aux composants de sécurité. ( j’ai enlevè les getters et setters, ils sont bien entendu nécessaires.)
/** User Entity.
* @author leakim
* Ici on a decide de rendre unique l'userName qui estannote en @userPrincipal et l'email.
*
* */
@Name("user")
@Entity
@Table(name = "User", uniqueConstraints = {
@UniqueConstraint(columnNames = "userName"),
@UniqueConstraint(columnNames = "email") })
public class User implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
/** id. */
@Id @GeneratedValue
private Long id;
/** name. */
@NotNull
@UserPrincipal // user principal permet de definir le "login" .
private String userName;
/** password. */
@NotNull
@UserPassword(hash = "MD5") // définit ce champ comme étant le mot de passe.
private String passwordHash;
/** email. */
@NotNull
@Email // pour la validation
private String email;
/** the user firstname. */
@NotNull @Length(max = 40)
@UserFirstName
private String firstName;
/** the user lastname. */
@NotNull @Length(max = 40)
@UserLastName
private String lastName;
/** the user roles.
* Ici ça se complique un peu, on définit que ce Set définit les rôles, et on annote
* l'attribut correctement pour les relations ( un rôle peut être affectée a plusieurs utilisateurs, un utilisateur peut avoir plusieurs rôles.)
*/
@UserRoles
@ManyToMany(targetEntity = Role.class)
@JoinTable(name = "UserRoles",
joinColumns = @JoinColumn(name = "UserId"),
inverseJoinColumns = @JoinColumn(name = "RoleId"))
private Set userRoles;
}
Composant d’authentification
Voilà on a notre modèle de données, maintenant le composant d’authentification, c’est lui qui definit la méthode d’authentification, appellée par le composant Identity. Il récupère les données de l’utilisateur dans le composant Credentials.
/** authenticate an user. */
@Name("authenticator")
public class Authenticator {
/** the logger. */
@Logger
private Log log;
/** entityManager. */
@In
private EntityManager entityManager;
/** the identity. */
@In
private Identity identity;
/** credentials. */
@In
private Credentials credentials;
/** Authenticate an user.
* @return true if the user is correctly authenticated.
* */
public boolean authenticate() {
try {
log.info("authenticating {0}", credentials.getUsername());
Query query = entityManager.createQuery("from User where username = :username "
+ "and passwordHash = :password");
query.setParameter("password", credentials.getPassword());
query.setParameter("username", credentials.getUsername());
User user = (User) query.getSingleResult();
if (user.getUserRoles() != null) {
for (Role mr : user.getUserRoles()) {
identity.addRole(mr.getRoleName());
}
}
return true;
} catch (NoResultException ex) {
return false;
}
}
}
Comme vous pouvez le voir c’est très simple, on récupère ce qu’a entré l’utilisateur dans le formulaire, on regarde s’il existe en base un utilisateur qui a ce “username” et ce “password”, si oui c’est que l’utilisateur a correctement entré les données dans le formulaire : on peut l’authentifier donc on ajoute a identity le ou les rôles de l’utilisateur puis on retourne TRUE a Identity : votre utilisateurs est loggé !
Et voilà vous avez toutes les clefs en main pour la gestion de vos utilisateurs, à vous d’adapter et de personnalisez tout ça pour votre application …
Prochainement, l’utilisation des Converter et des Validator dans Seam.