IV. La Sécurité▲
IV-A. Rappels sur la sécurité à travers le Web▲
IV-A-1. HTTPS et le certificat▲
Le protocole HTTPS a pour fonction de crypter les données entre le client et le serveur. Si vous vous connectez sur https://www.mi5.co.uk/home, aucun employé de votre FAI ne pourra lire vos données.
Sauf si cet employé a intercalé un serveur s'appelant également mi5.co.uk, à l'insu du site officiel. Le certificat a donc pour objet de s'assurer que le site mi5.co.uk est bien le site officiel, et non un site fourni par un escroc.
Ces deux outils sont indispensables à un site sécurisé, mais ne seront cependant pas le sujet de ce chapitre : ce n'est pas à Java de s'occuper de cela. Je signalerais quand il sera simulé une requête cryptée.
IV-A-2. L'authentification BASIC▲
Nous avons déjà abordé la notion d'authentification, en remplissant le champ Authorization par 'James Bond' et en l'envoyant à travers une requête non cryptée. Bien que très peu de gens savent lire ou créer eux-mêmes une requête HTTP, il y a peu de chance que cette méthode satisfasse les normes du MI5.
Le protocole dit BASIC sur Internet envoie une autorisation contenant l'identifiant (le terme anglais username est moins ambigu) et le mot de passe (password est plus pratique). Si l'on envoie à travers une requête cryptée :
HTTPS 1.1
POST /mi5/login
Authorization: BASIC "jamesbond:howisyourblanquette"
<root>
It's good</root>
Alors seuls le serveur et son administrateur sont capables de connaître le couple username:password. Vous avez surement déjà rencontré une telle demande d'identification reconnaissable par un pop-up :
Il est recommandé d'utiliser des caractères spéciaux comme ' ' ou '%' dans un password, malheureusement le protocole HTTP(s) n'aime pas ces caractères. On utilise donc une fonction particulière nommée Base64 afin d'envoyer le couple ; cette fonction se trouve dans la classe robusta.utils.Codec. Codec.encodeBase64(« jamesbond:howisyourblanquette »)=amFtZXNib25kOmhvd2lzeW91cmJsYW5xdWV0dGU=
On utilisera donc :
HTTPS 1.1
POST /mi5/login
Authorization:BASIC amFtZXNib25kOmhvd2lzeW91cmJsYW5xdWV0dGU=
<root>
It's good</root>
Base64 se décode aussi facilement par Codec.decode64(). De plus une chaîne Base64 se remarque très facilement puisqu'elle finit souvent par un ou deux caractères '='. Il est donc EXTRÊMEMENT important d'utiliser HTTPS pour ce protocole, sinon n'importe quel hacker de votre FAI ou dans votre entreprise trouverait le mot de passe.
Le navigateur mémorise le champ Authorization, et chaque future requête vers le site web incorporera cette autorisation. En général, votre navigateur arrêtera d'envoyer ce champ à son prochain redémarrage. Il est possible que votre navigateur vous demande d'enregistrer le mot de passe, et il n'existe pas de bouton logout.
IV-A-3. Avantages et défauts de la BASIC Authentication pour une application web▲
Il s'agit ici de s'intéresser aux contraintes d'un site qui veut faire de l'argent en attirant des clients : design, ergonomie et sécurité sont les critères. On exclut cependant les applications hautement sensibles comme les banques, voire les emails en Saas.
Défauts
- Le « pop-up style » est ringard, et non personnalisable.
- Pas de log-out : si plusieurs personnes utilisent le même PC, il y a danger.
- Il faut être sûr que HTTPS est bien utilisé, sur toutes les pages du site (ou du « realm »).
- HTTPS consomme des ressources : faut-il l'utiliser partout ?
- Contrairement aux cookies, le temps de sauvegarde du mot de passe par les Managers de sécurité du navigateur est illimité.
Avantages
- C'est techniquement parfaitement sécurisé, tant qu'il y a HTTPS et qu'une seule personne utilise le PC.
- C'est simple à implémenter.
- Tous les navigateurs gèrent un minimum l'authentification BASIC.
Pour pallier ce défaut de la nécessité de garantir HTTPS, il existe le mode d'authentification DIGEST, mais les autres défauts perdurent.
Ces deux paragraphes montrent la nécessité de créer son propre système de login, comme le font tous les sites web. Robusta propose le mode ArmyLazy, proche du mode BASIC.
IV-B. Les contraintes ergonomiques et sécuritaires d'un site web▲
Le protocole ArmyLazy a été inventé pour répondre aux contraintes du site Edupassion.com. Il s'agit d'un logiciel de bulletin de notes et de QCM en ligne et doit se plier aux contraintes suivantes :
- les élèves ont accès aux mêmes ordinateurs que les professeurs ;
- certains élèves sont fils de professeurs dans la même école, et ont accès à la maison aux mêmes ordinateurs ;
- il est illusoire de penser qu'un professeur cliquera toujours sur log-out ;
- il est complètement inacceptable qu'un élève modifie les notes ;
- il est extrêmement gênant qu'un élève voie les notes des autres élèves ;
- il est gênant qu'un élève voie le sujet du prochain QCM ;
- si le professeur doit rentrer son mot de passe à chaque page, il n'utilisera pas le logiciel ;
- le professeur ne va pas laisser le logiciel de notes ouvert en pleine classe, mais peut laisser une page de présentation ou de QCM.
Il apparait logique de créer des zones ultrasécurisées, et d'autres moins sécurisées, mais moins contraignantes. Voici les zones :
- NONE : accès direct pour les personnes non inscrites : présentation, tutoriels, etc. ;
- LAZY-INIT : cette connexion cryptée, sous HTTPS, permet de se logguer et de créer le cookie ;
- LAZY : on se log, puis on navigue normalement. Si on quitte le site web, on revient sans se relogguer (style forum, facebook, email) ;
- ARMY : on doit taper le mot de passe, même si on s'est déjà loggué. Army est entièrement sous HTTPS.
La plupart des sites peuvent se passer du mode ARMY !
Par conséquent, le mot de passe ne peut être enregistré ad vitam aeternam, et doit être retapé pour passer à certaines pages. Voici quelques exemples en images.
Le dernier exemple montre que deux zones Army se comportent comme deux « realm » différents de la Basic Authentication.
IV-C. Cryptage != Sécurité▲
Si HTTPS n'existait pas, les escrocs n'auraient aucun mal à pirater vos mots de passe et leur business serait juteux. Puisque HTTPS existe, le crime ne paie pas et peu d'escrocs s'amusent à renifler vos requêtes donc paradoxalement crypter ou pas un site web a finalement peu d'influence sur la sécurité.
Le crime est dans la tentation ! Comptez davantage sur votre conjoint et surtout votre collègue pour vous piéger - éventuellement des élèves dans une école. C'est donc l'accès aux mots de passe qu'il faut protéger, et on en est bien loin. Faites un tour dans l'onglet Outils -> Options -> Mots De passe -> Mots de passe enregistrés du Firefox à votre travail.
Ces mots de passe sont tout autant lisibles dans les autres navigateurs s’ils ont été sauvés, et si l'on cherche un peu. Il ne faut donc jamais sauver son mot de passe d'email ou de banque sur son lieu de travail, et ne pas utiliser un mot de passe générique (c.-à-d. celui utilisé pour developpez.com, hattrick, labrute, etc. ) pour l'email, la banque ou d'autres applications web sensibles, comme Facebook.
Plus subtile est la récupération du cookie. Supposons que vous vous connectiez à Facebook au boulot, sans explicitement sauver le mot de passe. Un intrus peut copier le cookie (en 20 secondes avec une clé USB, ou par réseau) puis se connecter tranquillement sur son PC avec votre compte. Heureusement, le cookie contient rarement le mot de passe en clair.
Supposons maintenant que tous les sites web gèrent leurs cookies de la même façon, avec des utilisateurs utilisant le même mot de passe sur la plupart des sites. En récupérant un cookie de Facebook, vous pourriez vous connecter sur le compte Gmail. Le plus sûr est donc de faire intervenir une partie aléatoire dans la génération du cookie.
Dernier point, encore plus pernicieux. Vous faîtes un site web innocent jaimelesoranges.com, sauvez les mots de passe en clair, et un collègue mal intentionné a accès à la base de données. On peut coder le password en MD5, ou encore rajouter un grain de sel, ou « salt » dans la terminologie. À partir du mot de passe, vous sauvez dans la base de données MD5 (salt+password). Pour le décoder, le collègue aura besoin de l'accès à la base de données ET au code, ce qui diminue un peu les risques. Par contre, si un hacker extérieur accède à la basse de données, il n'aura pas accès au salt, et ne pourra pas décoder le mot de passe.
La seule réelle protection des utilisateurs est d'avoir un mot de passe différent pour chaque application sensible.
IV-D. Le protocole ArmyLazy▲
ArmyLazy est un protocole de sécurité inventé de toute pièce pour Robusta, mais fortement inspirée de BASIC Authentication. Il n'y a rien de révolutionnaire - et c'est une bonne chose. Comme c'est un protocole, ArmyLazy est indépendant de Java et donc de Robusta, bien que Robusta propose une interface et un Enum pour en faciliter l'implémentation.
Comme dit précédemment, il y a quatre zones à définir : NONE, LAZY-INIT, LAZY et ARMY. Sachant que la plupart des sites pourront se passer d'ARMY.
IV-D-1. Un peu de vocabulaire▲
Dans le header Authorization : LAZY-INIT amFtZXNib25kOmhvd2lzeW91cmJsYW5xdWV0dGU=
- L'ensemble « LAZY-INIT amFtZXNib25kOmhvd2lzeW91cmJsYW5xdWV0dGU= » est appelé AuthorizationValue
- La partie codée « amFtZXNib25kOmhvd2lzeW91cmJsYW5xdWV0dGU= » est appelé Credential
- Le prefix « LAZY-INIT » est appelé mode
IV-D-2. NONE▲
Il n'y a rien à faire, si ce n'est le préciser. Ça y est, c'est fait !
IV-D-3. LAZY-INIT▲
Par contre la partie LAZY-INIT est de loin la plus compliquée :
- le navigateur envoie une requête HTTPS ;
- le serveur décode le header Authorization ;
- le serveur valide le couple username/password ;
- le serveur crée ou update dans la base de données le lazyToken ;
- le serveur renvoie des données ;
- le navigateur analyse les données ;
- le navigateur crée les cookies ;
- le navigateur affiche la suite.
IV-D-3-a. Le navigateur envoie la requête▲
Le formulaire peut-être un FORM avec un bouton SUBMIT, ou une série de champs : le bouton envoie une requête Ajax. Quoi qu'il en soit, la page web montrant la boite doit être sous HTTPS !
Pour une requête Ajax, un username « jamesbond » et un password « howisyourblanquette », le header Autorization sera :
Authorization : LAZY-INIT Base64(« jamesbond: »+MD5(« howisyourblanquette »))
Ce qui se code en utilisant Prototype.js et robusta.js :
var username=
$F
(
"username"
),
password
=
$F
(
"password"
);
//$F() est une fonction de Prototype.js lisant les champs.
var credential =
robusta.
Codec.encodeB64
(
username+
":"
+
robusta.
Codec.md5
(
password
) );
var authorizationValue =
"LAZY-INIT "
+
credential;
new Ajax.Request
(
"/mywebapp/myjax/login"
,
{
method
:
'POST'
,
//eventuellement GET, c'est pareil
contentType
:
'application/xml;charset=UTF-8'
,
requestHeaders
:
$H
({
Authorization
:
authorizationValue}
),
onSuccess
:
function(
transport) {
//analyse de la requête
//Création des cookies
//affichage de la suite sur la même page ou redirection
}
}
);
Pour des raisons de licence (GPL vs BSD), robusta.js n'implémente pas encore directement de fonction MD5. Vous devez joindre ce fichier md5.js source créé par Paul Andrew Johnston : http://pajhome.org.uk/crypt/md5/
IV-D-3-b. Le travail du Serveur▲
Il va de soi que tout ce travail peut-être fait en PHP, ruby ou autre. L'implémentation est assez longue, donc très partiellement écrite ici, et je fournis le code source de la démo. Nous allons nous concentrer sur les bénéfices apportés par Robusta. Voici la table DbUser utilisée pour stocker les comptes.
id |
username |
passwordMD5 |
|
lazytoken |
Nous allons créer un ResourceController JAX-RS généraliste, pour tout le Web Service, implémentant l'interface ArmyLazyController. Le LoginController n'aura plus qu'à étendre ce SecuredController. Nous avons aussi besoin d'un objet implémentant robusta.security.User, qui est simple, mais suffisant. Robusta propose un objet UserImpl.
Le code Java ci-dessous récupère les credentials et extrait le username et password grâce aux fonctions Codec.getUsername(b64string) et son équivalent getPassord().
import
robusta.commons.codec.Codec;
import
robusta.security.User;
import
robusta.security.UserImpl;
import
robusta.security.ArmyLazyController;
import
robusta.security.ArmyLazy;//Plus quelques imports
class
SecuredController extends
JaxRsResourceController implements
ArmyLazyController{
User validateLazyInitAuthorization
(
) throws
UnknownUserException, ServerException, AuthenticationException{
ArmyLazy mode =
ArmyLazy.getArmyLazyMode
(
authorizationValue);
if
(
mode !=
ArmyLazy.LAZY_INIT) {
throw
new
AuthenticationException
(
"Illegal Authentication mode :"
+
mode);
}
String credentials, username, passwordMD5;
try
{
credentials =
mode.retrieveCredentials
(
authorizationValue);
username =
Codec.getUsername
(
credentials);
passwordMD5 =
Codec.getPassword
(
credentials); /* password should be in MD5 ; javascript is able to do it */
}
catch
(
Exception ex) {
throw
new
AuthenticationException
(
"Authorization Value :"
+
authorizationValue +
" - is probably not correct"
);
}
this
.user =
fetchUserWithPassword
(
username, passwordMD5);// recherche dans la database
updateLazyToken
(
user.getId
(
));// insertion du lazytoken (MD5(randomString)) dans la database.
return
user;
}
User validateLazyAuthorization
(
) throws
UnknownUserException, ServerException, AuthenticationException{}
User validateArmyAuthorization
(
) throws
UnknownUserException, ServerException, AuthenticationException{}
public
String getStoredLazyToken
(
){}
}
Ce controller pourra être utilisé dans tous les controllers de votre application. Vous validerez la sécurité et récupérerez votre User en une ligne. Voici le LoginController que tapera la requête avec le path /mywebapp/myjax/login
@Path
(
"/login"
)
public
class
LoginController extends
SecuredController {
@POST
@Produces
(
"application/xml"
)
/**
* The user must send something like :
* POST /xseditor/login
* Authorization:LAZY-INIT amFtZXNib25kOmhvd2lzeW91cmJsYW5xdWV0dGU=
*
@return
xml with idUser, username, lazyToken and email nodes and values
*/
public
Response loginUser
(
) {
try
{
System.out.println
(
"login authorization :"
+
getAuthorizationValue
(
));
User user =
validateLazyInitAuthorization
(
);
String lazyToken =
getStoredLazyToken
(
);
if
(!
StringUtilities.validateMD5
(
lazyToken))//check lazyToken ressemble à du MD5
throw
new
ServerException
(
"LazyToken is not correctly created"
);
// magic Xml function :)
String xml =
new
VsxSax
(
).buildVerySimpleXml
(
"idUser"
, user.getId
(
),
"username"
, user.getUsername
(
), "email"
, user.getEmail
(
), "lazyToken"
,lazyToken );
return
getResponse
(
xml);
}
catch
(
Exception ex) {
MyLog.sendLog
(
ex, ""
);
return
classicFailure
(
ex);
}
}
}
IV-D-3-c. Le navigateur reçoit la requête▲
Le navigateur reçoit le LazyToken sous forme de MD5, comme '9386f6cde6a557f23eeafa70c6e8e9d3'. Il y a très peu de chances que cela arrive, mais théoriquement, ce token peut être en double pour deux utilisateurs différents. Le cookie envoyé pour l'identification doit donc incorporer le username, car il est marqué Unique dans la database.
Autre problème, un cookie ne peut contenir d'espace : 'LAZY xyz=' n'est pas valable. Le cookie aura pour nom : lazy et valeur : xyz=. Je conseille fortement le nom lazy, mais la classe robusta.MyRobusta permet de renommer le nom du cookie qui sera détecté par les tags. Reprenons la fonction onSuccess () de la requête Ajax :
onSuccess
:
function
(
transport) {
//analyse de la requête : username est déjà connu
var
lazyToken =
robusta.VerySimpleXml.getValue
(
transport, "lazyToken"
);
var
idUser =
robusta.VerySimpleXml.getValue
(
transport, "idUser"
);
var
email =
robusta.VerySimpleXml.getValue
(
transport, "email"
);
//Création des cookies
document.cookie=
"idUser="
+
idUser+
";path=/"
;
document.cookie=
"email="
+
email+
";path=/"
;
document.cookie=
"username="
+
username+
";path=/"
;
var
lazyCredentials =
Base64.encode
(
username+
":"
+
lazyToken);
document.cookie=
"lazy="
+
lazyCredentials+
";path=/"
;
//affichage de la suite : dans l'exemple on affiche idUser
}
Ceci conclut le travail sur LAZY-INIT. Heureusement, le travail sur LAZY et ARMY sera quasiment identique.
IV-E. LAZY▲
Le mode LAZY permet de se connecter aux zones nécessitant une identification, mais en général non cryptées et non stratégiques. L'authentification se fait en principe par cookie, mais on peut également utiliser le header Authorization ce qui est pratique pour les tests unitaires.
IV-E-1. Tag dans une page JSP▲
Robusta fournit un tag <robusta:authentication/> qui permet de valider le cookie (ou le header Authorization), et fournit ensuite à la page l'attribut ${user} implémentant l'interface robusta.security.User. Selon votre implémentation, vous aurez accès à ce que vous voudrez de cet utilisateur. Vous avez également accès à ${authorizationValue}.
Voici un exemple :
<%@page
contentType
=
"text/html"
pageEncoding
=
"UTF-8"
%>
<%@taglib
uri
=
"http://java.sun.com/jsp/jstl/core"
prefix
=
"c"
%>
<%@ taglib
uri
=
"http://www.robustaweb.com"
prefix
=
"robusta"
%>
<
jsp
:
useBean
id
=
"controller"
class
=
"demo.myjax.SecuredController"
scope
=
"request"
/>
<
robusta
:
authentication
controller
=
"${controller}"
/>
<html><body>
User Id : ${user.id}<br/>
Email : ${user.email}
</body></html>
Notre page JSP utilise comme bean un JaxRSResourceController, sans utiliser les capacités liées à @Context. Cela montre la versatilité de la spécification JAX-RS. Notez aussi que JspResourceContoller aurait très bien pu être utilisé, en y implémentant l'interface ArmyLazyController.
IV-E-2. Fonctionnement interne▲
Le fonctionnement est simple :
- la requête arrive sur la page JSP avec un cookie lazy ;
- un javabean SecuredController est créé ;
- le tag <robusta:authentication/> signale l'authorizationValue (la valeur du cookie) au controller ;
- le tag appelle la fonction validateLazyAuthentication() du controller ;
- le tag crée l'attribut ${user}.
Notons d'une part que l'implémentation de validateLazyAuthentication() est très proche de celle du mode LAZY-INIT, et d'autre part que rien dans la page jsp ne gère automatiquement une exception, notamment en cas d'absence de cookie.
IV-E-3. Résultat▲
Voici le résultat lors de l'appel d'une page nécessitant une identification par cookie.
IV-E-4. Détection dans un Service Web▲
Nous avons utilisé le tag <robusta:authentication/>, mais JAX-RS a accès aux données de la requête (HttpServletRequest) grâce à l'utilisation de l'annotation @Context. JaxRsResourceController utilise ce système pour détecter le cookie et le header de la requête. Si votre ResourceController hérite de JaxRsResourceController, vous n'avez plus qu'à utiliser getAuthorizationValue().
IV-F. ARMY▲
Après avoir sécurisé votre domaine par HTTPS, le mode ARMY est extrêmement simple à implémenter une fois les autres modes assimilés. Le problème est de récupérer les valeurs du username et du password. En effet, une page web ne gère aucun état, excepté le cookie qui n'est pas sécurisé.
La solution consiste donc à passer par une page de Login, avec la fameuse boite demandant le couple username/pasword. Puis on fait la suite par Ajax. Ajax est en effet ici la solution de tous les problèmes d'état, car l'application peut se comporter comme une application Desktop.
Autrement, passer d'une page (ou URI) à une autre n'est pas aisé. Est-ce vraiment nécessaire ? Posez-vous bien la question, car cela augmente aussi les risques de faille de sécurité. Si votre réponse est affirmative, et si les exigences en ergonomie ne permettent pas de redemander le password, il existe heureusement des solutions. Vous pouvez faire passer la forme MD5 du password en paramètre de requête dans l'URL - c'est crypté ! Plus discret, il est possible d'utiliser le header « WWW-Authenticate » de la réponse pour faire passer ce password ; c'est ce qu'utilise l'authentification DIGEST.
IV-G. Conclusions▲
Il existe bien d'autres solutions de sécurité : JAAS, Acegi (Spring Security), etc. Le protocole ArmyLazy ne repose pas sur Java, et peut être aussi bien utilisé en PHP qu'en Ruby. Par extension, il n'y a pas non plus de dépendance à un serveur particulier. Cependant il vous faudra implémenter l'ensemble, car Robusta ne fournit qu'une interface et un Enum.
ArmyLazy ne fonctionne pas sur le principe d'Inversion de Controle, ni d'Interceptor. Côté serveur, cela se passe dans le code, et l'interface ArmyLazyController fournit à chaque fois un objet User, tout de suite réutilisable. Vous pouvez cependant utiliser le protocole ArmyLazy et vous passer sans difficulté de ce Controller, ou écrire le vôtre.
De plus, ArmyLazy ne protège pas votre application contre tout type d'injection de code. La classe robusta.utils.StringUtilities propose quelques fonctions efficaces, mais vous devrez pointer à chaque requête quelles sont les dangers potentiels, ainsi que définir le type de données acceptables.
Enfin, ne vous reposez pas sur la technologie. Si votre utilisateur ne voit pas de données sensibles à l'écran ou s'il ne s'est pas loggué le matin, il sera moins vigilant et un intrus pourra le surprendre. Ergonomie = Sécurité.