Dans cet article, on va s’intéresser au module “Security” d’Apache Deltaspike et la sécurisation d’appels CRUD.

Concepts

Apache Deltaspike Security propose 3 mécanismes classiques, à savoir:

  • Gestion de l’identité
  • Gestion des rôles
  • Gestion des permissions

Gestion de l’identité

Pour confirmer l’identité d’un utilisateur, Deltaspike met en œuvre un mécanisme de contrôle basé sur l’annotation @Secures. Cette annotation doit être complétée par une annotation personnalisée permettant de choisir le contrôle à effectuer (donc on peut avoir avoir plusieurs contrôles. On pourrait imaginer des URI réservées aux utilisateurs d’une branche particulière d’un LDAP mais interdites aux autres branches).

Gestion des rôles

Une fois l’identité confirmée, Deltaspike propose de vérifier les rôles de l’utilisateur. Par rôle, on entend la notion de fonction (ex: l’utilisateur est-il responsable d’équipe?). Souvent cette notion est traduite en groupes dans un LDAP. Bien sûr, la vérification des rôles est réalisée après la confirmation d’identité.

Gestion des permissions

En complément des deux concepts précédents, Deltaspike permet de gérer des droits fins. En effet, on va pouvoir configurer chaque URI avec un ou plusieurs droits supplémentaires. Par exemple, on peut imaginer que le responsable d’une équipe et son adjoint (qui n’est pas responsable) peuvent déposer des rapports, tandis que les autres membres de l’équipe peuvent uniquement lire ces rapports.

Exemple complet

Alice, Bob et Domi sont membres de l’équipe “IT Dev”. Alice en est la responsable et Bob est son adjoint.

Dans le LDAP, on trouve trois groupes:

  • IT Dev”, qui contient “IT Dev Resp” et “IT Dev Members”.
  • Alice est membre de “IT Dev Resp” puisqu’elle est responsable de l’équipe.
  • Bob et Domi sont membres de “IT Dev Members”.
  • Alice et Bob ont la permission “Dépôt”, que Domi ne possède pas.

L’application propose l’URL “/rapport” en GET et POST.

  • GET est accessible au groupe “IT Dev
  • POST est accessible à la permission “Dépôt

Mise en œuvre

Ajout du module

Pour ajouter Deltaspike-Security, il faut inclure deux dépendances (version 1.9.5 lors de la rédaction) au fichier pom.xml du projet:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
        <dependency>
            <groupId>org.apache.deltaspike.modules</groupId>
            <artifactId>deltaspike-security-module-api</artifactId>
            <version>${deltaspike.version}</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.deltaspike.modules</groupId>
            <artifactId>deltaspike-security-module-impl</artifactId>
            <version>${deltaspike.version}</version>
            <scope>runtime</scope>
        </dependency>

Et c’est tout. Facile.

Contrôle de l’identité

Ici on va ajouter trois classes et deux annotations qui interagissent via l’injection de dépendances.

LfdAuthorizer

Cette classe est chargée de contrôler l’identité de l’utilisateur. Ici, je me contente de vérifier que l’utilisateur possède une session.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@ApplicationScopedpublic class LfdAuthorizer {   
	@Inject private SessionService sessionService;

	@Secures   
	@LfdSecured   
	public boolean doSecuredCheck(InvocationContext invocationContext, BeanManager manager, 
		@LfdUserLogged LfdUser user) throws Exception {      
		return ((null != user) && (this.sessionService.hasSession(user.getUser())));   
	}
}

@LfdSecured

Cette annotation permet d’indiquer que la méthode est réservée aux utilisateurs identifiés.

1
2
3
4
5
@Retention(value = RUNTIME)
@Target( { TYPE, METHOD } )
@Documented
@SecurityBindingTypepublic
@interface LfdSecured { }

@LfdUserLogged

Cette annotation permet de récupérer l’identifiant de l’utilisateur effectuant la requête.

1
2
3
4
@Retention(value = RUNTIME)
@Target( { TYPE, METHOD, PARAMETER, FIELD } )
@Documented@Qualifierpublic 
@interface LfdUserLogged { }

LfdUserBean

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@SessionScoped
@Named
public class LfdUserBean implements Serializable {   
	@Inject   private LfdUser user;   
	
	public LfdUser getUser() { return user; }   
	public void setUser(LfdUser user) { this.user = user; }   
	
	@Produces   
	@LfdUserLogged   
	@RequestScoped   
	public LfdUser getLfdUser() { return this.user; }
}

C’est finalement ce bean qui est responsable de la magie: il stocke le nom de l’utilisateur et permet son injection via l’annotation @LfdUserLogged.

@LfdSecuredUrl

1
2
3
4
5
6
7
8
@ApplicationScoped
public class LfdSecuredUrl implements AccessDecisionVoter {

   @Override
   public Set<SecurityViolation> checkPermission(AccessDecisionVoterContext voterCtx) {
      return null;
   }
}

En l’état cette classe ne fait rien mais c’est elle qui doit vérifier les rôles et permissions de l’utilisateur.

Utilisation dans le processus de connexion

Lors de l’appel de l’url “/login” (note: cette url n’est pas sécurisée, bien sûr), on va appeler le service qui gère les utilisateurs (ici UserService, original en diable). La méthode loginUser reçoit donc le nom et le mot de passe de l’utilisateur (ici je n’ai pas écrit comment vérifier le mot de passe, ce n’est pas le sujet de l’article). Le point important est “this.lfdUserBean.setUser(new LfdUser(username));”: c’est cette ligne qui va permettre de conserver le nom de l’utilisateur pour les prochaines requêtes et permettre de l’injecter via @LfdUserLogged.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@ApplicationScoped
@Transactional
public class UserService {
   @Inject private SessionService sessionService;
   @Inject private LfdUserBean lfdUserBean;

   @Transactional(Transactional.TxType.REQUIRED)
   public Boolean loginUser(String username, String pwd) {
            this.sessionService.create(username);
            this.lfdUserBean.setUser(new LfdUser(username));
            return Boolean.TRUE;
   }
}

Utilisation dans le processus de déconnexion

Ici, au contraire du processus de connexion, l’url de déconnexion doit être sécurisée. On utilise l’annotation @LfdSecured en combinaison avec @Secures(LfdSecuredUrl.class).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
@RequestScoped
@Path("/user")
@Named
public class UserController {

   @Inject private UserService userService;
   @Inject private SessionService sessionService;

   @DELETE
   @Path("/logout")
   @Consumes(MediaType.APPLICATION_JSON)
   @Produces(MediaType.APPLICATION_JSON)
   @Secured(LfdSecuredUrl.class)
   @LfdSecured
   public Response logoutUser(JsonUserLogout nameLog) {
      this.userService.logoffUser(nameLog.getUsername());
      nameLog.setExited(true);
      return Response.status(Response.Status.OK).entity(nameLog).build();
   }
}

Détails du fonctionnement

Lors de l’appel de l’url “/logout”, Deltaspike va déclencher deux actions. La première est d’appeler la méthode LfdAuthorizer.doSecuredCheck (grâce au lien réalisée par l’annotation @LfdSecured présente sur la méthode logoutUser et doSecuredCheck). La deuxième est d’appeler la méthode LfdSecuredUrl.checkPermission en utilisant la classe indiquée dans l’annotation @Secures.

Conclusion

Malgré une documentation limitée et datée (utilisation de classes dépréciées par exemple), Apache Deltaspike propose un mécanisme de sécurisation plutôt simple à mettre en œuvre et offrant énormément de souplesse.