ATTENTION : Tutorial en cours d'écriture ! N'hésitez pas à nous signaler toute erreur ou suggestion.
Accès rapide : Présentation du moteur de sécurité du framework Ellipse Utilisation du composant <web:LoginForm>
Qui dit application Web, dit besoin de sécuriser les accés à cette application. D'ailleurs, cela est aussi souvent vraiment pour les applications non Web. Ce qui serait dommage, c'est de devoir réécrire un moteur de sécurité pour cette nouvelle application (avec un support de persistance, pourquoi pas vers une base de données relationnelles). C'est pour cela que le framework Ellipse propose un moteur de sécurité minimal. Il ne couvre pas une infinité de besoins, mais il est probable qu'il suffise à la majorité des cas. Il est important de remarquer qu'il a été architecturé de manière à permettre son extension, si d'aventure vous souhaitez l'enrichir. Notez aussi qu'en fait il y a deux parties bien distinctes dans ce qui va vous être présenté dans ce document. D'un côté, il y a le moteur de sécurité : il peut, sans difficulté, fonctionner pour des applications Web ou non Web. D'autre part, il y a le composant <web:LoginForm> : il s'utilise dans un formulaire Web Ellipse et se base sur le moteur de sécurité précédemment cité (dans ce cas, on considère forcément une application Web Ellipse).
<web:LoginForm>
Un moteur de sécurité doit, normalement, permettre deux types de mécanismes : l'authentification et l'autorisation. L'authentification consiste à identifier un utilisateur à partir d'un certain nombre d'informations : dans le cas du moteur de sécurité Ellipse, on en considère uniquement deux (le login et le password). Si une personne n'est pas authentifiée, l'accès à l'application lui est refusé. L'autorisation consiste, une fois un utilisateur aothentifié, à lui donner accés (ou non) à un certain nombre de resources. Par exemple, si l'utilisateur Toto (avec son mot de passe Titi) à un role d'administrateur, alors il pourra avoir accès aux pages d'administration de l'application. Dans le cas contraire, l'utilisateur (connecté à l'application) ne pourra pas accéder à ces pages. Mais il est envisageable qu'il puisse accéder à d'autres pages moins sensible en termes de sécurité.
Toto
Titi
Attention : dans l'état actuel des choses, le framework Ellipse ne gère que l'authentification. Mais dans un avenir plus ou moins proche, des possibilités en termes d'autorisations seront adjointes au framework.
Il faut aussi noter que les informations de sécurité doivent être persistante. Cela veux dire qu'on doit pouvoir sauvegarder ces informations sur un support persistant : pourquoi pas un annuaire X500 (active directory par exemple), une base de données (MySql ou autre), dans un fichier (XML ou autre) ou sur tout autre types de supports persistant. Il est important qu'une solution de sécurité puisse permettre d'envisager un grand nombre de cas. Le framework Ellipse anticipe ces possibilités en architecturant son moteur de sécurité autour d'un certain nombre d'interfaces Java. Ainsi, si une possibilité n'est actuellement pas supportée, il sera plus ou moins simple d'envisager d'étendre le système en fournissant de nouvelles implémentations pour les interfaces considérées.
Attention : toujours dans l'état actuel des choses, seules une persistance au sein d'une base de données relationnelle a été implémentée. Qui plus est, seules les bases de données MySql et JavaDB (aussi connue sous le nom de Derby) ont étaient testées (ce choix n'est pas annodin : il s'agit des deux bases de données fournies par Sun Microsystems). Plus tard, d'autres possibilités seront supportées. Mais gardez à l'esprit qu'il vous est possible de coder vos propres implémentations.
Il y a trois interfaces a connaitre pour utiliser le moteur de sécurité intégré au framework Ellipse. Elles se nomment : corelib.security.SecurityManager, corelib.security.UserManager et corelib.security.RoleManager. Analysons l'une après l'autre ces interfaces. La première, corelib.security.SecurityManager, constitue le point d'entrée sur le service de sécurité. En voici son contenu.
corelib.security.SecurityManager
corelib.security.UserManager
corelib.security.RoleManager
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
package corelib.security; /** * <p> * This interface defines methods for access to a security service. A security * service must provide two mechanisms: authentication and permissions management. * Authentication consist to identify a user and enable him (or not) connecting * to the considered system. The management of permissions allows, once the user * authenticated, him to have an access (or not) to resources. * </p> * * <p> * In the current version of the Ellipse framework, only authentication is supported. * But a future version of the framework will add the concepts of permissions. The * Ellipse framework provides the JdbcSecurityManager class : this is, of course, * an implementation of this interface that use a relational database to store * the security informations. * </p> * * @see corelib.security.JdbcSecurityManager * @see corelib.security.RoleManager * @see corelib.security.UserManager * * @author Dominique Liard * @since 0.3.6 */ public interface SecurityManager { /** * Open a session to the considered security service. * * @throws SecurityManagerException Thrown when connection to the security * service cannot be established. */ public void openSession() throws SecurityManagerException; /** * Close the session with the considered security service. * * @throws SecurityManagerException Thrown when connection to the security * service cannot be closed. */ public void closeSession() throws SecurityManagerException; /** * Returns the role manager associated to this security manager. * A role manager provided methods to manage roles. * * @return The role manager associated to this security manager. */ public RoleManager getRoleManager(); /** * Returns the user manager associated to this security manager. * A user manager provided methods to manage users. * * @return The user manager associated to this security manager. */ public UserManager getUserManager(); }
Quatre méthodes sont exposées par cette classe. Les deux premières permettent respectivement d'ouvrir et de fermer une session à système utilisé pour stocker les informations de sécurité (certainnement une base de données relationnelles, mais on peut envisager qu'il sagisse d'un autre système). Les deux autres méthodes permettent d'obtenir deux instances : un instance de type UserManager et une instance de type RoleManager. Ces deux objets permettront de gérer les utilisateurs et les roles. Voici le contenu de l'interface UserManager.
UserManager
RoleManager
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
package corelib.security; /** * This interface defines the methods used to manage User instances. * To can get a UserManager instance by asking it at your SecurityManager. * * @see corelib.security.SecurityManager * @see corelib.security.JdbcSecurityManager * @see corelib.security.User * * @author Dominique Liard * @since 0.3.6 */ public interface UserManager { /** * Check if the pair login/password represents an autorized user for the considered * application. If the identity is rejected, an exception will thrown. If the * identity is accepted, the connection number of the considered user is increased. * * @param userLogin The login for the considered user. * @param userPassword The password for the considered user. * @return The considered user instance. * * @throws BadCredientialsException Thrown if the identity is rejected. */ public User checkCredentials( String userLogin, String userPassword ) throws BadCredientialsException; /** * Insert a new user in the security system. The new used has the specified * login and the specified password. * * @param login The login for the considered user. * @param password The password for the considered user. The specified password * is automaticly encoded by this method. * @return The new user instance. * * @exception SecurityManagerException * Thrown if the new user cannot be inserted in the security system. * @exception UserAlreadyRegisteredException * Thrown if the specified login is already registered in the security system. */ public User insertUser( String login, String password ) throws SecurityManagerException ; /** * Update informations, in the security system, for the specified user. * * @param user The user instance to update. * * @throws SecurityManagerException * Thrown if this manager cannot update the user informations. */ public void updateUser( User user ) throws SecurityManagerException ; /** * Delete the specified user from the security system. * * @param user The user to delete. * * @throws SecurityManagerException * Thrown if this manager cannot remove the user. */ public void deleteUser( User user ) throws SecurityManagerException ; /** * Defines the algorithm used for encode password. User password is stored in * encoded format. * * @param clearPassword A password (in clear). * @return The encoded password. * * @throws SecurityManagerException * Thrown if password encription failed. */ public String encryptPassword( String clearPassword ) throws SecurityManagerException; }
La première méthode de cette interface est certainnement la principale : c'est elle qui permet de savoir si les informations fournies par un utilisateur constituent effectivement une identité autorisée. Si c'est le cas un objet de type User vous sera retourné (pour de plus amples informations sur le contenu de la classe User, je vous renvoie vers la documentation Javadoc). Dans le cas contraire, une exception sera déclenchée. Les autres méthodes permettent respectivement d'insérer un nouvel utilisateur, de mettre à jours les informations d'un utilisateur et de supprimer un utilisateur. Notez aussi que la dernière méthode permet de retrouver la forme encryptée d'un password (une fois un password inséré dans le système de sécurité, il ne vous sera plus possible d'en retrouver la version d'origine (en clair). Le fait que les mots de passe soient systématiquement encryptés permet, bien entendu, de garantir un maximum de sécurité.
User
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
package corelib.security; /** * This interface defines the methods used to manage Role instances. * To can get a RoleManager instance by asking it at your SecurityManager. * * @see corelib.security.SecurityManager * @see corelib.security.JdbcSecurityManager * @see corelib.security.Role * * @author Dominique Liard * @since 0.3.6 */ public interface RoleManager { /** * Select the role with the identifier specified in parameter. * * @param roleIdentifier The identifier of the role to returns. * @return The selected role. * * @exception SecurityManagerException * Thrown if the searched role don't exists. */ public Role selectRoleById( int roleIdentifier ) throws SecurityManagerException ; /** * Select the role with the name specified in parameter. * * @param roleName The name of the role to returns. * @return The selected role. * * @exception SecurityManagerException * Thrown if the searched role don't exists. */ public Role selectRoleByName( String roleName ) throws SecurityManagerException ; /** * Insert a new role into the used security system. * * @param roleName The name of the new role. * @return The new role. * * @exception SecurityManagerException * Thrown if the role cannot be inserted into the security system. * @exception RoleAlreadyRegisteredException * Thrown if the specified role name already exists in the security system. */ public Role insertRole( String roleName ) throws SecurityManagerException ; /** * Update the informations for this role (actually, only the role name). * * @param role The role to update. * * @exception SecurityManagerException * Thrown if the role cannot be updated into the security system. */ public void updateRole( Role role ) throws SecurityManagerException ; /** * Delete, on the security system, the specified role. * * @param role The role to delete. * @exception SecurityManagerException * Thrown if the specified role cannot be deleted from the seciry system. */ public void deleteRole( Role role ) throws SecurityManagerException ; }
Cette interface est en fait très similaire à la précédente, bien qu'un peu plus simple. Notons néanmoins qu'elle porte sur la manipulation des rôles qui vont être affectés aux utilisateurs.
Nous allons maintenant utiliser ces interfaces pour mettre en oeuvre un système de sécurité basé sur une base de données relationnelle. Pour ce faire, nous allons utiliser des implémentations de ces interfaces, basées sur JDBC(Java DataBase Connectivity) pour prendre en charge la connexion avec la base de données. Je vous rappelle que pour l'heure seules MySql et JavaDB (Apache Derby, c'est la même chose) sont supportées. Notez un point important : lors de la première utilisation de ces classes, si la base de données utilisée ne contient pas les tables attendues pour stocker les informations, le framework Ellipse les créera automatiquement : vous n'avez aucune action particulière à réaliser pour construire les tables dans la base. Vous vous appercevrez certainnement, à l'utilisation, que cela est fort pratique et que cela permet de gagner pas mal de temps. Il ne vous restera plus qu'a y injecter vos éventuels premiers utilisateurs et roles. L'exemple ci-dessous vous montre un exemple d'utilisation d'un moteur de sécurité (basé sur JDBC) fonctionnant dans une application Java en mode console (démarrant par un bon vieux main).
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import corelib.security.JdbcSecurityManager; import corelib.security.Role; import corelib.security.RoleManager; import corelib.security.SecurityManagerException; import corelib.security.User; import corelib.security.UserManager; import corelib.utilities.jdbc.DataSource; public class SecurityManagerSample { private static BufferedReader keyboard = new BufferedReader( new InputStreamReader( System.in ) ); private DataSource dataSource = null; private JdbcSecurityManager securityManager = null; private UserManager userManager = null; private RoleManager roleManager = null; public SecurityManagerSample() throws SecurityManagerException { this.dataSource = new DataSource() { @Override public String getUrl() { return "jdbc:mysql://localhost/TheBase"; } @Override public String getPassword() { return ""; } @Override public String getLogin() { return "root"; } @Override public String getDriver() { return "com.mysql.jdbc.Driver"; } }; this.securityManager = new JdbcSecurityManager( this.dataSource ); this.userManager = this.securityManager.getUserManager(); this.roleManager = this.securityManager.getRoleManager(); } public void init() throws SecurityManagerException { Role adminRole = this.roleManager.insertRole( "Administrator" ); Role userRole = this.roleManager.insertRole( "Simple User" ); User root = this.userManager.insertUser( "root", "azerty" ); root.addRole( adminRole ); root.addRole( userRole ); this.userManager.updateUser( root ); User toto = this.userManager.insertUser( "toto", "qwerty" ); toto.addRole( userRole ); this.userManager.updateUser( toto ); } public void run() throws SecurityManagerException, IOException { System.out.println( "Secure system: logon is required." ); while( true ) { System.out.print( "Login: " ); String strLogin = keyboard.readLine(); System.out.print( "Password: " ); String strPassword = keyboard.readLine(); try { User user = this.userManager.checkCredentials( strLogin, strPassword ); System.out.println( "Welcome " + user.getLogin() ); break; } catch( Exception exception ) { System.out.println( "Login failed, please retry" ); } } System.out.println( "Bye" ); } public void destroy() throws SecurityManagerException { this.userManager.deleteUser( this.userManager.checkCredentials( "root", "azerty" ) ); this.userManager.deleteUser( this.userManager.checkCredentials( "toto", "qwerty" ) ); this.roleManager.deleteRole( this.roleManager.selectRoleByName( "Administrator" ) ); this.roleManager.deleteRole( this.roleManager.selectRoleByName( "Simple User" ) ); } public static void main( String[] args ) throws Exception { SecurityManagerSample sample = new SecurityManagerSample(); try { sample.init(); sample.run(); } finally { sample.destroy(); } } }
Ce code est relativement simple. Un main démarre le programme en créant une instance sur la classe SecurityManagerSample : ce constructeur initialise le moteur de sécurité à partir du DataSource identifiant votre base de données. Ensuite, ce main invoque trois méthodes. La première, init, créer deux utilisateurs dans la base, ainsi que les rôles associés. La seconde, run, vous demande de saisir un login et un password afin de tenter une connexion au système. Enfin, la dernière méthode, destroy, détruit les éléments créer par la méthode init.
SecurityManagerSample
init
run
destroy
Pour compiler ce programme, il vous faudra utiliser le jar du framework Ellipse (javac -cp EllipseFramework.jar SecurityManagerSample.java). Pour exécuter cet exemple, il vous faudra aussi utiliser le framework Ellipse, mais il vous faudra aussi avoir le jar pour le driver JDBC permettant la connexion à la base de données MySql. Vous pouvez télécharger ce jar (dans sa dernière version) directement sur le site http://www.mysql.com. La ligne de commande permettant l'exécution du programme devrait ressembler à : java -cp EllipseFramework.jar:mysql-connector-java-5.1.7-bin.jar:. SecurityManagerSample.
javac -cp EllipseFramework.jar SecurityManagerSample.java
java -cp EllipseFramework.jar:mysql-connector-java-5.1.7-bin.jar:. SecurityManagerSample
Prochainement.
Dominique LIARD - © 2007..2010 SARL Infini Software - Tous droits réservés Les autres marques et les noms de produits cités dans ces documents sont la propriété de leurs détenteurs respectifs.