1. Présentation générale▲
Pour ceux qui ont déjà un peu manipulé tous ces outils avec un site ASP.Net, il ne devrait pas y avoir trop de problèmes pour comprendre. Les autres non plus, enfin j’espère !
ASP.Net nous fournit un ensemble d’outils destinés à gérer les utilisateurs :
- MembershipManager pour la gestion des utilisateurs ;
- RoleManager pour la gestion des permissions des utilisateurs ;
- ProfileManager pour la gestion des profils utilisateurs.
Ces outils offrent une étonnante combinaison de puissance et de souplesse. Outre les fonctionnalités offertes, vous pouvez passer d’un système de gestion à l’autre sans rien recompiler. Chaque fonctionnalité repose sur un socle unique nommé provider. Chaque type de provider (un pour chaque fonction) définit un contrat de base que doit remplir un provider, un peu à la manière d’une interface. Les providers de base sont d’ailleurs des classes abstraites.
Le .Net Framework fournit, par exemple, deux MemberShipProvider : SQLMemberShipProvider et ActiveDirectoryMembershipProvider. Ils dérivent tous deux de la classe MembershipProvider qui elle-même dérive de ProviderBase.
En se basant sur cet héritage, il est donc possible de créer un Provider qui s’appuye sur n’importe quelle source de données. De plus, ceci vous laisse la possibilité d’écrire vos propres providers. Consultez l’article de Didier Danse sur la création de son propre MembershipProvider pour vous faire une idée de la simplicité de la chose.
Pour ce qui est de la configuration, tout se fait par XML. Les Managers se chargent de charger et d’initaliser le(s) Provider(s) adéquat(s) en fonction de la configuration de l’application.
Étudions d’un peu plus près les différents types de Providers offerts par le .Net Framework. Afin de ne pas nous perdre, j’ai préféré tout réaliser avec les providers SQL Server du .Net Framework. Mis à part le fichier de configuration de l’application, cela n’influera en rien sur le reste de ce tutoriel.
Pour pouvoir utiliser SQL Server comme provider, il faut avant tout le préparer, créer les tables, vues et autres Stored Procédures nécessaires via l’assistant situé ici : \WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_regsql.exe. Il vous demandera de choisir une base de données. Afin de ne pas tout mélanger, j’en utilise une dédiée, mais vous pouvez très bien utiliser une base existante.
2. MembershipProvider▲
Le MembershipProvider est le provider essentiel. Si l’application n’a pas d’utilisateurs, à quoi bon avoir des rôles et des profils.
Comme vous l’aurez sans doute compris, le MembershipProvider s’occupe de la gestion des utilisateurs au sein d’une application. Il prend en charge toutes les fonctions d’identification, de gestion du mot de passe, les informations sur les dates de sessions… L’élément de base pour ce provider est le MembershipUser.
2-1. La classe MembershipUser▲
La classe MembershipUser est donc la brique de base du MembershipProvider. Elle représente un utilisateur, mais ne contient aucune information de plus que le strict nécessaire :
- UserName : le login / nom d’utilisateur
- ProviderName : le nom du provider qui gère cet utilisateur
- ProviderUserKey : clé unique qui identifie l’utilisateur au sein du provider
- CreationDate : date de création de l’utilisateur
- LastLoginDate : dernière date de login de l’utilisateur
- LastActivityDate : date de la dernière activité enregistrée de l’utilisateur
- LastPasswordChangedDate : date du dernier changement de mot de passe
- LastLockoutDate : dernière fois où l’utilisateur a été bloqué suite à des tentatives de login erronées
- IsOnline : si l’utilisateur est considéré comme en ligne
- IsLockedOut : si l’utilisateur s’est bloqué après plusieurs tentatives infructueuses de login
- IsApproved : si l’utilisateur est activé. Cela peut permettre aux administrateurs de valider un compte avec son utilisation.
- Email : l’adresse email de l’utilisateur, notamment pour pouvoir envoyer le mot de passe ou un nouveau mot de passe en cas de perte
- PasswordQuestion : question à laquelle doit répondre l’utilisateur lors de la perte de son mot de passe
- Comment : une description rapide de l’utilisateur
Cette classe dispose également de quelques méthodes intéressantes :
- ChangePassword : permet de changer le mot de passe de l’utilisateur
- ChangePasswordQuestionAndAnswer : permet de changer la question et la réponse pour l’éventuelle récupération de mot de passe
- ResetPassword : permet de générer aléatoirement un nouveau mot de passe en cas de perte et si le provider ne supporte pas la récupération du mot de passe
- GetPassword : si le provider le permet, récupération du mot de passe perdu
- UnlockUser : dans le cas où celui-ci serait bloqué suite à ses échecs de connexion, cette méthode permet de le réhabiliter.
Comme vous pouvez le voir, cette classe offre toutes les possibilités de base pour la gestion d’un utilisateur au sein d’une application.
2-2. Membership▲
Membership est le MembershipProviderManager. Toutes ses méthodes et propriétés sont statiques (shared en Visual Basic).
Pour les méthodes nous avons donc :
- CreateUser : permet de créer un utilisateur. Regardez les différentes surcharges de cette méthode pour voir toutes ses possibilités
- DeleteUser : supprime un utilisateur et toutes les données qui lui sont relatives ou non, au choix
- FindUsersByEmail : permet de retrouver des utilisateurs avec leur adresse email approchant. Une surcharge permet d’utiliser des fonctions de pagination
- FindUsersByName : permet de retrouver des utilisateurs par nom d’utilisateur approchant. Une surcharge permet d’utiliser des fonctions de pagination
- GeneratePassword : génère un mot de passe
- GetAllUsers : retourne tous les utilisateurs de l’application. Une surcharge permet d’utiliser des fonctions de pagination
- GetNumberOfUsersOnline : retourne le nombre d’utilisateurs actuellement en ligne
- GetUser : permet de récupérer un utilisateur, ou par son identifiant unique ou par son login. Elle permet également d’actualiser l’état en ligne de l’utilisateur
- GetUserNameByEmail : permet de récupérer un utilisateur grâce à son adresse email
- UpdateUser : met à jour l’utilisateur dans la source de données
- ValidateUser : valide le login et le mot de passe d’un utilisateur
Pour les propriétés :
- ApplicationName : retourne le nom de « l’application » utilisée par le provider
- EnablePasswordReset : indique si le Provider sélectionné est capable de réinitialiser le mot de passe de l’utilisateur
- EnablePasswordRetrieval : indique si le Provider sélectionné est capable d’afficher le mot de passe en clair
- HashAlgorithmType : indique l’algorithme de hashage utilisé pour le stockage du mot de passe
- MaxInvalidPasswordAttempts : nombre d’échecs d’authentification avant que l’utilisateur ne soit bloqué
- MinRequiredNonAlphanumericCharacters : indique le nombre minimum de caractères spéciaux (*$£@+- …) que doit contenir le mot de passe
- MinRequiredPasswordLength : indique la taille minimale des mots de passe
- PasswordAttemptWindow : indique le nombre de minutes durant lesquelles l’utilisateur peut effectuer des saisies erronées de mot de passe ou de réponse à la question avant d’être bloqué
- PasswordStrengthRegularExpression : permet de définir une Expression Régulière pour valider la robustesse mot de passe
- Provider : retourne le MembershipProvider utilisé par défaut dans l’application
- Providers : retourne l’ensemble des MembershipProviders définis pour l’application
- RequiresQuestionAndAnswer : indique si l’utilisateur doit répondre à une question pour récupérer son mot de passe
- UserIsOnlineTimeWindow : temps à partir de la dernière action durant lequel l’utilisateur est considéré « en ligne »
La majorité de ces propriétés sont basées sur la lecture des informations de configuration du Provider par défaut, propriété Provider. Toutes ces propriétés à l’exception de ApplicationName sont en lecture seule.
2-3. Déclatation d’un MembershipProvider dans une application▲
2-3-1. Initialisation▲
Il n’y a rien à faire. Comme toute utilisation du Provider se fait par l’intermédiaire du Manager, c’est celui-ci qui, lors de son premier appel, s’initialise en s’appuyant sur le fichier de configuration de l’application.
2-3-2. Configuration XML▲
Comme je vous l’ai dit précédemment, nous allons utiliser SQLServer 2005 comme source de données, et donc le SqlMembershipProvider pour l’exploiter.
<connectionStrings>
<add
name
=
"TestForm.My.MySettings.loginCS"
connectionString
=
"Data Source=RAIDERS;Initial Catalog=aspnetdb;Integrated Security=True"
providerName
=
"System.Data.SqlClient"
/>
</connectionStrings>
<system.web>
<membership
defaultProvider
=
"SqlProvider"
userIsOnlineTimeWindow
=
"20"
>
<providers>
<clear/>
<add
name
=
"SqlProvider"
type
=
"System.Web.Security.SqlMembershipProvider"
connectionStringName
=
"TestForm.My.MySettings.loginCS"
enablePasswordRetrieval
=
"false"
enablePasswordReset
=
"true"
requiresUniqueEmail
=
"false"
requiresQuestionAndAnswer
=
"true"
passwordFormat
=
"Hashed"
applicationName
=
"/"
/>
</providers>
</membership>
<system.web>
Comme vous le voyez, le Provider est configurable. Vous pouvez définir des paramètres comme l’obligation d’avoir une adresse email différente pour chaque utilisateur ou non…
Il y a un paramètre qui est très important dans cette configuration : applicationName. En effet, un provider peut servir pour différentes applications sur une même source. Imaginez par exemple un développeur sur sa machine, qui travaille pour plusieurs clients différents à la fois. Cette option lui permet de ne pas mélanger les utilisateurs entre les différentes applications.
Étant donné que c’est la configuration de mon application Winforms, je ne me fie pas à la configuration par défaut de la machine (à raison, elle était configurée sur une instance de SQLExpres non installée sur ma machine). J’ai donc déclaré ma propre instance de mon SQLMemberShipProvider. Et là, surprise : le provider par défaut était toujours celui de la machine. Un petit tour dans le code pour afficher quel provider était utilisé et là, surprise : c’était l’instance AspNetSqlMembershipProvider du SqlMembershipProvider qui était utilisée. C’est donc pour cela que la section membership comprend un attribut defaultProvider qui désigne celui que j’ai déclaré. Et pour la forme, un petit « clear » de la liste des providers afin que l’instance AspNetSqlMembershipProvider n’apparaisse même plus :p Ceci est valable pour les deux autres providers que nous verrons ensuite.
Il est également possible de configurer les providers par programmation et ainsi de se passer du fichier de configuration. Mais pourquoi faire compliqué quand on peut faire simple. Pour initialiser un provider, il faut passer par sa méthode Initialise. Je vous laisse le soin de découvrir son utilisation, des plus sympathiques. Vous perdez par contre l’utilisation de Membership, puisqu’après initialisation, Providers est passée en lecture seule.
2-4. Quelques exemples d’utilisation de gestion d’utilisateur▲
2-4-1. Création d’utilisateur▲
Public
Shared
Function
CreateUser (
_
username As
String
, _
password As
String
, _
email As
String
, _
passwordQuestion As
String
, _
passwordAnswer As
String
, _
isApproved As
Boolean
, _
<
OutAttribute>
ByRef
status As
MembershipCreateStatus _
) As
MembershipUser
Dim
status As
MembershipCreateStatus =
MembershipCreateStatus.UserRejected
Dim
user As
MembershipUser =
Nothing
user =
Membership.CreateUser
(
"login"
, "motdepasse"
, "email@domaine.com"
, _
"site numéro 1 des développeurs francophones"
, "developpez.com"
, False
, status)
If
status =
MembershipCreateStatus.Success
Then
Page.Title
(
"Création OK"
)
Else
Select
Case
status
Case
MembershipCreateStatus.DuplicateUserName
' ...
Case
MembershipCreateStatus.DuplicateEmail
' ...
Case
Else
' ...
End
Select
End
If
public
static
MembershipUser CreateUser (
string
username,
string
password,
string
email,
string
passwordQuestion,
string
passwordAnswer,
bool
isApproved,
out
MembershipCreateStatus status
)
MembershipCreateStatus status =
MembershipCreateStatus.
UserRejected;
MembershipUser user =
null
;
user =
Membership.
CreateUser
(
"login"
,
"motdepasse"
,
"email@domaine.com"
,
"site numéro 1 des développeurs francophones"
,
"developpez.com"
,
false
,
status);
if
(
status =
MembershipCreateStatus.
Success)
{
Page.
Title
(
"Création OK"
);
}
else
{
switch
(
status){
case
MembershipCreateStatus.
DuplicateUserName:
// ...
case
MembershipCreateStatus.
DuplicateEmail:
// ...
default
:
// ...
}
}
isApproved est un booléen qui indique si l’utilisateur est opérationnel après sa création ou s’il doit y avoir validation par un autre utilisateur pour que celui-ci soit activé.
status, de type MembershipCreateStatus, vous donne des indications sur la création du compte utilisateur. « Success » si tout s’est bien déroulé. Dans le cas contraire, la valeur de l’énumération est suffisamment explicite pour connaître le problème rencontré lors de la création.
Pour le reste des paramètres, leur nom est suffisamment explicite pour ne pas rentrer dans les détails.
Quand vous insérez un nouvel utilisateur, vous aurez sans doute la surprise de voir que la date de dernier login et de dernière activité correspondent à la date et l’heure du moment où vous avez inséré l’utilisateur. Ce n’est pas bien méchant, mais je ne trouve pas ça logique du tout.
3. RoleProvider▲
On retrouve souvent, mais pas toujours, une gestion des permissions accordées aux utilisateurs en fonction de leur appartenance à un groupe. C’est à ça que va servir le RoleManager.
Contrairement au MembershipProvider, le RoleProvider ne possède pas de type dédié à la gestion des rôles. En effet, dans la plupart des cas, l’unique information nécessaire, c’est de savoir si un utilisateur appartient ou non à un groupe. Nous allons donc travailler principalement avec des booléens et des tableaux de chaines.
3-1. L’objet Roles▲
L’objet Roles est l’équivalent de Membership. Il sera le pont entre votre application et le provider.
L’objet Roles possède de nombreuses propriétés. La plupart de celles-ci sont destinées à être utilisées avec un navigateur. Toutes celles dont le nom commence par Cookie sont de ce type et ne serviront pas ici. ApplicationName est la même que pour Membership, Provider et Providers ont les mêmes fonctions que pour Membership. Une propriété très importante est Enabled. En effet, il est possible d’utiliser les utilisateurs, mais pas forcément les Roles. Pour cette raison, le RoleManager peut être désactivé.
Le plus intéressant de cet objet se situe au niveau de ses nombreuses méthodes :
- AddUsersToRole : permet d’ajouter une liste d’utilisateurs dans un rôle
- AddUsersToRoles : permet d’ajouter une liste d’utilisateurs à une liste de rôles
- AddUserToRole : permet d’ajouter un utilisateur à un rôle
- AddUserToRoles : permet d’ajouter un utilisateur à une liste de rôles
- CreateRole : crée un nouveau rôle
- DeleteRole : permet de supprimer un rôle. Attention si vous utilisez la première surcharge de cette méthode qui ne prend que le nom du rôle en argument. Si le rôle en question est encore attribué à des utilisateurs, vous aurez une jolie exception. Utilisez plutôt la seconde surcharge avec le second argument pour éviter ce désagrément.
- FindUsersInRole : retourne une liste des utilisateurs dans le rôle dont le nom ressemble à la chaine de recherche
- GetAllRoles : retourne une liste de tous les rôles
- GetRolesForUser : retourne la liste des rôles pour l’utilisateur spécifié. La deuxième surcharge ne fonctionne qu’en mode Web.
- GetUsersInRole : retourne la liste des utilisateurs associés au rôle spécifié
- IsUserInRole : vérifie si l’utilisateur appartient bien au rôle spécifié
- RemoveUserFromRole : Eenlève l’utilisateur spécifié du rôle spécifié
- RemoveUserFromRoles : retire l’utilisateur spécifié des rôles sélectionnés
- RemoveUsersFromRole : retire une liste d’utilisateurs d’un rôle
- RemoveUsersFromRoles : retire une liste d’utilisateurs d’une liste de rôles
- RoleExists : vérifie si le rôle en question existe
Comme vous le voyez, tout est prévu pour gérer les rôles. De plus, pas besoin d’avoir une classe pour représenter un rôle. Pour l’ajout, la suppression ou l’obtention d’information, un tableau de chaines est largement suffisant.
3-2. Déclaration d’un RoleProvider▲
Comme pour le MembershipProvider, l’initialisation est transparente. Pas besoin de revenir dessus. Les mêmes remarques sont d’ailleurs applicables.
<roleManager
enabled
=
"true"
defaultProvider
=
"SqlRoleProvider"
>
<providers>
<clear/>
<add
name
=
"SqlRoleProvider"
connectionStringName
=
"SampleApp.My.MySettings.loginCS"
applicationName
=
"/"
type
=
"System.Web.Security.SqlRoleProvider, System.Web, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a"
/>
</providers>
</roleManager>
Du fait qu’il sera utilisé en WinForms, la majorité des informations de configuration, concernant les cookies, sont ignorées. On retrouve donc le Nom, la chaine de connexion utilisée puisque c’est un provider SQL Server et l’application de laquelle dépend le Provider. La seule petite nouveauté par rapport au MembershipManager c’est l’apparition de l’attribut « enabled ». C’est en plaçant celui-ci à la valeur « True » que vous activerez la gestion des rôles dans l’application, ou la désactiverez en la plaçant à « False ».
3-3. Quelques exemples d’utilisation du RoleManager▲
3-3-1. Création de rôle▲
If
Not
Roles.RoleExists
(
"roles"
) Then
Roles.CreateRole
(
"roles"
)
End
If
if
(!
Roles.
RoleExists
(
"roles"
))
{
Roles.
CreateRole
(
"roles"
);
}
Si vous ne vérifiez pas l’existence d’un rôle avant de le créer et que celui-ci existe déjà, vous obtiendrez une System.Configuration.Provider.ProviderException. C’est cette exception que vous obtiendrez chaque fois que vous tenterez d’exécuter une action de ce type.
3-3-2. Suppression de rôle▲
Dim
result as
Boolean
=
Roles.DeleteRole
(
"roleexample"
, False
)
bool
result =
Roles.
DeleteRole
(
"roleexample"
,
false
);
Vous vous exposez à une System.ArgumentException si le nom du rôle est une chaine vide ou contient une virgule. Si le rôle que vous tentez de supprimer contient encore des utilisateurs et que vous utilisez la surcharge de cette méthode qui prend seulement le nom de rôle en paramètre ou cette surcharge avec le second attribut à False, c’est une System.Configuration.Provider.ProviderException qui sera lancée.
3-3-3. Récupération des utilisateurs d’un rôle▲
If
Roles.RoleExists
(
"roles"
) Then
Dim
users
(
) As
String
=
Roles.GetUsersInRole
(
"roles"
)
For
i As
Integer
=
0
To
users.Length
-
1
Trace.WriteLine
(
users
(
i))
Next
End
If
if
(!
Roles.
RoleExists
(
"roles"
))
{
string
[]
users =
Roles.
GetUsersInRole
(
"roles"
);
for
(
int
i =
0
;
i++;
i <
users.
Length -
1
)
{
Trace.
WriteLine
(
users
(
i));
}
}
Comme vous le voyez, même avec des contrôles de vérification, l’utilisation du RoleManager est des plus simples.
Passons au dernier type de Provider.
4. ProfileProvider▲
Le ProfileManager est sans doute la partie la plus complexe à traiter en WinForms.
Le ProfileManager vous permet de définir un profil, c’est-à-dire des propriétés personnalisées en fonction de chaque utilisateur. Ce profil peut très bien ressembler au profil que vous possédez sur le forum : signature, avatar, date de naissance, adresse MSN… Du fait que d’une application à une autre le contenu du profil, ses champs, peuvent varier, les concepteurs de ce provider ont décidé de laisser leur définition dans la configuration du ProfileManager.
<properties>
<clear/>
<add
name
=
"FullName"
serializeAs
=
"String"
/>
<add
name
=
"WebSite"
serializeAs
=
"String"
/>
<add
name
=
"Birthdate"
serializeAs
=
"Xml"
type
=
"System.DateTime"
/>
<add
name
=
"Voiture"
serializeAs
=
"Xml"
type
=
"SampleProfile.SampleClass, SampleProfile"
/>
<add
name
=
"PreferedColor"
serializeAs
=
"String"
type
=
"System.Drawing.Color, System.Drawing,
Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
/>
<group
name
=
"Address"
>
<add
name
=
"FirstLine"
serializeAs
=
"String"
/>
<add
name
=
"SecondLine"
serializeAs
=
"String"
/>
<add
name
=
"PostalCode"
/>
<add
name
=
"City"
serializeAs
=
"String"
type
=
"System.String"
/>
<add
name
=
"Country"
serializeAs
=
"String"
/>
</group>
</properties>
Comme vous le voyez, un profil est un ensemble de propriétés, définies par un nom. D’autres attributs comme le type sont définissables, ou encore la forme sous laquelle la propriété est sauvegardée dans la banque de données, ici, SQL Server. Vous pouvez également faire appel à des librairies autres que les librairies standards du Framework, comme le champ Voiture. La classe SampleProfile n’est pas des plus compliquées, elle contient un constructeur par défaut et 2 propriétés de type String.
Laissons ça de côté pour le moment, regardons un peu comment fonctionne le ProfileManager avant de continuer.
4-1. ProfileManager▲
Le ProfileManager possède peu de propriétés. Celles que nous allons utiliser sont les mêmes que pour le MembershipManager et le RoleManager. L’unique nouvelle propriété ne s’applique pas dans notre cas.
Au niveau des méthodes, c’est déjà un peu plus intéressant :
- DeleteInactiveProfiles : efface les profils inactifs avant la date spécifiée
- DeleteProfile : efface le profil de l’utilisateur spécifié
- DeleteProfiles : efface les profils des utilisateurs spécifiés
- FindInactiveProfilesByUserName : retourne les profils inactifs par nom d’utilisateurs similaires à la recherche
- FindProfilesByUserName : retourne les profils par nom d’utilisateurs similaires à la recherche
- GetAllInactiveProfiles : retourne les profils inactifs avant la date spécifiée
- GetAllProfiles : retourne tous les profils. Cette méthode offre des fonctions de pagination
- GetNumberOfInactiveProfiles : retourne le nombre de profils inactifs avant la date spécifiée
- GetNumberOfProfiles : retourne le nombre de profils actuellement stockés
Dans le cadre de ce tutoriel, je n’utiliserai pas le ProfileManager. Nous passerons par le ProfileBase pour toutes les opérations sur les profils.
4-2. ProfileBase▲
Comme je vous le disais plus haut, la partie complexe du ProfileManager, c’est le profil lui-même.
Le désavantage d’utiliser les profils dans une application WinForms, c’est qu’ASP.Net ne nous génère pas la classe ProfileCommon, qui hérite de ProfileBase. La classe ProfileCommon a l’avantage de rendre le profil fortement typé. Chaque champ du profil est mappé en propriété fortement typée, c’est-à-dire que le type de la propriété correspond au type du champ.
5. Conclusion▲
Comme vous pouvez le constater, vous disposez d’un système puissant de gestion des utilisateurs et des rôles dans votre application ASP.Net.
5-1. Remerciements▲
Merci à tous ceux qui ont pris le temps de relire et critiquer ce tuto avant sa publication : Ditch, mehdi_tn