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:
- “/crud/user/login” (logique puisqu’il s’agit de l’url utilisée pour se connecter).
- “/crud/user/register” (sinon comment créer son compte?)
- “/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:
- il est annoté
@SessionScoped
- 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
C’est par ici