Exporter (0) Imprimer
Développer tout

Sécurité d'entreprise à authentification unique pour les applications Web

Paul D. Sheriff
PDSA, Inc.

S'applique à :
   Microsoft® ASP.NET

Résumé : Découvrez une technique permettant d'activer l'authentification unique pour plusieurs applications Web. Cet article fournit également un exemple de code comme point de départ pour la création d'un système de sécurité d'entreprise à authentification unique.

Téléchargez les exemples de code relatifs à cet article . (Reportez-vous à Installation des exemples avant de les télécharger.)

Sommaire

Introduction
Vue d'ensemble d'une solution à authentification unique
Classes et pages
Tableaux
Application Web AppLauncher
Classe Apps
Récupération de jetons dans les applications Web
Amélioration de votre système à authentification unique
Installation des exemples
Conclusion
Bibliographie

Introduction

À moins que votre organisation ne soit vraiment différente, elle a certainement recours à plusieurs applications Web. La plupart de ces applications doivent être sécurisées pour empêcher tout utilisateur d'y entrer à son gré. Par exemple, un système de prise en charge des décisions générales d'entreprise ne doit être accessible qu'à un certain nombre d'utilisateurs, tout comme un système d'informations client. Il est également possible que des utilisateurs externes doivent accéder à ces systèmes alors qu'ils ne font pas partie de votre domaine Microsoft® Windows®. Vous pouvez contrôler cet accès grâce à un mécanisme de sécurité qui oblige chaque utilisateur à ouvrir une session sur votre système. Cependant, si votre organisation utilise plusieurs systèmes, l'utilisateur passe sont temps à ouvrir des sessions, ce qui peut parfois poser problème. Même si vous n'avez que cinq applications Web, l'utilisateur autorisé à y accéder se lassera vite d'ouvrir et de fermer des sessions. Il y a certainement une meilleure solution !

Cet article vous présente une méthode d'utilisation d'une solution à authentification unique dans toute votre entreprise. Cette méthode permet aux utilisateurs internes d'utiliser l'authentification Windows pour accéder aux applications, tandis que les utilisateurs externes sont obligés d'ouvrir une session (cf. figure 1).

Sujets abordés dans cet article

Authentification unique pour applications Web

  • Génération d'un jeton d'authentification unique
  • Mappage des utilisateurs sur les applications
  • Authentification basée sur les formulaires
  • Authentification Windows intégrée

Vue d'ensemble d'une solution à authentification unique

La figure 1 illustre une série d'étapes que les utilisateurs internes et externes devront suivre pour ouvrir une session sur un site Web hébergé sur l'intranet d'une entreprise. Vu de très haut, ce système s'articule autour de quatre composants : un site Web avec authentification Windows, Active Directory, un serveur de base de données et un ou plusieurs sites Web avec authentification basée sur les formulaires.

singlesignon_01.gif

Figure 1. Exemple de solution à authentification unique prenant en charge les utilisateurs externes et internes

La solution Intranet

Examinez la figure 1 en commençant par le coin supérieur gauche. Un utilisateur interne ouvre un navigateur et accède à un site Web particulier (Étape 1). Ce site Web vérifie (Étape 2) les informations d'authentification Windows de l'utilisateur (via Active Directory). Si l'utilisateur est un utilisateur Windows valide, il est autorisé à accéder au site. Après avoir authentifié l'utilisateur, la solution récupère l'identité de l'utilisateur et appelle la table de la base de données (Étape 3) qui contient une liste des applications que celui-ci peut exécuter. Ces applications sont affichées dans un contrôle DataGrid pour que l'utilisateur puisse les sélectionner.

L'utilisateur clique sur l'application à exécuter et un jeton à usage unique est généré (Étape 4). Ce jeton est stocké aux côtés de l'identité de l'utilisateur dans une autre table de la base de données. Le jeton est ensuite transféré à une page spéciale (par le biais de la ligne de requête) de l'application Web que l'utilisateur veut exécuter. Cette page spéciale lit le jeton dans la ligne de requête et vérifie sa présence dans la table de la base de données (Étape 5). Si le jeton existe, l'ID de connexion est récupéré dans la base de données et l'enregistrement dans lequel le jeton était stocké est effacé. Ceci empêche toute personne de réutiliser le jeton, tandis que l'ID de connexion est renvoyé à l'application Web.

L'application Web connaît désormais l'identité de l'utilisateur. L'étape suivante consiste donc à générer un ticket d'authentification basée sur les formulaires ASP.NET étant donné qu'il s'agit de la méthode utilisée par toutes les applications Web de votre intranet. C'est ce ticket qui permettra à l'utilisateur d'accéder aux différentes pages sécurisées du site en question.

La solution Extranet

Les utilisateurs externes (qui ne font pas partie de votre domaine) qui souhaitent utiliser votre application Web sont orientés vers une page de démarrage différente de celle des utilisateurs internes (cf. figure 2). L'application Web vers laquelle les utilisateurs internes et externes sont dirigés est sécurisée au moyen de l'authentification basée sur des formulaires. Lorsqu'un utilisateur externe tente d'accéder à une page de cette application Web, il est automatiquement redirigé vers une page d'authentification. Cette page est différente de celle qui sert à authentifier les utilisateurs internes. L'utilisateur doit d'abord entrer ses informations d'authentification, après quoi la solution appelle la même base de données que pour les utilisateurs internes afin de déterminer s'il peut accéder à l'application demandée. Si c'est le cas, un cookie normal d'authentification basée sur des formulaires est généré pour la session de l'utilisateur.

Classes et pages

La prise en charge du système de sécurité d'entreprise requiert la création de quelques classes et de quelques pages Web. La figure 2 illustre celles que vous devrez écrire pour ce système. Chaque classe présente dans la figure est détaillée plus loin dans l'article. La figure comprend également une description de la fonction principale de chaque classe et de chaque page Web dans votre système.

singlesignon_02.gif

Figure 2. Quelques classes et pages nécessaires au développement d'une solution à authentification unique

Classe Apps

Cette classe est celle qui permet de récupérer la liste d'applications pour un utilisateur spécifié. Cette classe sert également à générer un nouveau jeton, à extraire l'identité d'un utilisateur contenue dans un jeton spécifié, et à supprimer un jeton.

Tableau 1. Méthodes de la classe Apps

Nom de la méthodeDescription
GetAppsByLoginIDCette méthode recherche les applications associées à un utilisateur à partir d'un ID de connexion dans son domaine et renvoie un DataSet contenant ces applications.
CreateLoginTokenCette méthode crée et renvoie un nouveau jeton d'authentification.
GenerateTokenCette méthode génère le nouveau jeton. Dans cette version, le jeton est un simple GUID.
VerifyLoginTokenCette méthode vérifie la présence d'un jeton donné dans la base de données. Elle crée une instance de la classe AppToken, complète les informations nécessaires et renvoie le nouvel objet.
DeleteTokenCette méthode supprime le jeton dans la table.

Classe AppToken

Cette classe contient les quatre propriétés dont la méthode VerifyLoginToken a absolument besoin pour renvoyer les informations du jeton dans la classe Apps. Le tableau 2 décrit ces quatre propriétés.

Tableau 2. Propriétés de la classe AppToken

PropriétéDescription
LoginIDValeur de chaîne représentant l'ID de connexion de l'utilisateur.
AppNameValeur de chaîne représentant le nom de l'application à laquelle cet enregistrement AppToken correspond.
LoginKeyValeur entière qui sert de clé primaire pour l'utilisateur dans la table esUsers.
AppKeyValeur entière qui sert de clé primaire pour l'application dans la table esApps.

Classe AppUserRoles

Cette classe sert à extraire des informations sur l'utilisateur qui tente d'ouvrir une session sur une application. Cette classe comprend trois méthodes. La première vérifie la validité d'une ouverture de session à partir d'un ID de connexion et d'une clé d'application donnés. La deuxième renvoie le jeu de rôles d'un utilisateur donné. La dernière renvoie la clé primaire de la table esUser pour un ID de connexion et une clé d'application donnés.

Page Default.aspx (dans l'application AppLauncher)

Cette classe de page Web identifie l'utilisateur Windows authentifié par IIS et renvoie la liste d'applications auxquelles il est autorisé à accéder. Cette liste s'affiche sous la forme d'un contrôle DataGrid (figure 3), ce qui permet à l'utilisateur de cliquer sur l'application de son choix. Dès que l'utilisateur clique sur une application, cette page génère un nouveau jeton, stocke le jeton et l'ID de l'utilisateur dans la table esAppToken, puis appelle l'application en transmettant le jeton à la page AppLogin.aspx de celle-ci.

singlesignon_03.gif

Figure 3. Le lanceur d'applications affiche la liste des applications que l'utilisateur ayant ouvert la session est autorisé à exécuter

Page AppLogin.aspx (dans chaque site Web)

Cette classe de page Web est uniquement appelée à partir du lanceur d'applications. Toute autre application qui essaie de l'appeler voit ses utilisateurs redirigés vers la page Default.aspx du site Web. Étant donné que toutes les applications Web utilisent l'authentification basée sur des formulaires, cette redirection oblige ASP.NET à rediriger l'utilisateur vers la page Login.aspx du site en question.

Si, au contraire, la page est appelée par un jeton provenant du site du lanceur d'applications, elle utilise des méthodes de la classe Apps afin de vérifier la validité du jeton. Si le jeton est valide, la classe Apps renvoie un objet AppToken que la page peut ensuite utiliser pour créer un utilisateur selon la méthode d'authentification basée sur des formulaires.

Page Login.aspx (dans chaque site Web)

Cette page est une page Web d'ouverture de session normale qui demande les informations d'authentification de l'utilisateur, vérifie leur validité dans une base de données, crée un ticket d'authentification et redirige l'utilisateur vers la page qu'il a demandée à son arrivée sur le site.

Page Default.aspx (dans chaque site Web)

Cette page est la page de départ principale de chaque site Web. Cette page, tout comme les autres pages du site, n'est accessible que si l'utilisateur a été authentifié sur la page AppLogin ou sur la page Login.

Tables

La prise en charge d'un système de sécurité d'entreprise à authentification unique nécessite la création de quelques tables de base de données. Les tables décrites dans cet article ne contiennent pas beaucoup de champs, mais suffisamment pour faire passer l'idée. La figure 4 montre les relations entre les tables de la base de données que vous devrez créer. En dessous de la figure 4, vous trouverez une liste de toutes les tables, avec une description du rôle de chaque table dans la solution présentée.

singlesignon_04.gif

Figure 4. Quelques tables toutes simples suffisent à l'implémentation d'un système d'authentification unique avec rôles

esApps

Cette table contient une liste de toutes les applications Web dans votre entreprise. La table comprend le nom et l'URL de chaque application, ainsi qu'une description détaillée. L'URL est l'URL complète et elle doit toujours avoir la page AppLogin.aspx comme point final. La page default.aspx du lanceur d'applications se chargera d'ajouter « Token=<GeneratedToken> » à l'URL avant de rediriger l'utilisateur vers l'application Web.

Cliquez ici pour agrandir l'image

Figure 5. Exemples de données de la table esApps

esUsers

Cette table répertorie les utilisateurs autorisés à utiliser vos applications. Vous devrez dupliquer votre ID de connexion de domaine d'utilisateur pour tous les utilisateurs internes. Il peut également être intéressant d'ajouter un champ de mot de passe pour les utilisateurs externes.

singlesignon_06.gif

Figure 6. Exemples de données de la table esUsers

esAppsUsers

Cette table met en relation les utilisateurs de la table esUsers et les applications qu'ils sont autorisés à exécuter dans la table esApps. Cette table ne peut contenir comme données que des clés étrangères aux tables esUsers et esApps.

esAppRoles

Cette table contient un jeu de rôles pour chaque application. Par exemple, une application peut contenir les rôles « Admin » et  « User », tandis qu'une autre contiendra les rôles « User » et « Supervisor ».

Cliquez ici pour agrandir l'image

Figure 7. Exemples de données de la table esAppRoles

esAppUsersRoles

Cette table indique le rôle de chaque utilisateur dans chaque application. L'utilisateur « Joe » peut être « Supervisor » dans l'application « HR », et avoir les rôles « Admin » et « User » dans l'application « Payroll ».

esAppToken

La table esAppToken contient les jetons que le portail d'authentification génère, puis transmet aux applications Web individuelles. Normalement, ces enregistrements durent deux secondes tout au plus. En effet, l'application Web appelée les efface dès qu'elle a collecté les informations dans la table esAppToken. Ceci empêche toute réutilisation d'un même jeton.

Cliquez ici pour agrandir l'image

Figure 8. Exemples de données de la table esAppToken

Application Web AppLauncher

Le lanceur d'applications (figure 9) se compose d'un site authentifié par Windows et d'un projet de bibliothèque de classes. Ce site authentifié par Windows lit l'ID de domaine de l'utilisateur directement à partir du thread et l'utilise pour rechercher l'utilisateur dans la table esUsers. Examinons brièvement la page Web qui affiche les applications qu'un utilisateur peut exécuter.

singlesignon_09.gif

Figure 9. La solution AppLauncherxx contient le projet AppLauncherxx et une référence au projet AppLauncherDataxx

Page default.aspx dans le projet AppLauncherxx

Le site Web Lanceur d'applications ne doit contenir qu'une seule page : default.aspx. Cette page Web lit l'ID de connexion Windows dans l'objet User qu'elle contient, puis charge la liste d'applications définie dans la base de données pour cet utilisateur. Le code ci-dessous correspond à la procédure d'événement Page_Load appelée lors du chargement de la page default.aspx.

// C#
private void Page_Load(object sender, System.EventArgs e)
{
 // Display the User Name
 // Without the Domain prefix
 lblLogin.Text = "Applications Available for: " + 
 Apps.LoginIDNoDomain(User.Identity.Name);

 AppLoad();
}

' VB.NET
Private Sub Page_Load(ByVal sender As System.Object, _
 ByVal e As System.EventArgs) Handles MyBase.Load
 ' Display the User Name
 ' Without the Domain prefix
 lblLogin.Text = "Applications Available for: " & _
 Apps.LoginIDNoDomain(User.Identity.Name)

 AppLoad()
End Sub

La méthode statique LoginIDNoDomain de la classe Apps est appelée pour rogner le préfixe du domaine. Si un utilisateur dont l'ID de connexion est « Ken » est authentifié sur un domaine appelé « PDSA », la propriété User.Identity.Name renvoie « PDSA\Ken », contrairement à cette méthode, qui ne renvoie que la chaîne « Ken ».

Chargement des applications que l'utilisateur authentifié est autorisé à utiliser

La méthode AppLoad utilise une instance de la classe Apps pour récupérer un DataSet des applications que l'utilisateur est autorisé à exécuter. La méthode GetAppsByLoginID de la classe Apps sera détaillée plus loin dans cet article.

// C#
private void AppLoad()
{
 Apps app = new Apps();

 try
 {
 // Load applications for this user
 grdApps.DataSource =
 app.GetAppsByLoginID(User.Identity.Name);
 grdApps.DataBind();

 }
 catch (Exception ex)
 {
 lblMessage.Text = ex.Message;
 }
}

' VB.NET
Private Sub AppLoad()
 Dim app As New Apps

 Try
 ' Load applications for this user
 grdApps.DataSource = app.GetAppsByLoginID(User.Identity.Name)
 grdApps.DataBind()

 Catch ex As Exception
 lblMessage.Text = ex.Message
 End Try

End Sub

Le contrôle LinkButton

Une fois que la liste d'applications accessibles à l'utilisateur spécifié apparaît dans le contrôle DataGrid de la page default.aspx (cf. figure 3), l'utilisateur peut sélectionner celle qu'il souhaite utiliser. Le contrôle utilisé dans DataGrid pour afficher un lien hypertexte sur lequel l'utilisateur peut cliquer s'appelle LinkButton. Le contrôle LinkButton est définit comme suit sur la page Web.

<asp:LinkButton id=lnkApp runat="server" 
AppID='<%# DataBinder.Eval(Container.DataItem, "iAppID") %>' 
UserID='<%# DataBinder.Eval(Container.DataItem, "iUserID") %>' 
CommandArgument='<%# DataBinder.Eval(Container.DataItem, "sURL") %>' 
Text='<%# DataBinder.Eval(Container.DataItem, "sAppName") %>'>
</asp:LinkButton>

Comme vous pouvez le voir, le code ci-dessus comprend deux attributs ajoutés qui correspondent à la clé primaire de la table esApps (iAppID) et à la clé primaire de la table esUsers (iUserID). Ces attributs, en plus de l'URL indiquée dans le CommandArgument, fournissent suffisamment d'informations de profil utilisateur à la base de données. Ces attributs supplémentaires seront récupérés par la procédure d'événement ItemCommand expliquée ci-après.

Conseil vous pouvez ajouter autant d'attributs à un contrôle de serveur que vous le souhaitez. ASP.NET ignorera ces attributs, mais vous pourrez récupérer leurs valeurs en utilisant la propriété Attributes sur le contrôle de serveur.

Événement ItemCommand

Lorsque l'utilisateur clique sur le contrôle LinkButton dans le contrôle DataGrid, la procédure d'événement ItemCommand est appelée. Cette méthode doit créer une nouvelle instance de la classe Apps, récupérer le contrôle LinkButton afin d'accéder à ses attributs et appeler la méthode CreateLoginToken dans la classe Apps pour stocker ces données dans la table esAppToken de la base de données. Pour terminer, le jeton récupéré par la méthode et l'URL de la propriété CommandArgument du contrôle LinkButton sont juxtaposés et Response.Redirect est invoqué pour appeler l'application Web qui transmet le jeton.

// C#
private void grdApps_ItemCommand(object source, 
 System.Web.UI.WebControls.DataGridCommandEventArgs e)
{
 Apps app = new Apps();
 bool redirect = false;
 string token = String.Empty;
 LinkButton lb;

 try
 {
 lb = (LinkButton) e.Item.Cells[0].Controls[1];
 
 // Create a Token for this user/app
 token = app.CreateLoginToken(
 lb.Text, 
 User.Identity.Name, 
 Convert.ToInt32(lb.Attributes["UserID"]), 
 Convert.ToInt32(lb.Attributes["AppID"]));

 redirect = true;
 }
 catch (Exception ex)
 {
 redirect = false;
 lblMessage.Text = ex.Message;
 }

 if (redirect)
 {
 // Redirect to Web application 
 // passing in the generated token

 Response.Redirect(e.CommandArgument.ToString() +
 "?Token=" + token, false);
 }
}

' VB.NET
Private Sub grdApps_ItemCommand(ByVal source As Object, _
 ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) _
 Handles grdApps.ItemCommand
 Dim app As New Apps
 Dim boolRedirect As Boolean
 Dim token As String
 Dim lb As LinkButton

 Try
 lb = DirectCast(e.Item.Cells(0).Controls(1), LinkButton)

 ' Create a Token for this user/app
 token = app.CreateLoginToken(lb.Text, _
 User.Identity.Name, _
 Convert.ToInt32(lb.Attributes("UserID")), _
 Convert.ToInt32(lb.Attributes("AppID")))

 boolRedirect = True

 Catch ex As Exception
 boolRedirect = False
 lblMessage.Text = ex.Message

 End Try

 If boolRedirect Then
 ' Redirect to Web application 
 ' passing in the generated token
 Response.Redirect(e.CommandArgument.ToString() & _
 "?Token=" & token, False)
 End If
End Sub

Comme vous pouvez le constater, vous récupérez le contrôle LinkButton à partir de l'argument e.Item. Cet argument est une référence à la ligne sur laquelle vous avez cliqué dans le contrôle DataGrid. Vous pouvez récupérer l'instance précise du contrôle LinkButton sur laquelle vous avez cliqué en recherchant la colonne qui le contient. Dans ce cas, il s'agit de Cells(0). Dans cette cellule, vous pouvez obtenir le contrôle en recherchant Controls(1). La raison pour laquelle le contrôle est à l'emplacement un (1) est que le modèle ItemTemplate utilisé dans le contrôle DataGrid, pour vous permettre de placer un contrôle LinkButton dans une cellule, est considéré comme l'élément zéro (0).

Une fois que vous avez récupéré le contrôle LinkButton, vous pouvez utiliser la propriété Attributes pour récupérer les identifiants UserID et AppID stockés lors de la configuration du contrôle. La propriété Attributes est un ensemble d'attributs supplémentaires ajoutés au contrôle du serveur, c'est-à-dire qui ne font pas partie de sa définition originale.

Modification du fichier Web.Config

Avant de pouvoir authentifier les utilisateurs dans une application Web, vous devez attribuer à l'élément <authentication> du fichier Web.Config la valeur « Windows ». De plus, vous devez exclure les utilisateurs anonymes dans l'élément <authorization> du fichier Web.Config.

<authorization>
 <deny users="?" />
</authorization> 

Ces deux actions obligent le serveur à récupérer les informations d'authentification Windows de l'utilisateur à partir du navigateur. Naturellement, cela ne fonctionnera que si vous utilisez Internet Explorer dans un domaine dans lequel l'utilisateur a ouvert une session.

À ce stade, il ne vous reste plus qu'à créer la chaîne de connexion permettant d'accéder aux tables de la base de données. Dans les exemples de cet article, j'ai simplement utilisé la section <appSettings> du fichier Web.config pour stocker la chaîne de connexion.

<appSettings>
<add key="eSecurityConnectString" 
 value="server=(local);Database=eSecurity;uid=myUserID;pwd=myPassword" />
</appSettings>

Pour cet article, j'ai créé une base de données SQL Server™ appelée eSecurity, ainsi que toutes les tables décrites jusqu'ici. Le code fourni dans cet article comprend un script SQL que vous pouvez exécuter pour créer les différentes tables mentionnées dans une base de données SQL Server. Si vous utilisez un système de base de données différent, vous devrez modifier ces scripts en conséquence.

Classe Apps

Examinons maintenant la classe Apps, véritable plaque tournante de notre système de sécurité d'entreprise. Les trois classes de l'assembly AppLauncherData s'occupent de charger les applications et les rôles pour les utilisateurs et de gérer les jetons de sécurité. Il est intéressant de maintenir l'interaction avec la base de données et la manipulation des jetons en dehors de l'interface utilisateur. En effet, cela vous permet de modifier la manière dont les jetons sont créés ainsi que votre interaction avec la base de données sans modifier l'interface utilisateur.

La classe Apps est responsable de la gestion des jetons et du chargement des applications pour les utilisateurs. Examinons la définition de cette classe importante.

// C#
public class Apps
{
 string mConnectString;

 public Apps()
 {
 mConnectString = ConfigurationSettings.
 AppSettings["eSecurityConnectString"];
 }

...
}

' VB.NET
Public Class Apps
 Private mConnectString As String

 Public Sub New()
 mConnectString = ConfigurationSettings. _
 AppSettings("eSecurityConnectString")
 End Sub

...
End Class

Comme vous pouvez le voir, la première chose que la classe fait est de charger une variable de membre avec une chaîne de connexion qu'elle extrait du fichier Web.config.

Méthode GetAppsByLoginID

Le chargement des applications correspondant à un utilisateur spécifié se fait grâce à la transmission de l'ID de connexion de l'utilisateur à la méthode GetAppsByLoginID. Cette méthode est responsable de l'exécution de l'instruction Join permettant d'extraire toutes les informations nécessaires. L'instruction SQL Join doit extraire des informations des tables esApps et esAppsUsers. Cependant, vous devez aussi inclure la table esUsers puisque vous voulez récupérer l'application pour un utilisateur en particulier et que la seule information dont vous disposiez est son ID de connexion. Par conséquent, vous devez rechercher la clé primaire de l'utilisateur dans la table esUsers pour faire le lien avec les autres tables.

Remarque Cet article utilise le SQL dynamique uniquement pour illustrer les concepts. Dans un système de sécurité d'entreprise réel, vous devrez utiliser des procédures stockées pour tous les appels SQL.
// C#
public DataSet GetAppsByLoginID(string loginID)
{
 DataSet ds = new DataSet();
 SqlCommand cmd;
 SqlDataAdapter da;
 string sql;

 sql = "SELECT esApps.iAppID, esAppsUsers.iUserID, ";
 sql += " esApps.sAppName, esApps.sDesc, esApps.sURL ";
 sql += " FROM esApps";
 sql += " INNER JOIN esAppsUsers ";
 sql += " ON esApps.iAppID = esAppsUsers.iAppID ";
 sql += " INNER JOIN esUsers ";
 sql += " ON esAppsUsers.iUserID = esUsers.iUserID ";
 sql += " WHERE sLoginID = @sLoginID ";
 sql = String.Format(sql, Apps.LoginIDNoDomain(loginID));

 try
 {
 cmd = new SqlCommand(sql);
 cmd.Parameters.Add(new 
 SqlParameter("@sLoginID", SqlDbType.Char));
 cmd.Parameters["@sLoginID"].Value = 
 Apps.LoginIDNoDomain(loginID);
 cmd.Connection = new SqlConnection(mConnectString);

 da = new SqlDataAdapter(cmd);

 da.Fill(ds);

 return ds;
 }
 catch (Exception ex)
 {
 throw ex;
 }
}

' VB.NET
Public Function GetAppsByLoginID(ByVal LoginID As String) _
 As DataSet
 Dim ds As New DataSet
 Dim cmd As SqlCommand
 Dim da As SqlDataAdapter
 Dim sql As String

 sql = "SELECT esApps.iAppID, esAppsUsers.iUserID, "
 sql &= " esApps.sAppName, esApps.sDesc, esApps.sURL "
 sql &= " FROM esApps"
 sql &= " INNER JOIN esAppsUsers "
 sql &= " ON esApps.iAppID = esAppsUsers.iAppID "
 sql &= " INNER JOIN esUsers "
 sql &= " ON esAppsUsers.iUserID = esUsers.iUserID "
 sql &= " WHERE sLoginID = @sLoginID "
 sql = String.Format(sql, Apps.LoginIDNoDomain(LoginID))

 Try
 cmd = New SqlCommand(sql)
 cmd.Parameters.Add(New _
 SqlParameter("@sLoginID", SqlDbType.Char))
 cmd.Parameters("@sLoginID").Value = _
 Apps.LoginIDNoDomain(LoginID)
 cmd.Connection = New SqlConnection(mConnectString)

 da = New SqlDataAdapter(cmd)
 da.Fill(ds)

 Return ds

 Catch ex As Exception
 Throw ex
 End Try
End Function

La méthode CreateLoginToken

Lorsque l'utilisateur clique sur une application dans le contrôle DataGrid, un nouveau jeton doit être créé. Cette tâche est assurée par la méthode CreateLoginToken.

// C#
public string CreateLoginToken(string appName, 
 string loginID, int userID, int appID)
{
 SqlCommand cmd = new SqlCommand();
 SqlParameter param;
 string token;
 string sql;

 // Generate a new Token
 token = GenerateToken();

 sql = "INSERT INTO esAppToken(sToken, sAppName, ";
 sql += " sLoginID, iUserID, iAppID, dtCreated) ";
 sql += " VALUES(@sToken, @sAppName, @sLoginID, ";
 sql += " @iUserID, @iAppID, @dtCreated) ";

 param = new SqlParameter("@sToken", SqlDbType.Char);
 param.Value = token;
 cmd.Parameters.Add(param);

 param = new SqlParameter("@sAppName", SqlDbType.Char);
 param.Value = appName;
 cmd.Parameters.Add(param);

 param = new SqlParameter("@sLoginID", SqlDbType.Char);
 param.Value = Apps.LoginIDNoDomain(loginID);
 cmd.Parameters.Add(param);

 param = new SqlParameter("@iUserID", SqlDbType.Int);
 param.Value = userID;
 cmd.Parameters.Add(param);

 param = new SqlParameter("@iAppID", SqlDbType.Int);
 param.Value = appID;
 cmd.Parameters.Add(param);

 param = new SqlParameter("@dtCreated", SqlDbType.DateTime);
 param.Value = DateTime.Now;
 cmd.Parameters.Add(param);

 try
 {
 cmd.CommandType = CommandType.Text;
 cmd.CommandText = sql;

 cmd.Connection = new SqlConnection(mConnectString);
 cmd.Connection.Open();

 cmd.ExecuteNonQuery();
 }
 catch (Exception ex)
 {
 throw ex;
 }
 finally
 {
 if (cmd.Connection.State != ConnectionState.Closed)
 {
 cmd.Connection.Close();
 cmd.Connection.Dispose();
 }
 }

 return token;
}

' VB.NET
Public Function CreateLoginToken(ByVal AppName As String, _
 ByVal LoginID As String, ByVal UserID As Integer, _
 ByVal AppID As Integer) As String
 Dim cmd As New SqlCommand
 Dim param As SqlParameter
 Dim token As String
 Dim sql As String

 ' Generate a new Token
 token = GenerateToken()

 sql = "INSERT INTO esAppToken(sToken, sAppName, "
 sql &= " sLoginID, iUserID, iAppID, dtCreated) "
 sql &= " VALUES(@sToken, @sAppName, @sLoginID, "
 sql &= " @iUserID, @iAppID, @dtCreated)"
 sql = String.Format(sql, token, AppName, _
 Apps.LoginIDNoDomain(LoginID), UserID, AppID, _
 DateTime.Now.ToString())

 param = New SqlParameter("@sToken", SqlDbType.Char)
 param.Value = token
 cmd.Parameters.Add(param)

 param = New SqlParameter("@sAppName", SqlDbType.Char)
 param.Value = AppName
 cmd.Parameters.Add(param)

 param = New SqlParameter("@sLoginID", SqlDbType.Char)
 param.Value = Apps.LoginIDNoDomain(LoginID)
 cmd.Parameters.Add(param)

 param = New SqlParameter("@iUserID", SqlDbType.Int)
 param.Value = UserID
 cmd.Parameters.Add(param)

 param = New SqlParameter("@iAppID", SqlDbType.Int)
 param.Value = AppID
 cmd.Parameters.Add(param)

 param = New SqlParameter("@dtCreated", SqlDbType.DateTime)
 param.Value = DateTime.Now
 cmd.Parameters.Add(param)

 Try
 cmd.CommandType = CommandType.Text
 cmd.CommandText = sql

 cmd.Connection = New SqlConnection(mConnectString)
 cmd.Connection.Open()

 cmd.ExecuteNonQuery()

 Catch ex As Exception
 Throw ex

 Finally
 If cmd.Connection.State <> ConnectionState.Closed Then
 cmd.Connection.Close()
 cmd.Connection.Dispose()
 End If
 End Try

 Return token
End Function

Pour créer le jeton, c'est la méthode GenerateToken qui est appelée. Cette scission de la méthode CreateLoginToken vous permet de modifier ultérieurement le type de jeton que vous générez. Vous trouverez quelques idées à ce sujet à la fin de cet article. Cette méthode utilise la classe Guid pour générer un nouveau GUID comme jeton.

// C#
public string GenerateToken()
{
 return System.Guid.NewGuid().ToString();
}

' VB.NET
Public Function GenerateToken() As String
 Return System.Guid.NewGuid().ToString()
End Function

Récupération des jetons dans les applications Web

Pour tester le lancement d'une application à partir du lanceur d'applications, le mieux est de créer une application Web fictive (cf. figure 10). Cette application Web de test utilisera le projet AppLauncherDataxx (cf. supra).

singlesignon_10.gif

Figure 10. Chaque application Web utilisera le projet AppLauncherDataxx en combinaison avec une page AppLogin, Login et Default.

Modification du fichier Web.Config

Pour créer une application Web compatible avec le système d'authentification unique, vous devez d'abord modifier le fichier Web.config et créer une section <appSettings> qui accueillera la chaîne de connexion qui permet de faire l'interface avec la base de données eSecurity. Vous devez également stocker l'ID et le nom de l'application pour prévoir l'accès d'utilisateurs externes. Vu que vous n'avez pas créé de jeton dans ces cas-là, vous devez connaître de quelle application il s'agit de manière à pouvoir charger un objet AppToken à utiliser lors de la configuration des rôles d'un utilisateur. Vous verrez comment faire plus loin dans l'article.

<appSettings>
 <add key="eSecurityConnectString"
 value="server=(local);Database=
  eSecurity;uid=mUserID;pwd=myPassword"></add>
 <add key="eSecurityAppID" value="1"></add>
 <add key="eSecurityAppName" value="Payroll"></add>
</appSettings>

Vous devez également configurer l'application Web de manière à ce qu'elle utilise l'authentification basée sur des formulaires. Pour ce faire, modifiez l'élément <authentication> comme illustré ci-dessous.

<authentication mode="Forms">
 <forms name="AppTest" loginUrl="Login.aspx" />
</authentication>

Pour terminer, vous devez également modifier l'élément <authorization> afin d'exclure les utilisateurs anonymes.

<authorization>
 <deny users="?" />
</authorization> 

La page AppLogin.aspx

Souvenez-vous que chaque application exécutée à partir du lanceur d'applications doit appeler la page AppLogin et lui transmettre le jeton généré. La page AppLogin vérifiera la validité du jeton, extraira les informations adéquates de la table esAppToken et supprimera l'enregistrement dans la table esAppToken pour empêcher toute réutilisation du jeton.

// C#
private void Page_Load(object sender, System.EventArgs e)
{
 VerifyToken();
}

private void VerifyToken()
{
 Apps app = new Apps();
 AppToken al;

 try
 {
 al = app.VerifyLoginToken(
 Request.QueryString["Token"].ToString());

 if(al.LoginID.Trim() == "")
 {
 // Not a valid login
 // Redirect them to default page 
 // This will put them at the login page
 Response.Redirect("default.aspx");
 }
 else
 {
 // Create a Forms Authentication Cookie
 // Set Forms authentication variables
 FormsAuthentication.Initialize();
 FormsAuthentication.SetAuthCookie(
 al.LoginID.ToString(), false);

 // Set the Application Token Object
 Application["AppToken"] = al;

 // Redirect to Default page
 Response.Redirect("default.aspx");
 }
 }
 catch
 {
 // Redirect them to the login page via the Default page
 Response.Redirect("default.aspx");
 }
}

' VB.NET
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load VerifyToken() End Sub Private Sub VerifyToken() Dim app As New Apps Dim al As AppToken Try al = app.VerifyLoginToken( _ Request.QueryString("Token").ToString()) If al.LoginID.Trim() = "" Then ' Not a valid login ' Redirect them to default page ' This will put them at the login page Response.Redirect("default.aspx") Else ' Create a Forms Authentication Cookie ' Set Forms authentication variables FormsAuthentication.Initialize() FormsAuthentication.SetAuthCookie( _ al.LoginID.ToString(), False) ' Set the Application Token Object Application("AppToken") = al ' Redirect to Default page Response.Redirect("default.aspx") End If Catch ' Redirect them to the login page via the Default page Response.Redirect("default.aspx") End Try End Sub

La vérification de la validité du jeton se fait au moyen de la méthode VerifyLogin. Pour ce faire, appelez la méthode VerifyLoginToken dans la classe Apps. Cette méthode renvoie une instance de la classe AppToken. Si la propriété LoginID de cette classe est complétée, vous savez que vous avez affaire à un utilisateur valide. Si ce n'est pas le cas, le jeton n'est pas valide. La méthode renvoie alors l'utilisateur à la page default.aspx du site Web. Naturellement, comme l'authentification basée sur des formulaires est activée, l'utilisateur est redirigé vers la page Login.aspx où il est invité à ouvrir une session.

Un cookie d'authentification par formulaires est envoyé grâce à l'appel de deux méthodes : FormsAuthentication.Initialize et FormsAuthentication.SetAuthCookie. Ces deux méthodes envoient un cookie de mémoire au navigateur. Ce cookie est ensuite vérifié par le runtime ASP.NET à chaque fois que l'utilisateur revient sur le site.

La méthode VerifyLoginToken

Cette méthode repère le jeton généré transmis par le biais de la ligne de requête et vérifie sa validité. Elle accède à la table esAppToken et y recherche le jeton. Si le jeton s'y trouve, toutes les valeurs de la table sont placées dans des propriétés individuelles de l'objet AppToken. Cet objet AppToken est ensuite renvoyé par la méthode.

// C#
public AppToken VerifyLoginToken(string Token)
{
 AppToken al = new AppToken();
 DataSet ds = new DataSet();
 SqlCommand cmd;
 DataRow dr;
 SqlDataAdapter da;
 string sql;

 sql = "SELECT iAppTokenID, sAppName, sLoginID, ";
 sql += " iAppID, iUserID ";
 sql += " FROM esAppToken";
 sql += " WHERE sToken = @sToken ";

 try
 {
 cmd = new SqlCommand(sql);
 cmd.Parameters.Add(new 
 SqlParameter("@sToken", SqlDbType.Char));
 cmd.Parameters["@sToken"].Value = Token;
 cmd.Connection = new SqlConnection(mConnectString);

 da = new SqlDataAdapter(cmd);
 da.Fill(ds);

 if (ds.Tables[0].Rows.Count > 0)
 {
 dr = ds.Tables[0].Rows[0];

 al.LoginID = dr["sLoginID"].ToString();
 al.AppName = dr["sAppName"].ToString();
 al.AppKey = Convert.ToInt32(dr["iAppID"]);
 al.LoginKey = Convert.ToInt32(dr["iUserID"]);

 DeleteToken(Convert.ToInt32(dr["iAppTokenID"]));
 }
 }
 catch (Exception ex)
 {
 throw ex;
 }

 return al;
}

' VB.NET
Public Function VerifyLoginToken(ByVal Token As String) As AppToken
 Dim al As New AppToken
 Dim ds As New DataSet
 Dim cmd As SqlCommand
 Dim dr As DataRow
 Dim da As SqlDataAdapter
 Dim sql As String

 sql = "SELECT iAppTokenID, sAppName, sLoginID, "
 sql &= " iAppID, iUserID "
 sql &= " FROM esAppToken"
 sql &= " WHERE sToken = @sToken "

 Try
 cmd = New SqlCommand(sql)
 cmd.Parameters.Add(New _
 SqlParameter("@sToken", SqlDbType.Char))
 cmd.Parameters("@sToken").Value = Token
 cmd.Connection = New SqlConnection(mConnectString)

 da = New SqlDataAdapter(cmd)

 da.Fill(ds)

 If ds.Tables(0).Rows.Count > 0 Then
 dr = ds.Tables(0).Rows(0)

 al.LoginID = dr("sLoginID").ToString()
 al.AppName = dr("sAppName").ToString()
 al.AppKey = Convert.ToInt32(dr("iAppID"))
 al.LoginKey = Convert.ToInt32(dr("iUserID"))

 DeleteToken(Convert.ToInt32(dr("iAppTokenID")))
 End If

 Catch ex As Exception
 Throw ex
 End Try

 Return al
End Function

Sécurité par rôle

Une fois authentifié par l'application Web, l'utilisateur est redirigé vers la page default.aspx. Étant donné que vous avez envoyé un cookie d'authentification au navigateur, ce cookie est renvoyé à chaque fois. Avant que l'utilisateur ne soit redirigé vers la page demandée, la méthode Application_AuthenticateRequest du fichier .asax est appelée. C'est dans cette méthode que vous pouvez configurer les éventuels rôles associés à l'utilisateur. Je ne vais pas vous montrer cette méthode car elle fait l'objet d'autres articles. Le code fourni dans cet article est un exemple de la manière dont elle fonctionne. Ce code est très simple. Vous voudrez certainement l'améliorer pour utiliser la mise en cache, mais il vous donne déjà une idée de par où commencer.

Amélioration de votre système à authentification unique

Cet article vous a présenté les différentes étapes de la création d'un système de sécurité d'entreprise. Cependant, le but poursuivi n'était pas d'en décrire tous les aspects. Par exemple, vous devrez ajouter une série d'écrans de maintenance pour permettre à l'administrateur d'ajouter des utilisateurs et de les mapper vers les applications. Ces écrans doivent également être sécurisés de manière à ce que seuls les utilisateurs dotés un certain rôle puissent y accéder.

Une autre manière de compléter votre système consisterait à permettre l'ajout automatique d'un utilisateur de domaine qui n'est pas encore dans le système. Cela vous éviterait de devoir entrer tous les utilisateurs manuellement, et vous permettrait même d'écrire une application qui s'en charge. Il est fort probable que vous vouliez attribuer à ces utilisateurs un rôle par défaut pour qu'ils puissent accéder à certaines applications et pas à d'autres. De plus, vous pourriez prévoir d'avertir l'administrateur par courrier électronique lorsqu'un nouvel utilisateur est ajouté de cette manière.

Étant donné que ce système de sécurité repose sur la création d'un jeton spécifique et l'application Web appelée après cette création, un problème pourrait se poser si l'application Web ne répond pas à la requête de lancement. Si cela se produit, le jeton reste dans la base de données, ce qui présente un risque potentiel pour la sécurité de l'entreprise. Pour parer à cette éventualité, vous pourriez créer une tâche planifiée qui supprime les jetons générés il y a plus de x minutes. Vous pourriez également créer votre propre jeton comprenant le jeton en soi et l'heure à laquelle il a été créé. Il vous suffirait ensuite de modifier la méthode VerifyLoginToken pour qu'elle vérifie l'heure et ne valide pas le jeton si le nombre de minutes spécifié est dépassé.

Le jeton utilisé dans cet article est un GUID, ce qui vous oblige d'utiliser un rappel à la base de données pour récupérer les informations de profil d'un utilisateur. Par conséquent, une belle amélioration du système présenté dans cet article consisterait à utiliser un jeton basé sur la norme d'amélioration WS-Security, c'est-à-dire qui contient les informations de profil cryptées, et de le transmettre du lanceur d'applications à chaque application Web. Cela éliminerait les allers-retours vers la base de données.

Pour terminer, il faudra bien entendu remplacer tous les appels SQL dynamiques par des procédures stockées et des objets Command avec paramètres pour éviter toute possibilité d'attaques par injection SQL et complètement sécuriser les tables de la solution. Bref, il est préférable d'utiliser des procédures stockées et des paramètres sur les objets Command.

Installation des exemples

Deux exemples d'applications ont été créés pour les besoins de cet article. Elles sont toutes deux des applications Web. Les solutions pour chacune des applications comprennent aussi une bibliothèque de classes. Les exemples sont écrits en Microsoft Visual Basic® .NET et en C# pour vous permettre de choisir. Le fichier de solution contient des fichiers .SQL pour vous aider à créer les tables nécessaires dans la base de données. Les fichiers .SQL sont destinés à être utilisés dans SQL Server, mais vous pouvez facilement les modifier pour les utiliser dans d'autres systèmes de base de données. Voici les étapes nécessaires pour installer les exemples d'applications.

  1. Créez une base de données dans votre DBMS. Appelez-la eSecurity.
  2. Exécutez les fichiers .SQL pour créer les tables dans la base de données eSecurity.
  3. Décompressez les fichiers du fichier .ZIP fourni dans un dossier.
  4. Créez des répertoires virtuels qui pointent vers chacun des dossiers que vous voulez utiliser. Par exemple, si vous utilisez les exemples VB.NET, créez deux répertoires virtuels appelés AppLauncherVB et AppTestVB qui pointent vers ces deux dossiers.
  5. Modifiez les fichiers .SLN pour pointer vers vos dossiers de répertoires virtuels.

Conclusion

Un système à authentification unique pour applications Web aide les utilisateurs à gagner du temps en leur évitant de devoir mémoriser les nombreux ID de connexion et mots de passe qu'ils doivent utiliser dans l'entreprise. De plus, ce type de système vous permet d'autoriser des utilisateurs externes à utiliser vos applications Web internes sans devoir les ajouter à votre domaine. De plus, l'implémentation d'un tel système ne requiert que quelques tables et quelques classes très simples. La création d'un système de sécurité d'entreprise robuste avec authentification unique demande, certes, un travail plus conséquent, mais cet article vous offre déjà une belle longueur d'avance.

Bibliographie (en anglais)

À propos de l'auteur

Paul D. Sheriff est président de PDSA, Inc., (http://www.pdsa.com/ Lien externe au site MSDN France Site en anglais), une société de conseil partenaire de Microsoft qui propose des conseils, des produits et des services .NET, dont un document SDLC et un framework architectural. Paul est Directeur régional Microsoft pour la Californie du Sud. Il est l'auteur notamment de l'ouvrage ASP.NET Developer's Jumpstart (Addison-Wesley) sur .NET et de plusieurs ouvrages électroniques disponibles sur le site Web PDSA. Vous pouvez le contacter directement en écrivant à PSheriff@pdsa.com

Dernière mise à jour le mardi 15 juin 2004



Afficher:
© 2014 Microsoft