Cinquième article sur LoFiDroX: détails de la sécurité.

Sécurité de l’application

Elle est globalement réduite au strict minimum: gestion de session, chiffrage et vérification du mot de passe. Je vais d’abord présenter le code puis les liens entre les différents éléments.

Code source

Annotation LfdSecured

    
 1package ch.gobothegeek.lofidrox.security;
 2
 3import org.apache.deltaspike.security.api.authorization.SecurityBindingType;
 4
 5import java.lang.annotation.Documented;
 6import java.lang.annotation.Retention;
 7import java.lang.annotation.Target;
 8
 9import static java.lang.annotation.ElementType.METHOD;
10import static java.lang.annotation.ElementType.TYPE;
11import static java.lang.annotation.RetentionPolicy.RUNTIME;
12
13@Retention(value = RUNTIME)
14@Target( { TYPE, METHOD } )
15@Documented
16@SecurityBindingType
17public @interface LfdSecured { }

Classe LfdAuthorizer

    
 1package ch.gobothegeek.lofidrox.security;
 2
 3import ch.gobothegeek.lofidrox.security.sessions.LfdUser;
 4import ch.gobothegeek.lofidrox.services.SessionService;
 5import org.apache.deltaspike.security.api.authorization.Secures;
 6import javax.enterprise.context.ApplicationScoped;
 7import javax.enterprise.inject.spi.BeanManager;
 8import javax.inject.Inject;
 9import javax.interceptor.InvocationContext;
10
11@ApplicationScoped
12public class LfdAuthorizer {
13
14	@Inject private SessionService sessionService;
15
16	@Secures
17	@LfdSecured
18	public boolean doSecuredCheck(InvocationContext invocationContext, BeanManager manager, @LfdUserLogged LfdUser user) throws Exception {
19		return ((null != user) && (this.sessionService.hasSession(user.getUser())));
20	}
21}

Classe LfdUserBean

    
 1package ch.gobothegeek.lofidrox.security;
 2
 3import ch.gobothegeek.lofidrox.security.sessions.LfdUser;
 4import javax.enterprise.context.RequestScoped;
 5import javax.enterprise.context.SessionScoped;
 6import javax.enterprise.inject.Produces;
 7import javax.inject.Inject;
 8import javax.inject.Named;
 9import java.io.Serializable;
10
11@SessionScoped
12@Named
13public class LfdUserBean implements Serializable {
14	@Inject
15	private LfdUser user;
16
17	public LfdUser getUser() { return user; }
18	public void setUser(LfdUser user) { this.user = user; }
19
20	@Produces
21	@LfdUserLogged
22	@RequestScoped
23	public LfdUser getLfdUser() {
24		return this.user;
25	}
26}

Annotation LfdUserLogged

    
 1package ch.gobothegeek.lofidrox.security;
 2
 3import org.apache.deltaspike.security.api.authorization.SecurityBindingType;
 4
 5import javax.inject.Qualifier;
 6import java.lang.annotation.Documented;
 7import java.lang.annotation.Retention;
 8import java.lang.annotation.Target;
 9
10import static java.lang.annotation.ElementType.*;
11import static java.lang.annotation.RetentionPolicy.RUNTIME;
12
13@Retention(value = RUNTIME)
14@Target( { TYPE, METHOD, PARAMETER, FIELD } )
15@Documented
16@Qualifier
17public @interface LfdUserLogged { }

Classe LfdUserLoggedEvent

    
1package ch.gobothegeek.lofidrox.security.events;
2
3// Simple event to declare user logged in
4public class LfdUserLoggedEvent { }

Classe LfdSession

    
 1package ch.gobothegeek.lofidrox.security.sessions;
 2
 3import ch.gobothegeek.lofidrox.exceptions.LfdSessionException;
 4
 5import java.util.HashMap;
 6
 7public class LfdSession {
 8	private HashMap<String, Object> properties;
 9
10	public LfdSession() {
11		this.properties = new HashMap<>();
12	}
13
14	public void destroy() {
15		this.properties.clear();
16		this.properties = null;
17	}
18
19	public void add(String key, Object obj) {
20		if (!this.properties.containsKey(key)) {
21			this.properties.put(key, obj);
22		} else {
23			this.properties.replace(key, obj);
24		}
25	}
26
27	public Object get(String key) throws LfdSessionException {
28		if (this.properties.containsKey(key)) { return this.properties.get(key); }
29		throw new LfdSessionException("No such object in session (" + key + ")");
30	}
31}

Classe LfdUser

    
 1package ch.gobothegeek.lofidrox.security.sessions;
 2
 3import java.io.Serializable;
 4
 5public class LfdUser implements Serializable {
 6	private String user;
 7
 8	public LfdUser() { }
 9
10	public LfdUser(String user) {
11		this.user = user;
12	}
13
14	public String getUser() { return user; }
15	public void setUser(String user) { this.user = user; }
16}

Classe LfdSecuredUrl

    
 1package ch.gobothegeek.lofidrox.security.urls;
 2
 3import ch.gobothegeek.lofidrox.security.permissions.LfdPermissionsManager;
 4import ch.gobothegeek.lofidrox.security.permissions.LfdSecurityPermission;
 5import ch.gobothegeek.lofidrox.security.roles.LfdSecurityRole;
 6import org.apache.deltaspike.security.api.authorization.AccessDecisionVoter;
 7import org.apache.deltaspike.security.api.authorization.AccessDecisionVoterContext;
 8import org.apache.deltaspike.security.api.authorization.SecurityViolation;
 9import javax.enterprise.context.ApplicationScoped;
10import javax.inject.Inject;
11import javax.interceptor.InvocationContext;
12import java.lang.reflect.Method;
13import java.util.Set;
14
15@ApplicationScoped
16public class LfdSecuredUrl implements AccessDecisionVoter {
17
18	@Inject private LfdPermissionsManager lfdPermissionsManager;
19
20	@Override
21	public Set<SecurityViolation> checkPermission(AccessDecisionVoterContext voterCtx) {
22		Method method = voterCtx.<InvocationContext>getSource().getMethod();
23		LfdSecurityRole annoRole;
24		LfdSecurityPermission annoPerm;
25
26		// as url is secured, only logged users can access
27		// check if user has the required role (if needed)
28		annoRole = method.getDeclaredAnnotation(LfdSecurityRole.class);
29		if (null != annoRole) {
30
31		}
32		// check if user has the required permission (if needed)
33		annoPerm = method.getDeclaredAnnotation(LfdSecurityPermission.class);
34		if (null != annoPerm) {
35
36		}
37		return null;
38	}
39}

Classe LfdUnsecuredUrl

    
 1package ch.gobothegeek.lofidrox.security.urls;
 2
 3import ch.gobothegeek.lofidrox.services.UserService;
 4import org.apache.deltaspike.security.api.authorization.AccessDecisionVoter;
 5import org.apache.deltaspike.security.api.authorization.AccessDecisionVoterContext;
 6import org.apache.deltaspike.security.api.authorization.SecurityViolation;
 7import javax.enterprise.context.ApplicationScoped;
 8import javax.inject.Inject;
 9import java.util.Set;
10
11@ApplicationScoped
12// Check if user is not logged in
13public class LfdUnsecuredUrl implements AccessDecisionVoter {
14
15	@Inject private UserService userService;
16
17	@Override
18	public Set<SecurityViolation> checkPermission(AccessDecisionVoterContext voterCtx) {
19		return null;
20	}
21}

Annotation LfdSecurityRole

    
 1package ch.gobothegeek.lofidrox.security.roles;
 2
 3import java.lang.annotation.ElementType;
 4import java.lang.annotation.Retention;
 5import java.lang.annotation.RetentionPolicy;
 6import java.lang.annotation.Target;
 7
 8@Retention(RetentionPolicy.RUNTIME)
 9@Target(ElementType.METHOD)
10public @interface LfdSecurityRole {
11	public String name() default "";
12}

Annotation LfdSecurityPermission

    
 1package ch.gobothegeek.lofidrox.security.permissions;
 2
 3import java.lang.annotation.ElementType;
 4import java.lang.annotation.Retention;
 5import java.lang.annotation.RetentionPolicy;
 6import java.lang.annotation.Target;
 7
 8@Retention(RetentionPolicy.RUNTIME)
 9@Target(ElementType.METHOD)
10public @interface LfdSecurityPermission {
11	public String name() default "";
12}

Fonctionnement de l’ensemble

Annotation des urls

Comme vu dans l’article n°2, il faut annoter les méthodes CRUD avec @Secured afin que Deltaspike exécute un contrôle de sécurité. C’est grâce à la classe indiquée dans @Secured que Deltaspike va savoir quel traitement effectuer.

Dans LoFiDroX, on dispose de deux classes:

  • LfdSecuredUrl: l’utilisateur doit être connecté pour utiliser la méthode annotée.
  • LfdUnsecuredUrl: l’utilisateur n’a pas besoin d’être connecté pour utiliser la méthode annotée.

Appel d’une url non sécurisée

Trois urls ne sont pas sécurisées:

  1. /crud/user/login” (logique puisqu’il s’agit de l’url utilisée pour se connecter).
  2. /crud/user/register” (sinon comment créer son compte?)
  3. /crud/user/check” (pour vérifier si la session de l’utilisateur est encore active) Ces méthodes sont soient annotées avec @Secured(LfdUnsecuredUrl.class), soit pas annotée. Le résultat est le même: Deltaspike ne déclenche aucun contrôle de sécurité.

Appel d’une url sécurisée

Pour sécuriser une url, il faut ajouter deux annotations:

  • @LfdSecured
  • @Secured(LfdSecuredUrl.class)

Annotation @LfdSecured

Cette annotation est présente sur les urls à sécuriser et également dans la classe LfdAuthorizer (combinée avec @Secures). C’est grâce à cette combinaison que Deltaspike sait comment intercepter les appels et vérifier les accès.

Dans la classe LfdAuthorizer, il est important de noter que la méthode doSecuredCheck reçoit un paramètre particulier: @LfdUserLogged LfdUser user. Ce paramètre est injecté lors de l’appel et provient du processus de connexion.

Annotation @Secured(LfdSecuredUrl.class)

Si l’accès est autorisé, Deltaspike enchaînera le contrôle de permission via la classe désignée par l’annotation @Secured. Ici on passera dans la méthode checkPermission de la classe LfdSecuredUrl.

En résumé, lorsqu’on appelle l’url d’un CRUD, on a la séquence d’exécution suivante: Url -> LfdAuthorizer.doSecuredCheck -> LfdSecuredUrl.checkPermission -> Méthode

Connexion d’un utilisateur

Pour s’identifier, il faut appeler l’url “/crud/user/login”. La méthode loginUser du contrôleur UserController va appeler la méthode loginUser du service UserService pour procéder au contrôle du mot de passe. En cas de succès, cette méthode va définir l’utilisateur dans le bean LfdUserBean.

Le bean LfdUserBean possède deux particularités:

  1. il est annoté @SessionScoped
  2. sa méthode getLfdUser est @RequestScoped et surtout elle produit une instance de @LfdUserLogged qui est injectée dans LfdAuthorizer lors d’un contrôle de sécurité.

Et là c’est magique de simplicité!

Conclusion

Apache Deltaspike propose les mécanismes classiques et suffisants, tout en offrant un contrôle total, permettant de gérer la sécurité d’une application Web.

Code source

Comme annoncé dans l’article Fuyez GitHub, le code n’est plus disponible sur GitHub. L’intégralité du code est disponible sur mon CodeBerg: https://codeberg.org/GoboTheGeek/LoFiDroX