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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package ch.gobothegeek.lofidrox.security;

import org.apache.deltaspike.security.api.authorization.SecurityBindingType;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Retention(value = RUNTIME)
@Target( { TYPE, METHOD } )
@Documented
@SecurityBindingType
public @interface LfdSecured { }

Classe LfdAuthorizer

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package ch.gobothegeek.lofidrox.security;

import ch.gobothegeek.lofidrox.security.sessions.LfdUser;
import ch.gobothegeek.lofidrox.services.SessionService;
import org.apache.deltaspike.security.api.authorization.Secures;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.spi.BeanManager;
import javax.inject.Inject;
import javax.interceptor.InvocationContext;

@ApplicationScoped
public 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())));
	}
}

Classe LfdUserBean

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package ch.gobothegeek.lofidrox.security;

import ch.gobothegeek.lofidrox.security.sessions.LfdUser;
import javax.enterprise.context.RequestScoped;
import javax.enterprise.context.SessionScoped;
import javax.enterprise.inject.Produces;
import javax.inject.Inject;
import javax.inject.Named;
import java.io.Serializable;

@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;
	}
}

Annotation LfdUserLogged

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package ch.gobothegeek.lofidrox.security;

import org.apache.deltaspike.security.api.authorization.SecurityBindingType;

import javax.inject.Qualifier;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Retention(value = RUNTIME)
@Target( { TYPE, METHOD, PARAMETER, FIELD } )
@Documented
@Qualifier
public @interface LfdUserLogged { }

Classe LfdUserLoggedEvent

1
2
3
4
package ch.gobothegeek.lofidrox.security.events;

// Simple event to declare user logged in
public class LfdUserLoggedEvent { }

Classe LfdSession

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package ch.gobothegeek.lofidrox.security.sessions;

import ch.gobothegeek.lofidrox.exceptions.LfdSessionException;

import java.util.HashMap;

public class LfdSession {
	private HashMap<String, Object> properties;

	public LfdSession() {
		this.properties = new HashMap<>();
	}

	public void destroy() {
		this.properties.clear();
		this.properties = null;
	}

	public void add(String key, Object obj) {
		if (!this.properties.containsKey(key)) {
			this.properties.put(key, obj);
		} else {
			this.properties.replace(key, obj);
		}
	}

	public Object get(String key) throws LfdSessionException {
		if (this.properties.containsKey(key)) { return this.properties.get(key); }
		throw new LfdSessionException("No such object in session (" + key + ")");
	}
}

Classe LfdUser

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package ch.gobothegeek.lofidrox.security.sessions;

import java.io.Serializable;

public class LfdUser implements Serializable {
	private String user;

	public LfdUser() { }

	public LfdUser(String user) {
		this.user = user;
	}

	public String getUser() { return user; }
	public void setUser(String user) { this.user = user; }
}

Classe LfdSecuredUrl

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package ch.gobothegeek.lofidrox.security.urls;

import ch.gobothegeek.lofidrox.security.permissions.LfdPermissionsManager;
import ch.gobothegeek.lofidrox.security.permissions.LfdSecurityPermission;
import ch.gobothegeek.lofidrox.security.roles.LfdSecurityRole;
import org.apache.deltaspike.security.api.authorization.AccessDecisionVoter;
import org.apache.deltaspike.security.api.authorization.AccessDecisionVoterContext;
import org.apache.deltaspike.security.api.authorization.SecurityViolation;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.interceptor.InvocationContext;
import java.lang.reflect.Method;
import java.util.Set;

@ApplicationScoped
public class LfdSecuredUrl implements AccessDecisionVoter {

	@Inject private LfdPermissionsManager lfdPermissionsManager;

	@Override
	public Set<SecurityViolation> checkPermission(AccessDecisionVoterContext voterCtx) {
		Method method = voterCtx.<InvocationContext>getSource().getMethod();
		LfdSecurityRole annoRole;
		LfdSecurityPermission annoPerm;

		// as url is secured, only logged users can access
		// check if user has the required role (if needed)
		annoRole = method.getDeclaredAnnotation(LfdSecurityRole.class);
		if (null != annoRole) {

		}
		// check if user has the required permission (if needed)
		annoPerm = method.getDeclaredAnnotation(LfdSecurityPermission.class);
		if (null != annoPerm) {

		}
		return null;
	}
}

Classe LfdUnsecuredUrl

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package ch.gobothegeek.lofidrox.security.urls;

import ch.gobothegeek.lofidrox.services.UserService;
import org.apache.deltaspike.security.api.authorization.AccessDecisionVoter;
import org.apache.deltaspike.security.api.authorization.AccessDecisionVoterContext;
import org.apache.deltaspike.security.api.authorization.SecurityViolation;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import java.util.Set;

@ApplicationScoped
// Check if user is not logged in
public class LfdUnsecuredUrl implements AccessDecisionVoter {

	@Inject private UserService userService;

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

Annotation LfdSecurityRole

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package ch.gobothegeek.lofidrox.security.roles;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LfdSecurityRole {
	public String name() default "";
}

Annotation LfdSecurityPermission

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package ch.gobothegeek.lofidrox.security.permissions;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LfdSecurityPermission {
	public String name() default "";
}

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

Commentaires

C’est par ici