BT

Diffuser les Connaissances et l'Innovation dans le Développement Logiciel d'Entreprise

Contribuez

Sujets

Sélectionner votre région

Accueil InfoQ Articles Sécurité des applications avec Apache Shiro

Sécurité des applications avec Apache Shiro

Êtes-vous frustré lorsque vous essayez de sécuriser vos applications ? Pensez-vous que les solutions de sécurité Java existantes sont difficiles à utiliser et ne font que vous embrouiller un peu plus? Cet article présente Apache Shiro, un framework de sécurité Java qui fournit une approche simple mais puissante pour la sécurité des applications. Il explique les objectifs du projet Apache Shiro, les philosophies architecturales et comment vous pouvez utiliser Shiro pour sécuriser vos propres applications.

Qu'est-ce qu'Apache Shiro?

Apache Shiro (prononcé "shee-roh", le mot japonais pour «château») framework de sécurité Java puissant et facile à utiliser qui prend en compte la gestion de l'authentification, des autorisations, la cryptographie et la gestion des sessions, qui peut être utilisé pour sécuriser une application - des applications en ligne de commande, applications mobiles aux plus imposantes applications Web/d'entreprise.

L'API de Shiro permet de traiter les aspects suivants (que j'aime appeler les 4 piliers de la sécurité des applications):

  • Authentification - prouver l'identité de l'utilisateur, le 'login'.
  • Autorisation - contrôle des droits d'accès
  • Cryptographie - protéger ou cacher des données des regards indiscrets
  • Gestion de session - garder un état par utilisateur tout en prenant en compte des contraintes de durée

Shiro prend également en charge certaines fonctions auxiliaires, telles que la sécurité des applications Web, les tests unitaires, et le support du multithreading, mais ceux-ci renforcent essentiellement les quatre principaux aspects ci-dessus.

Pourquoi at-on créé Apache Shiro?

Pour qu'un framework puisse vraiment légitimiser son existence, et ainsi vous donner une bonne raison de l'utiliser, il devrait satisfaire des besoins qui ne sont pas satisfaits par d'autres alternatives. Pour comprendre cela, nous devons regarder l'histoire de Shiro et les solutions alternatives existantes quand il a été créé.

Avant d'intégrer l'Apache Software Foundation en 2008, Shiro avait déjà 5 ans et était précédemment connu sous le nom de projet JSecurity, qui a démarré début 2003. En 2003, il n'y avait pas beaucoup de solutions alternatives de sécurité génériques pour les développeurs d'applications Java - nous étions en somme un peu coincé avec le Java Authentication and Authorization Service, autrement connu comme JAAS. Il y avait beaucoup de lacunes dans JAAS - bien que ses capacités en termes d'authentification étaient assez satisfaisantes, les aspects d'autorisation étaient obtus et frustrants à utiliser. En outre, JAAS était fortement lié à des problèmes de sécurité au niveau machine virtuelle, comme par exemple déterminer si une classe peut être autorisée à être chargée dans la machine virtuelle Java. En tant que développeur de l'application, je me souciais plus de ce qu'un utilisateur final de mon application peut faire plutôt que de ce que mon code pourrait faire à l'intérieur de la JVM.

Concernant les applications sur lesquelles je travaillais à l'époque, j'avai aussi besoin d'avoir accès à un mécanisme de session propre, indépendant de mon conteneur applicatif. Sur le marché les seuls choix concernant les sessions à l'époque étaient HttpSessions, qui nécessitait un conteneur Web, ou les Stateful Session Beans d'EBJ 2.1, qui nécessitaient un conteneur EJB. J'avais besoin quelque chose qui puisse être découplé du conteneur, utilisable dans n'importe quel environnement de mon choix.

Enfin, il y avait la question de la cryptographie. Nous avions tous à un moment ou à un autre besoin de sécuriser les données, mais le standard Java Cryptography Architecture était difficile à comprendre à moins d'être un véritable expert en cryptographie. L'API était pleine de "checked exceptions" et avait une certaine lourdeur à l'utilisation. J'espérais voir arriver une solution clé-en-mains plus propre et efficace pour facilement crypter et de décrypter les données selon les besoins.

Donc, en regardant le paysage de la sécurité du début de 2003, vous pouvez rapidement réaliser qu'il n'y avait rien qui puisse satisfaire à toutes ces exigences dans un framework unique et cohérent. A cause de çà, JSecurity, puis plus tard Apache Shiro, est né.

Pourquoi voudriez-vous utiliser Apache Shiro aujourd'hui?

Le paysage des frameworks a quelque peu changé depuis 2003, et il devrait donc toujours y avoir une raison impérieuse d'utiliser Shiro aujourd'hui. Il y a bien quelques raisons en fait. Apache Shiro est:

  • Facile à utiliser - La facilité d'utilisation est le but ultime du projet. La sécurité des applications peut être extrêmement déroutante et frustrante et est souvent considéré comme un «mal nécessaire». Si vous la rendez si facile à utiliser que des programmeurs débutants peuvent commencer à l'utiliser, cela n'est plus aussi douloureux.
  • Global - Il n'existe pas d'autre framework de sécurité ayant l'ampleur et la portée que vise Apache Shiro, qui peut donc probablement être la "solution universelle" à vos besoins en termes de sécurité.
  • Flexible - Apache Shiro peut fonctionner dans n'importe quel environnement d'application. Bien qu'il fonctionne dans les environnements web, EJB et avec l'IoC (Inversion de Contrôle), il n'en a pas besoin. Shiro ne ne prescrit aucune spécification et n'a pas non plus de nombreuses dépendances.
  • Adapté au Web - Apache Shiro a un fantastique support des applications Web, et vous permet de créer des politiques de sécurité flexibles basées sur les URL et les protocoles d'application Web (par exemple, REST), tout en fournissant un ensemble de bibliothèques JSP pour contrôler la sortie sur les pages web.
  • Intégrable - L'API propre de Shiro et les design patterns utilisés, font qu'il est facile de s'intégrer à de nombreux autres frameworks et applications. Vous verrez Shiro s'intégrer de façon transparente avec des frameworks comme Spring, Grails, Wicket, Tapestry, Mule, Apache Camel, Vaadin, et bien d'autres.
  • Supporté - Apache Shiro fait partie de l'Apache Software Foundation, une organisation connue pour agir dans le meilleur intérêt de sa communauté. Les groupes de développement et d'utilisateurs du projet sont pleins de citoyens serviables prêts à vous aider. Des sociétés commerciales, comme Katasoft, fournissent également un support et des services professionnels si vous le désirez.

Qui se sert de Shiro?

Shiro et son prédécesseur JSecurity ont été utilisés pendant des années dans des projets pour des entreprises de toutes tailles et de tout secteurs d'activité. Depuis qu'il est devenu un projet Apache de premier niveau, le trafic du site et l'adoption ont continué à croître de manière significative. Beaucoup de communautés open-source utilisent aussi Shiro, comme par exemple Spring, Grails, Wicket, Tapestry, Tynamo, Mule, et Vaadin, pour n'en nommer que quelques-uns.

Les sociétés commerciales comme Katasoft, Sonatype, MuleSoft, l'un des principaux réseaux sociaux, et plusieurs banques New-Yorkaises utilisent Shiro pour sécuriser leurs logiciels commerciaux et leurs sites Web.

Concepts de base: Sujets, Gestionnaire de Sécurité et Domaines

Maintenant que nous avons couvert les avantages de Shiro, nous allons passer directement à la découverte de son API afin que vous puissiez avoir une idée de son utilisation. L'architecture de Shiro a trois concepts principaux - le sujet, la classe SecurityManager (gestionnaire de sécurité) et les royaumes.

Sujet

Lorsque vous sécurisez votre application, les questions probablement les plus pertinentes à se poser sont: "Qui est l'utilisateur courant?" ou "Est-ce que l'utilisateur courant a la permission de faire X"? Il est commun pour nous de nous poser ces questions pendant que nous codons ou pendant la conception d'interfaces utilisateur: les applications sont généralement construites sur la base de cas d'utilisations, et vous voulez des fonctionnalités représentées (et sécurisées) utilisateur par utilisateur. Ainsi, le moyen le plus naturel pour nous de penser sécurité dans notre application est basé sur l'utilisateur courant. L'API de Shiro représente fondamentalement cette façon de penser dans son concept de Sujet.

Le mot Sujet est un terme de sécurité qui signifie fondamentalement "l'utilisateur en cours d'exécution". Il n'est tout simplement pas appelé un 'utilisateur', car le mot 'utilisateur' est habituellement associé à un être humain. Dans le monde de la sécurité, le terme "Sujet" peut se rapporter à un être humain, mais aussi à un processus tierce, un démon applicatif, ou quelque chose de semblable. Cela signifie simplement "la chose qui est en train d'interagir avec le logiciel". A toutes fins utiles cependant, vous pouvez penser au concept 'Utilisateur' de Shiro. Vous pouvez facilement acquérir le Sujet Shiro n'importe où dans votre code comme montré dans le Listing 1 ci-dessous.

Listing 1. L'acquisition du Sujet

import org.apache.shiro.subject.Subject;
import org.apache.shiro.SecurityUtils;
...
Subject currentUser = SecurityUtils.getSubject();

Une fois que vous avez acquis le Sujet, vous avez immédiatement accès à 90% de tout ce que vous voudriez faire avec Shiro concernant l'utilisateur courant, tels que connexion, déconnexion, accès à sa session, exécution de vérifications d'autorisation, et plus encore - mais gardons cela pour plus tard. Le point clé ici est que l'API de Shiro est en grande partie intuitive, car elle reflète la tendance naturelle pour les développeurs à penser contrôle de la sécurité "par utilisateur". Il est également facile d'accéder à un Sujet n'importe où dans le code, ce qui permet de réaliser des opérations de sécurité partout où elles sont nécessaires.

SecurityManager

La contrepartie "en coulisses" du Sujet est le SecurityManager ou gestionnaire de sécurité. Alors que le Sujet représente les opérations de sécurité pour l'utilisateur actuel, le SecurityManager gère les opérations de sécurité pour tous les utilisateurs. C'est le cœur de l'architecture de Shiro et il agit comme une sorte d'objet 'chapeau' qui référence de nombreux composants de sécurité imbriqués en interne, formant un graphe d'objets. Cependant, une fois que le SecurityManager et son graphe d'objets interne est configuré, il est généralement laissé tranquille et les développeurs d'applications passent presque tout leur temps à utiliser l'API Sujet.

Alors, comment mettre en place un gestionnaire de sécurité? Eh bien, cela dépend de votre environnement d'application. Par exemple, une application Web va généralement spécifier un filtre de servlet Shiro dans web.xml, et cela mettra en place l'instance de SecurityManager. Si vous utilisez une application client lourd, vous aurez besoin de le configurer d'une autre façon. Mais il y a beaucoup de modes de configuration.

Il y a presque toujours une instance de SecurityManager unique par application. Il s'agit essentiellement d'un singleton au niveau application (même si il n'a pas besoin d'être un singleton statique). Comme presque toutes choses dans Shiro, les implémentations par défaut du SecurityManager sont des POJO et sont configurables avec n'importe quel mécanisme de configuration compatible avec les POJO - code Java standard, Spring XML, YAML, fichiers .properties et fichiers .ini, etc... Fondamentalement tout ce qui est capable d'instancier des classes et d'appeler des méthodes compatibles JavaBeans peut être utilisé.

À cette fin, Shiro propose une solution par défaut «plus petit dénominateur commun» basé sur les fichiers de configuration texte INI. INI est facile à lire, facile à utiliser et nécessite très peu de dépendances. Vous verrez aussi que, avec une compréhension simple de la navigation de graphes d'objets, INI peut être utilisé efficacement pour configurer un graphe d'objets simple comme le SecurityManager. Notez que Shiro prend également en charge les fichiers de configuration XML de Spring ainsi que d'autres alternatives, mais nous allons couvrir ici INI.

L'exemple le plus simple de configuration de Shiro basée sur INI est illustré dans l'exemple du Listing 2 ci-dessous.

Listing 2. Configuration Shiro avec INI

[main]
cm = org.apache.shiro.authc.credential.HashedCredentialsMatcher
cm.hashAlgorithm = SHA-512
cm.hashIterations = 1024
# Base64 encoding (less text):
cm.storedCredentialsHexEncoded = false
iniRealm.credentialsMatcher = $cm

[users]
jdoe = TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJpcyByZWFzb2
asmith = IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbXNoZWQsIG5vdCB

Dans le Listing 2, nous pouvons voir la configuration INI exemple que nous allons utiliser pour configurer l'instance de SecurityManager. Il ya deux sections INI: [main] et [users].

La section [main] vous permet de configurer l'objet SecurityManager et / ou les objets (comme les Domaines) utilisés par le SecurityManager. Dans cet exemple, on voit deux objets étant configurés:

  • L'objet cm, qui est une instance de la classe Shiro HashedCredentialsMatcher. Comme vous pouvez le voir, les diverses propriétés de l'instance cm sont configurés via une syntaxe de 'points imbriqués'- une convention utilisée par le IniSecurityManagerFactory dans le Listing 3, pour représenter la navigation de graphe d'objets et le paramétrage de propriétés.
  • L'objet iniRealm, qui est un composant utilisé par le gestionnaire de sécurité pour représenter des comptes utilisateurs définis dans le format INI.

Dans la section [users] vous pouvez spécifier une liste statique de comptes utilisateurs - pratique pour des applications simples ou lors des tests.

Pour les besoins de cette introduction, il n'est pas important de comprendre les subtilités de chaque section, mais plutôt de voir que la configuration INI est un moyen simple de configurer Shiro. Pour plus d'informations sur la configuration INI, veuillez vous référer à la documentation de Shiro.

Listing 3. Chargement du fichier de configuration shiro.ini

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.util.Factory;
...

//1. Charger la configuration INI
Factory<SecurityManager> factory =
new IniSecurityManagerFactory("classpath:shiro.ini");

//2. Créer le SecurityManager
SecurityManager securityManager = factory.getInstance();

//3. Le rendre accessible
SecurityUtils.setSecurityManager(securityManager);

Dans le Listing 3, nous voyons un exemple simple d'un processus en trois étapes:

  • Charger la configuration INI qui permet de configurer le SecurityManager et ses composants.
  • Créer l'instance de SecurityManager en fonction de la configuration (en utilisant la Factory de Shiro qui implémente le patron de conception Fabrique), ou Factory Method).
  • Rendre le singleton SecurityManager accessible à l'application. Dans cet exemple simple, nous le définissons comme un singleton statique dans toute la machine virtuelle, mais ce n'est généralement pas nécessaire - votre mécanisme de configuration de l'application permettra souvent de déterminer si vous avez besoin d'utiliser la mémoire statique ou non.

Domaines

Le troisième et dernier concept de base de Shiro est celui de Domaine ("Realm"). Un Domaine joue le rôle de 'pont' ou 'connecteur' entre Shiro et les données sécurisées de votre application. Autrement dit, lorsque vient le temps de réellement interagir avec les données sécurisées, telles que les comptes utilisateur pour l'authentification (login) et l'autorisation (contrôle d'accès), Shiro cherche beaucoup de ces élément dans un ou plusieurs Domaines configurés pour l'application.

En ce sens, un Domaine est essentiellement un DAO spécifique à la sécurité: il encapsule les détails de connexion pour les sources de données et met les données associés à disposition de Shiro selon les besoins. Lors de la configuration Shiro, vous devez spécifier au moins un Domaine à utiliser pour l'authentification et / ou l'autorisation. Plus d'un Domaine peuvent être configurés, mais au moins un est requis.

Shiro fournit des Domaines clés-en-mains pour se connecter à un certain nombre de sources de données de sécurité (aussi appellés répertoires) tels que LDAP, une base de données relationnelle (JDBC), des sources de configuration texte comme INI et les fichiers .properties, et plus encore. Vous pouvez brancher vos propres implémentations de Domaines pour représenter des sources de données personnalisées si les Domaines par défaut ne répondent pas à vos besoins. Le Listing 4 ci-dessous est un exemple de configuration Shiro (via INI) d'utilisation d'un annuaire LDAP comme l'un des Domaines de l'application.

Listing 4. Exemple d'extrait de configuration de Domaine pour la connection à un répertoire de données utilisateurs LDAP

[Main]
ldapRealm = org.apache.shiro.realm.ldap.JndiLdapRealm
ldapRealm.userDnTemplate = uid={0},ou=users,dc=mycompany,dc=com
ldapRealm.contextFactory.url = ldap://ldapHost:389
ldapRealm.contextFactory.authenticationMechanism = DIGEST-MD5 

Maintenant que nous avons vu comment mettre en place un environnement de base Shiro, nous allons discuter de la façon dont vous, en tant que développeur, pourriez utiliser le framework.

Authentification

L'authentification est le processus consistant à vérifier l'identité d'un utilisateur. Autrement dit, quand un utilisateur s'authentifie avec une application, il prouve qu'il est réellement ce qu'il prétend être. Ceci est également parfois appelé «login». Il s'agit généralement d'un processus en trois étapes.

  • Collecter les informations d'identification de l'utilisateur, le commettant, et la preuve d'identité associée, parfois aussi appelée facteur d'authentification.
  • Soumettre le commettant et sa preuve au système.
  • Si le(s) facteur(s) d'authentification présenté(s) correspond(ent) à ce que le système attend pour cette identité utilisateur (ce commettant), l'utilisateur est considéré comme authentifié. S'ils ne correspondent pas, l'utilisateur n'est pas considéré comme authentifié.

Un exemple courant de ce processus que tout le monde connaît est celui de la combinaison nom d'utilisateur / mot de passe. Quand la plupart des utilisateurs se connectent à une application logicielle, ils fournissent habituellement leur nom d'utilisateur (le commettant) et leur mot de passe (facteur d'identification). Si le mot de passe (ou une représentation de celui-ci) stockée dans le système correspond à ce que l'utilisateur indique, il est considéré comme authentifié.

Shiro supporte ce processus d'une manière simple et intuitive. Comme nous l'avons dit, Shiro a une API centrée sur le Sujet - presque tout ce que vous vous souciez de faire avec Shiro lors de l'exécution est assuré par l'interaction avec le Sujet courant. Ainsi, pour authentifier un Sujet, il vous suffit d'appeler sa méthode de connexion, en passant une instance d'AuthenticationToken qui représente le commettant et les facteurs d'authentification présentés (dans ce cas, un nom d'utilisateur et mot de passe). Cet exemple est montré dans le Listing 5 ci-dessous.

Listing 5. Connexion d'un Sujet

//1. Acquérir le commettant et les facteurs d'authentification soumis:
AuthenticationToken token =
new UsernamePasswordToken(username, password);

//2. Récupérer le Sujet courant:
Subject currentUser = SecurityUtils.getSubject();

//3. Login:
currentUser.login(token);

Comme vous pouvez le voir, l'API de Shiro reflète facilement le processus commun. Vous allez continuer de constater cette simplicité comme un thème de fond dans l'ensemble des opérations liées au Sujet. Lorsque la méthode de connexion est appelée, le SecurityManager va recevoir l'AuthenticationToken et l'envoyer à un ou plusieurs Domaines configurés, permettant à chacun d'effectuer les contrôles d'authentification requis. Chaque Domaine a la capacité de réagir de manière adhoc aux AuthenticationTokens soumis. Mais qu'advient-il si la tentative de connexion échoue? Que faire si l'utilisateur a spécifié un mot de passe incorrect? Vous pouvez gérer les échecs en réagissant aux runtime exceptions AuthenticationException comme dans le Listing 6.

Listing 6. Traiter les échecs de connexion

//3. Login:
try {
    currentUser.login(token);
} catch (IncorrectCredentialsException ice) { …
} catch (LockedAccountException lae) { …
}

catch (AuthenticationException ae) {…

Vous pouvez choisir d'intercepter l'une des sous-classes d'AuthenticationException et de réagir spécifiquement ou bien gérer de manière générique toutes les AuthenticationException (par exemple, montrer à l'utilisateur un message générique du type "nom d'utilisateur ou mot de passe incorrect"). Le choix est vôtre en fonction des exigences de votre application.

Après qu'un Sujet se soit connecté avec succès, il est considéré comme authentifié et habituellement vous lui permettez d'utiliser votre application. Mais juste parce qu'un utilisateur a prouvé son identité ne signifie pas qu'il peut faire ce qu'il veux dans votre application. Cela soulève la question suivante: "Comment puis-je contrôler ce que l'utilisateur est autorisé à faire ou pas?" Décider de ce que les utilisateurs sont autorisés à faire est appelé l'*autorisation*. Nous allons couvrir comment Shiro permet l'autorisation dans le chapitre suivant.

Autorisation

L'autorisation est essentiellement le contrôle d'accès - contrôler ce à quoi vos utilisateurs peuvent accéder dans l'application, que ce soit les ressources, les pages Web, etc... La plupart des utilisateurs effectuent un contrôle d'accès en utilisant des concepts tels que les rôles et les permissions. Autrement dit, un utilisateur est généralement autorisé à faire quelque chose ou pas basé sur quels rôles et/ou permissions leur sont assignés. Votre application peut alors contrôler quelles fonctionnalités sont exposées en effectuant des contrôles pour ces rôles et permissions. Comme on pouvait s'y attendre, l'API Sujet vous permet d'effectuer des vérifications de rôles et de droits très facilement. Par exemple, l'extrait de code dans le Listing 7 montre comment vérifier si un Sujet possède un certain rôle.

Listing 7. Vérifier l'attribution d'un rôle

if ( subject.hasRole(“administrator”) ) {
    //montrer le boutton ‘Créer Utilisateur’
} else {
    //griser le boutton?

Comme vous pouvez le voir, votre application peut activer ou désactiver des fonctionnalités en se basant sur des contrôles d'accès.

Les contrôles de permissions sont une autre façon de procéder à l'autorisation. Vérifier des rôles comme dans l'exemple ci-dessus souffre d'un défaut important: vous ne pouvez pas ajouter ou supprimer des rôles à l'exécution. Les noms de rôles sont codés en dur, donc si vous modifiez les noms de rôle et / ou leur configuration, votre code ne fonctionnera plus! Si vous avez besoin d'être en mesure de changer le sens d'un rôle à l'exécution, ou d'ajouter et de supprimer des rôles comme vous le souhaitez, vous devez vous reposer sur autre chose.

À cette fin, Shiro propose sa notion de permissions. Une permission exprime de manière brute une fonctionnalité, par exemple "ouvrir une porte", "créer une entrée de blog", "supprimer l'utilisateur jsmith", etc... En faisant en sorte que les permissions reflètent les fonctionnalités basiques de votre application, vous n'avez besoin de changer les permissions que lorsque ces fonctionnalités changent. Par la suite, vous pouvez attribuer autant de permissions que nécessaire aux rôles ou aux utilisateurs, à l'exécution.

A titre d'exemple, dans le Listing 8 ci-dessous, nous pouvons réécrire notre vérification de rôle précédente pour utiliser plutôt une vérification de permissions.

Listing 8. Vérifier les permissions

if ( subject.isPermitted(“user:create”) ) {
    //montrer le boutton ‘Créer Utilisateur’
} else {
    //griser le boutton?
}

De cette manière, n'importe quel rôle ou utilisateur ayant la permission "user:create" pourra cliquer sur le bouton "Créer Utilisateur", et ces rôles et attributions peuvent même changer à l'exécution, vous offrant ainsi un modèle de sécurité très flexible.

La chaîne de caractères "user:create" est un exemple de chaîne de permission qui adhère à certaines conventions facilitant son analyse. Shiro supporte cette convention nativement avec son WildcardPermission. Bien que hors du champ de cet article d'introduction, vous verrez que la WildcardPermission peut être extrêmement flexible lors de la création des politiques de sécurité, et permet même un certain contrôle d'accès jusqu'au niveau de l'instance.

Listing 9. Vérifier une Permission au niveau de l'instance

if ( subject.isPermitted(“user:delete:jsmith”) ) {
    //supprimer l'utilisateur ‘jsmith’
} else {
    //ne pas supprimer ‘jsmith’
}

Cet exemple montre que vous pouvez contrôler l'accès à des ressources individuelles, même à un niveau de granularité très fin, si vous en avez le besoin. Vous pouvez même inventer votre propre syntaxe de permissions si vous voulez. Voir la documentation des permissions dans Shiro pour plus d'informations. Enfin, tout comme pour l'authentification, les appels ci-dessus finissent par faire leur chemin vers le SecurityManager, qui consultera un ou plusieurs Domaines pour prendre les décisions de contrôle d'accès. Ceci permet à un Domaine de répondre à la fois aux demandes d'opérations d'authentification et d'autorisation selon les besoins.

Voilà donc un bref aperçu des fonctionnalités d'autorisation de Shiro. Mais tandis que la plupart des frameworks de sécurité s'arrêtent à l'authentification et l'autorisation, Shiro offre beaucoup plus. Dans le prochain chapitre, nous allons parler des fonctionnalités avancées de gestion de session de Shiro.

Gestion des sessions

Apache Shiro offre quelque chose d'unique dans le monde des frameworks de sécurité: une API de session cohérente et utilisable dans n'importe quelle application et à n'importe quel niveau architectural. C'est-à-dire que Shiro permet d'implémenter le paradigme de Session dans n'importe quelle application - des petites applications autonomes de type démons aux plus grandes des applications Web en cluster. Cela signifie que les développeurs d'applications qui souhaitent utiliser les sessions ne sont plus obligés d'utiliser des servlet ou des conteneurs d'EJB s'ils n'en ont pas besoin autrement. Ou, s'ils utilisent ces conteneurs, les développeurs ont désormais la possibilité d'utiliser une API de Session unifiée et cohérente dans n'importe quel tiers, au lieu de devoir se reposer sur des mécanismes liés spécifiquement aux servlets ou aux EJB.

Mais peut-être que l'un des avantages les plus importants de la gestion des sessions dans Shiro est qu'elle est indépendante du conteneur. Ceci a des implications subtiles mais extrêmement puissantes. Par exemple, considérons le clustering de session. Combien existe-t'il de méthodes spécifiques à un conteneur pour mettre les sessions en cluster afin de garantir la tolérance aux pannes et le basculement en cas de panne? Tomcat fait cela différemment de Jetty, qui fait cela différemment de WebSphere, etc... Mais avec des sessions Shiro, vous obtenez une solution de clustering indépendante. L'architecture de Shiro permet de stocker, par un système de plugins, les Sessions dans des magasins de données variés, tels que les caches d'entreprise, bases de données relationnelles, systèmes NoSQL et bien plus encore. Cela signifie que vous pouvez configurer le clustering de session une fois et qu'il va fonctionner de la même façon quel que soit votre environnement de déploiement - Tomcat, Jetty, un serveur JEE ou une application autonome. Il n'est pas nécessaire de reconfigurer votre application en fonction de la façon dont vous la déployez.

Un autre avantage des Sessions de Shiro est que les données de session peuvent être partagées entre les technologies clientes si désiré. Par exemple, un client lourd Swing peut participer à la session d'une application Web si vous le souhaitez - utile si l'utilisateur final utilise les deux interfaces simultanément. Alors, comment accédez-vous à la session d'un Sujet dans n'importe quel environnement? Il existe deux méthodes de la classe Sujet comme le montre l'exemple ci-dessous.

Listing 10. Accès à la Session d'un Sujet

Session session = subject.getSession();
Session session = subject.getSession(boolean create);

Comme vous pouvez le voir, dans leur concept ces méthodes sont identiques à l'API HttpServletRequest. La première méthode va retourner la Session existante du Sujet courant, ou en créer une s'il n'en existe pas. La seconde méthode accepte un argument de type booléen qui détermine si oui ou non une nouvelle session sera créée si elle n'existe pas encore. Une fois que vous avez récupéré la Session du sujet, vous pouvez l'utiliser presque à l'identique d'une HttpSession. L'équipe de Shiro a estimé que l'API HttpSession était la plus confortable pour les développeurs Java, donc nous avons conservé une grande partie de son "ressenti". La grande différence, bien sûr, c'est que vous pouvez utiliser les Sessions Shiro dans n'importe quelle application, et pas seulement des applications web. Le Listing 11 montre cette ressemblance.

Listing 11. Méthodes de session

Session session = subject.getSession();
session.getAttribute(“key”, someValue);
Date start = session.getStartTimestamp();
Date timestamp = session.getLastAccessTime();
session.setTimeout(millis);
...

Cryptographie

La cryptographie est le processus de cacher ou de rendre inintelligibles des données de sorte que des regards indiscrets ne puissent pas les comprendre. L'objectif de Shiro en termes de cryptographie est de simplifier et de rendre utilisable le support de la cryptographie du JDK.

Il est important de noter que la cryptographie n'est pas une partie de l'API de Shiro qui soit spécifique aux Sujets. Vous pouvez utiliser le support cryptographie de Shiro n'importe où, même si aucun Sujet n'est défini/utilisé. Les deux domaines cryptographiques sur lesquels Shiro se concentre vraiment sont le hachage cryptographique ("hash" ou "message digests") et le chiffrement cryptographique. Jetons un coup d'oeil à ces deux domaines plus en détail.

Hachage

Si vous avez utilisé la classe MessageDigest du JDK, vous vous êtes vite rendu compte qu'elle est un peu délicate à utiliser. Son API combine maladroitement une Factory et l'utilisation de méthodes statiques, au lieu d'être correctement orientée-objet, et vous êtes obligé d'attraper tout un tas de checked exceptions qui ne se déclencheront probablement jamais. Si vous avez besoin d'effectuer des hachages de messages encodés en hexadécimal ou en Base64, vous devrez vous débrouiller - aucun n'est supporté en standard par le JDK. Shiro adresse ces questions via une API de hachage claire et intuitive.

Par exemple, considérons le cas relativement fréquent du hachage MD5 d'un fichier et de la détermination de la valeur hexadécimale de ce hash. Appelé une "somme de contrôle" (ou "checksum"), cela est utilisé régulièrement dans le cadre de téléchargements de fichiers - les utilisateurs peuvent effectuer leur propre hachage MD5 sur le fichier téléchargé et vérifier que leur somme de contrôle correspond à celle indiquée sur le site de téléchargement. Si elles correspondent, l'utilisateur peut raisonnablement supposer que le fichier n'a pas été altéré lors du transit.

Voici comment vous pouvez essayer de le faire sans Shiro:

  • Convertir le fichier en un tableau d'octets. Il n'y a rien dans le JDK pour vous assister, vous aurez donc besoin de créer une méthode d'aide qui ouvre un FileInputStream, utilise une mémoire tampon d'octets, et déclenche le cas échéant des IOExceptions, etc...
  • Utilisez la classe MessageDigest pour hacher le tableau d'octets, tout en traitant les exceptions appropriées, comme dans le Listing 12 ci-dessous.
  • Encoder le tableau d'octets haché en caractères hexadécimaux. Il n'y a rien dans le JDK pour aider à faire cela non plus, vous aurez donc besoin de créer une autre méthode d'assistance, en opérant probablement au niveau des bits (opérateurs de décalage de bits par exemple).

Listing 12. MessageDigest JDK

try {
    MessageDigest md = MessageDigest.getInstance("MD5");
    md.digest(bytes);
    byte[] hashed = md.digest();
} catch (NoSuchAlgorithmException e) {
    e.printStackTrace();

C'est une quantité importante de travail pour quelque chose de si simple et relativement commun. Maintenant, voici comment faire exactement la même chose avec Shiro.

String hex = new Md5Hash(myFile).toHex();

Il est remarquablement plus simple et plus facile de comprendre ce qui se passe lorsque vous utilisez Shiro pour simplifier tout ce travail. Le hachage SHA-512 et l'encodage Base64 des mots de passe sont tout aussi faciles.

String encodedPassword =
new Sha512Hash(password, salt, count).toBase64();

Vous pouvez voir combien Shiro simplifie le hachage et l'encodage, vous permettant d'économiser un peu de santée mentale au passage.

Les Chiffres

Les Chiffres sont des algorithmes cryptographiques qui peuvent de manière réversible transformer des données en utilisant une clé. Nous les utilisons pour protéger les données, en particulier lors du transfert ou du stockage de celles-ci, des moments où les données sont particulièrement sensibles aux regards indiscrets.

Si vous avez déjà utilisé l'API Cryptography du JDK, et en particulier la classe javax.crypto.Cipher, vous savez qu'il peut s'agir d'une bête incroyablement complexe à apprivoiser. Pour commencer, toutes les configurations possibles de chiffrement sont toujours représentées par une instance de javax.crypto.Cipher. Besoin de faire de la cryptographie asymétrique à clé publique/privée? Vous devez utiliser un Cipher. Besoin d'utiliser un chiffrement par bloc pour des opérations de streaming? Vous devez utiliser un Cipher. Besoin de créer un cryptage AES 256-bit pour sécuriser les données? Vous devez utiliser un Cipher. Vous saisissez l'idée.

Et comment créez-vous l'instance de Cipher dont vous avez besoin? Vous créez une chaîne d'options complexe, non intuitive, sous forme de jetons d'options de chiffrement, appelé "chaîne de transformation", et vous transmettez cette chaîne à une Fabrique statique, Cipher.getInstance. Avec cette approche chaîne d'options de chiffrement, il n'y a pas de typage fort pour assurer que vous utilisez des options valides. Cela signifie aussi implicitement qu'il n'y a pas de JavaDoc pour vous aider à comprendre les options pertinentes. Et vous êtes également tenu de traiter les checked exceptions dans le cas où votre chaîne est mal formulée, même si vous savez que la configuration est correcte. Comme vous pouvez le voir, le chiffrement en utilisant le JDK est une tâche assez lourde. Ces techniques étaient autrefois standard pour les API Java il y a longtemps, mais les temps ont changé, et nous voulons une approche beaucoup plus facile.

Shiro tente de simplifier l'ensemble du concept de chiffrement cryptographique en introduisant son API CipherService. Un CipherService est ce que la plupart des développeurs veulent pour la sécurisation des données: une API simple, thread-safe, sans état, qui permette de crypter ou décrypter l'intégralité des données en un seul appel de méthode. Tout ce que vous devez faire est de fournir votre clé, et vous pouvez crypter ou décrypter le cas échéant. Par exemple, le cryptage 256-bit AES peut être utilisé comme indiqué dans le Listing 13 ci-dessous.

Listing 13. API de chiffrement Apache Shiro

AesCipherService cipherService = new AesCipherService();
cipherService.setKeySize(256);
//création d'une clé de test:
byte[] testKey = cipherService.generateNewKey();

//encrypter les octets d'un fichier:
byte[] encrypted = cipherService.encrypt(fileBytes, testKey);

L'exemple Shiro est plus simple par rapport à l'API Cipher du JDK:

  • Vous pouvez instancier un CipherService directement - pas de Fabriques étranges ou déroutantes.
  • Les options de configuration de chiffrement sont représentés en tant que getters et setters compatibles JavaBeans - il n'y a pas de "chaîne de transformation" étrange et difficile à comprendre.
  • Le chiffrement et le déchiffrement sont exécutés en un seul appel de méthode.
  • Pas besoin de vérifier des exceptions checkées. Traitez la runtime exception CryptoException si vous voulez.

Il existe d'autres avantages à l'API Shiro CipherService, tels que le support à la fois du (dé)cryptage de tableaux d'octets (appelé opérations par "blocs") ainsi que du (dé)cryptage de flux (par exemple, le cryptage audio ou vidéo).

Pas besoin de rendre la Cryptographie en Java douloureuse. Le support de la cryptographie dans Shiro a pour but de simplifier vos efforts pour garder vos données en sécurité.

Support Web

Dernier point, et non des moindres, nous allons présenter brièvement le support Shiro pour web. Shiro est livré avec un module de support du Web robuste pour aider à sécuriser les applications Web. Il est simple à mettre en place dans ce contexte. La seule chose nécessaire est de définir un filtre de servlet Shiro dans son web.xml. Le Listing 14 montre le fragment de code associé.

Listing 14. ShiroFilter dans le web.xml

<filter>
    <filter-name>ShiroFilter</filter-name>
    <filter-class>
        org.apache.shiro.web.servlet.IniShiroFilter
    </filter-class>
    <!-- ne pas mettre d'init-param indique qu'il faut
        charger la config INI depuis classpath:shiro.ini --> 
</filter>
<filter-mapping>
    <filter-name>ShiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

Ce filtre peut lire la configuration shiro.ini mentionnée ci-dessus, et vous avez donc une expérience de configuration cohérente quel que soit l'environnement de déploiement. Une fois configuré, le filtre Shiro va filtrer toutes les requêtes et s'assurer que le Sujet spécifique à la requête est disponible. Et comme il filtre toute les requêtes, vous pouvez mettre en place une logique de sécurité spécifique pour vous assurer que seules les requêtes qui répondent à certains critères sont autorisés à traverser.

Chaînes de filtres spécifiques aux URLs

Shiro prend en charge des règles de filtrage spécifiques à la sécurité grâce à sa capacité innovante à filtrer des URLs en chaîne. Cela vous permet de spécifier des chaînes de filtres ad-hoc pour tout motif d'URLs. Cela signifie que vous avez une grande souplesse dans l'application des règles de sécurité (ou la combinaisons de règles) à l'aide des mécanismes de filtrage de Shiro - beaucoup plus que ce que vous pourriez réaliser en définissant des filtres dans le fichier web.xml seul. Le Listing 15 montre un extrait de configuration dans le shiro.ini.

Listing 15. Chaînes de Filtres spécifiques aux Chemins

[urls]
/assets/** = anon
/user/signup = anon
/user/** = user
/rpc/rest/** = perms[rpc:invoke], authc
/** = authc

Comme vous pouvez le voir, il y a une section INI [url] dédiée aux applications Web. Pour chaque ligne, les valeurs à gauche du signe égal représentent un chemin relatif dépendant du contexte de l'application Web. Les valeurs sur la droite définissent une chaîne de filtres - une liste ordonnée, délimitée par des virgules, des filtres de servlet à exécuter pour le chemin donné. Chaque filtre est un filtre de servlet classique, mais les noms de filtres que vous voyez ci-dessus (anon, user, perms, authc) sont des instances particulières de filtres liées à la sécurité que fournit nativement Shiro. Vous pouvez mélanger et assortir ces filtres de sécurité pour créer une expérience de sécurité très personnalisée. Vous pouvez aussi spécifier un autre filtre de servlet existant en votre possession.

N'est-ce pas bien plus agréable comparé à l'utilisation du web.xml, où vous définissez un ensemble de filtres, puis un ensemble séparé et déconnecté de modèles de filtre? En utilisant l'approche de Shiro, il est beaucoup plus facile de voir exactement la chaîne de filtrage qui est exécutée pour un chemin donné. Si vous le souhaitiez, vous pourriez définir uniquement le ShiroFilter dans le fichier web.xml et définir l'ensemble de vos autres filtres et chaînes de filtres dans le shiro.ini pour un mécanisme de définition de filtres beaucoup plus succinct et facile à comprendre que web.xml. Même si vous n'utilisiez pas une seule des fonctionalités de sécurité de Shiro, l'aspect pratique de cette petite fonctionnalité peut en soi rendre Shiro intéressant à utiliser.

Bibliothèque de Tags JSP

Shiro fournit également une bibliothèque de balises JSP qui vous permet de contrôler la sortie de vos pages JSP en se basant sur l'état du Sujet courant. Un exemple courant où cela est utile est dans l'affichage d'un texte d'accueil "Bonjour " lors de sa connexion. Mais si l'utilisateur est anonyme, vous voudrez peut-être montrer autre chose, comme "Bonjour! Inscrivez-vous aujourd'hui!" à la place. Le Listing 16 montre comment vous pouvez réaliser cela avec des balises JSP Shiro.

Listing 16. Exemple d'utilisations des taglib JSP Shiro

<%@ taglib prefix="shiro"
    uri="http://shiro.apache.org/tags" %>
...
<p>Hello
<shiro:user>
    <!-- shiro:principal écris l'identité principale du Sujet 
        - dans ce cas, un nom d'utilisateur: -->
    <shiro:principal/>!
</shiro:user>
<shiro:guest>
    <!-- non connecté, considérer comme invité. Montrer le lien pour s'enregistrer: -->
    ! <a href=”register.jsp”>Register today!</a>
</shiro:guest>
</p> 

Il existe d'autres balises qui vous permettent de produire une sortie basée sur les rôles que les utilisateurs ont (ou n'ont pas), les permissions attribuées (ou non affectées), et si ils sont authentifiés, offrir des services du type "se souvient de moi", ou encore gérer un client anonyme.

Il existe de nombreuses autres fonctionnalités spécifiques au Web que Shiro supporte, comme des services simple de "Se souvenir de moi", des services d'authentification basés sur REST ou BASIC, et bien sûr le support transparent des sessions HttpSession si vous voulez utiliser les sessions de Shiro. Voir la documentation Apache Shiro Web pour plus de détails.

Gestion des sessions Web

Enfin, il est intéressant d'insister sur le support de Shiro des sessions dans un environnement Web.

Sessions HTTP par défaut Pour les applications Web, l'infrastructure de sessions de Shiro bascule par défaut sur les sessions existantes du conteneur de servlet auxquelles nous sommes tous habitués. Autrement dit, lorsque vous appelez les méthodes subject.getSession() et subject.getSession(boolean) Shiro retournera des instances de session s'appuyant sur l'HttpSession du conteneur de servlet. La beauté de cette approche est que le code métier qui appelle subject.getSession() interagit avec une instance de Session Shiro - il n'a aucune idée qu'il travaille en fait avec un objet HttpSession Web. C'est une très bonne chose pour maintenir une séparation nette entre les niveaux architecturaux.

Sessions natives Shiro dans le contexte du Web Si vous avez activé la gestion de sessions de Shiro dans une application web, car vous avez besoin des fonctionnalités avancées de Shiro (comme le clustering indépendant du conteneur), vous voulez évidemment que les API HttpServletRequest.getSession() et HttpSession fonctionnent avec les sessions 'native' de Shiro et pas les sessions du conteneur de servlet. Cela serait vraiment frustrant si vous aviez à refactorer tout code utilisant HttpServletRequest.getSession() ou HttpSession pour pouvoir utiliser l'API de sessions de Shiro. Shiro bien entendu ne s'attend pas à ce que vous ayez à faire ça. En fait, Shiro implémente la partie Session de la spécification des Servlets afin de supporter les sessions natives dans les applications Web. Cela signifie que dès que vous appellez une méthode de HttpServletRequest.getSession() ou HttpSession, Shiro délègue à son API interne de gestion des sessions. Le résultat final est que vous n'avez pas à changer votre code, même si vous utilisez les sessions 'natives' de Shiro - une fonctionnalité très pratique (et nécessaire) en effet.

Caractéristiques supplémentaires

Il ya d'autres fonctions dans le framework Apache Shiro qui sont utiles à la sécurisation des applications Java, telles que:

  • Support de la Concurrence et du Threading, pour utiliser des Sujets dans plusieurs threads (support des Executor et ExecutorService)
  • Support des Callable et Runnable pour exécuter de la logique métier avec les droits d'un Sujet donné.
  • Support du "Exécuter en tant que" pour prendre l'identité d'un autre Sujet (par exemple utile dans des applications d'administration)
  • Support des tests, rendant cela très facile de tester le code sécurisé par Shiro dans des tests unitaires ou d'intégration.

Limitations du framework

Bien que nous aimerions qu'il le soit, Apache Shiro n'est pas une "solution miracle" - il ne va pas résoudre tous les problèmes de sécurité sans effort. Il ya des choses que Shiro n'aborde pas qu'il pourrait être intéressant de connaître:

  • Préoccupations au niveau de la Machine Virtuelle: Apache Shiro ne traite pour le moment pas de la sécurité au niveau de la JVM, comme par exemple empêcher certaines classes de se charger en fonction d'un politique de contrôle d'accès. Néanmoins, ce n'est pas inconcevable que Shiro puisse s'intégrer aux opérations de sécurités existantes dans la JVM - c'est juste que personne n'a encore contribué dans cette direction au projet.
  • Authentification en plusieurs étapes: Shiro ne supporte pas pour l'instant en natif l'authentification en plusieurs étapes, dans laquelle un utilisateur pourrait se connecter via un mécanisme, pour se voir demander immédiatement de se connecter à nouveau via un autre mécanisme (authentification à deux facteurs par exemple). Cela a été fait dans des applications basées sur Shiro néanmoins, en faisant en sorte que l'application collecte en avance de phase toutes les données d'authentification nécessaires avant d'interagir avec Shiro. C'est une réelle possibilité que cette fonctionnalité soit supportée dans une future version de Shiro.
  • Opérations d'écriture de Domaines: pour le moment toutes les implémentations de Domaines supportent des opérations de lecture pour acquérir des informations d'authentification et d'autorisation pour la connection et le contrôle d'accès. Les opérations d'écriture, comme créer des comptes utilisateurs, des groupes, des rôles ou encore associer des utilisateurs avec des rôles ou des permissions, ne sont pas supportées. La raison est que le modèle de données nécessaire pour supporter ces opérations varie drastiquement d'une application à l'autre, et il serait donc difficile de déterminer une API commune qui puisse satisfaire tous les utilisateurs du framework.

Fonctionnalités à venir

La communauté Apache Shiro continue de grandir tous les jours, et avec elle, les fonctionnalités de Shiro aussi. Dans les prochaines versions, vous verrez probablement:

  • un mécanisme de Filtres Web plus propre qui permette de brancher plus de filtres, sans avoir à créer des sous-classes.
  • des implémentations par défaut de Domaines plus intégrables, favorisant la composition par rapport à l'héritage. Vous serez en mesure de brancher plusieurs composants capables d'aller chercher des données d'authentification et d'autorisation plutôt que d'avoir à créer des sous-classes des implémentations de Domaines de Shiro.
  • support client robuste d'OpenId et OAuth.
  • support des Captcha.
  • configuration facilitée pour les applications 100% sans état (par exemple beaucoup d'applications REST).
  • authentification multi-étapes via un protocole de requêtes/réponses.
  • autorisation à large granularité via une AuthorizationRequest.
  • grammaire ANTLR pour l'évaluation des requêtes de sécurité (ex: ('role(admin) && (guest || !group(developer))')

Résumé

Apache Shiro est un framework de sécurité Java plein de fonctionnalités, robuste et polyvalent que vous pouvez utiliser pour sécuriser toutes vos applications. En simplifiant quatre domaines de la sécurité des applications, à savoir l'authentification, l'autorisation, la gestion de session et la cryptographie, la sécurité des applications est beaucoup plus facile à comprendre et à mettre en œuvre dans des applications réelles. L'architecture simple de Shiro et la compatibilité JavaBeans lui permette d'être configuré et utilisé dans presque n'importe quel environnement. Un support Web et des fonctionnalités auxiliaires comme le multithreading et le support de tests lui donnent une finission suffisante pour fournir ce qui pourrait être votre "solution unique" pour la sécurité de vos applications. L'équipe de développement d'Apache Shiro continue à aller de l'avant, en perfectionnant le code, avec le soutien de la communauté. Avec une adoption continue de la communauté open source et des logiciels commerciaux, Shiro ne devrait faire que se renforcer.

Ressources

À propos de l'auteur

Les Hazlewood est le président de l'Apache Shiro PMC et est co-fondateur et CTO de Katasoft , une start-up se concentrant sur ​​les produits de sécurité des applications et le support profressionnel d'Apache Shiro. Les a 10 ans d'expérience en tant que développeur Java et architecte professionnel d'entreprise, avec des postes de direction chez Bloomberg, Delta Airlines, et JBoss. Les a été activement impliqué dans le développement Open Source depuis plus de 9 ans, contribuant à des projets tels que le framework Spring, Hibernate, JBoss, Openspaces et JSecurity bien sûr, le prédécesseur d'Apache Shiro. Les vit actuellement à San Mateo, Californie, où il pratique le Kendo et étudie le japonais quand il ne fait pas de la programmation.

Evaluer cet article

Pertinence
Style

Contenu Éducatif

BT