Authentification Windows de bout en bout (IE > IIS > ASP.NET > WCF > C# > SQLServer)

Le but

Au programme de ce post, une explication pas-à-pas de tout ce qu’il faut mettre en place pour faire circuler une authentification Windows depuis un navigateur jusqu’à la base de données, en passant par tous les serveurs d’une application n-tier. L’idée est qu’on puisse partir d’une authentification d’un utilisateur sur une machine cliente, et propager cette authentification de manière automatique, et ce jusqu’à la base de données, sur laquelle on sera authentifié sous l’identité de cette personne.

image

Bref, le but est de vous faire voir toutes les étapes d’impersonification, authentification, sécurité, etc. sur chacun des tiers concernés.

Mise en place de la solution

Nous allons créer une solution Visual Studio qui contiendra tous les tiers qui nous intéressent. Il s’agit de trois projets vides, un de type Librairie de classes, et deux autres de type Application Web ASP.NET Vide.

image

Dans le site web, on rajoute une simple page ASPX. Dans la librairie métier, on rajoute une classe qu’on nommera Dossier, et dans le service, une entrée WCF nommée Service.svc. On ajoute une référence du service au métier, et une référence de service du site web vers le service, ce qui nous donne au final ceci dans l’explorateur de solutions :

image

Pour l’instant, nous n’avons créé que le squelette, qui correspond à l’organisation suivante :

image

Astuce pour les projets web

Si vous vous êtes déjà battus avec les droits pour créer un répertoire virtuel directement depuis Visual Studio lors de la création d’un projet, ou bien si vous avez pesté comme beaucoup sur l’absence de cet onglet magique qu’était “Partage Web” dans les nouvelles versions de Windows, si le mode de création d’une application web depuis la console IIS vous donne des envies de sauter par la fenêtre, voici enfin la solution propre et anti-prise de tête pour créer un répertoire virtuel à l’endroit où vous souhaitez, sans avoir de problème de droits ou de prise en compte par Visual Studio (et sans tomber sur l’erreur due à la mauvaise version de .NET dans le pool d’application choisi au hasard)…

Dans Visual Studio, créez un projet de type Application Web ASP.NET Vide : comme ça, il vous laisse choisir l’endroit qui va bien pour votre TFS, etc. (faites bien un Nouveau Projet, et par un Nouveau Site Web) :

image

Ensuite, allez dans les propriétés du projet, sur l’onglet Web, et choisissez Utiliser le serveur Web IIS local plutôt que le choix par défaut qui utilise Cassini (Cher Microsoft, il faudra d’ailleurs penser à le virer au profit de IIS Express), puis cliquez sur le bouton magique “Créer un répertoire virtuel”.

image

Si tout se passe bien, et c’est précisément ce qui est bien avec cette méthode : c’est qu’elle se passe toujours bien, vous vous retrouvez avec une configuration qui vous permet de déboguer, où le répertoire virtuel est bien configuré, sur la bonne version de .NET, et avec le code au non endroit pour votre gestionnaire de code source. Plus de problème de droits sur les fichiers non accessibles par l’utilisateur, etc. La seule condition est de démarrer Visual Studio en mode Administrateur… mais c’est une faible concession au confort apporté par cette solution.

Réalisez cette opération pour les deux sites web que nous avons créés : Service et SiteWeb. J’adore cette fenêtre :

image

Après le temps cumulé perdu à régler des problèmes de répertoires virtuels / droits / versions de .NET / site web bloqué, c’est un plaisir de la voir s’afficher Sourire.

Côté base de données

Je ne vais pas m’attarder sur la base de données : déjà parce que ce n’est pas là qu’il y a le plus de difficulté, mais aussi parce que ce n’est pas ma spécialité et je ne veux pas risquer de raconter des bêtises…

En gros, il faut commencer par déclarer les utilisateurs Windows au niveau de l’instance SQLServer elle-même, dans la section Sécurité / Connexions (attention à ne pas vous tromper avec Sécurité / Utilisateurs de la base de données) :

image

Un clic-droit, puis “Nouvelle connexion”, et vous pourrez utiliser le bouton Rechercher de l’interface ci-dessous (je ne capture qu’une partie) pour retrouver l’utilisateur Windows qui vous intéresse :

image

Une fois cette étape réalisée, vous pourrez passer à la suite, dont on parlait précédemment, et ajouter un nouvel utilisateur dans la base de données cible (section Sécurité / Utilisateurs dans Management Studio) :

image

Là encore, l’interface vous permettra de retrouver facilement la connexion à associer :

image

Il faut ensuite régler tous les droits comme il faut pour que l’utilisateur créé ait bien les droits sur les tables de données qu’on va utiliser. Comme je vous le disais, je ne connais pas bien toutes ces histoires de droits sur les schémas / sur les entités / sur les utilisateurs, donc on va faire au plus simple, plutôt que de risquer de vous embrouiller. En créant la base de données en tant que l’utilisateur cible, il est automatiquement db-owner, ce qui simplifie considérablement les choses (attention, ce n’est bien sûr pas la pratique la plus propre du point de vue de la sécurité) :

image

Pour notre exemple, on crée une simple table de test pour recevoir des entrées, et valider ainsi la bonne authentification de bout en bout :

image

Avertissement

Avant de continuer, il faut préciser que le fait de propager l’authentification du client à la base de données n’est pas nécessairement une bonne idée. Ca l’est si vous avez un nombre limité et peu fluctuant d’utilisateurs et de droits. Dans le cas contraire, la gestion deviendra vite un casse-tête. Mais si cette condition est établie, les avantages existent également : les droits sont gérés au plus bas, donc la sécurité est maximale. Les capacités d’audits des opérations sont optimales, car vous pouvez suivre à la commande SQL près qui fait quoi et d’où vient telle ou telle entrée. Pour qui s’est déjà retrouvé dans l’impasse en observant qu’une ligne problématique avait été créé par l’utilisateur générique d’une application pilotée par quelques centaines d’utilisateurs physiques, c’est un énorme progrès.

Bref, dans tout ce qui touche au bancaire, ou avec des besoins étendus d’accountability, ça peut être intéressant, mais ne vous amusez pas à mettre ça en place pour votre prochaine boutique web Clignement d'œil.

Configuration du lien entre le business layer et la base de données

Cette partie-là est simplissime : insérez simplement “Integrated Security=True” dans votre chaîne de connexion, et l’identité de l’appelant sera celle présentée à la base de données.

Gestion de l’authentification dans le Business Layer

Du côté du métier, l’idée est bien sûr de pouvoir utiliser le système de Principal proposé par la BCL dans System.Security. En plus de la chaîne de connexion (normalement bien au chaud dans le fichier de configuration, mais on la met dans le code pour l’exemple), on voit comment utiliser le principal si on en a besoin, et surtout comment sécuriser la fonction contre tout accès non authentifié :

using System;
using System.Data.SqlClient;
using System.Threading;
using System.Security.Permissions;

namespace Business
{
    public class Dossier
    {
        [PrincipalPermission(SecurityAction.Demand, Authenticated=true)]
        public static void AjouterDossier(string Libelle)
        {
            Libelle += string.Format(" (créé par %s)", Thread.CurrentPrincipal.Identity.Name);

            using (SqlConnection conn = new SqlConnection(@"Data Source=ANTARES\SQLExpress;"
                + "Initial Catalog=AuthentificationBoutEnBout;Integrated Security=True"))
            using (SqlCommand comm = new SqlCommand(
                "INSERT INTO TEST (code, libelle) VALUES (@code, @lb)", conn))
            {
                comm.Parameters.AddWithValue("code", Guid.NewGuid().ToString("N"));
                conn.Open();
                comm.ExecuteNonQuery();
            }
        }
    }
}

 

Ce qui est particulièrement intéressant avec l’attribut PrincipalPermission, c’est qu’on peut jouer avec les rôles, et mettre ainsi en place une sécurité déclarative, qui garantit en plus que, quel que soit le code qu’on rajoute dans la fonction, on obtiendra de toute façon une SecurityException si on n’a pas les habilitations pour l’exécuter :

[PrincipalPermission(SecurityAction.Demand, Role = "ControleurFinancier")]
[PrincipalPermission(SecurityAction.Deny, Name = "domaine\\Johnny-LaFouine")]

 

Impersonification dans WCF

Bon, c’est là que ça devient un peu plus le bazar. C’est bien sympa de dire dans le métier qu’on se base sur le Principal et qu’on gère la sécurité, mais à un moment, il va bien falloir l’affecter, cette authentification. Et en WCF, c’est loin d’être aussi simple qu’en ASMX où un bon vieux <identity impersonate=”true”/> dans le fichier web.config faisait l’affaire.

La première chose à faire est de mettre le mode d’authentification en Windows dans le fichier web.config du site Service :

image

Dans la console IIS, on va changer les modes d’authentification :

image

Plus besoin de l’anonyme, mais on active le mode Windows :

image

Ca devrait suffire, mais accrochez-vous : si vous lancez ainsi le service, vous aurez une erreur vous expliquant qu’il vous manque le mode anonyme, dont on cherche justement à se débarrasser (désolé pour la version anglaise) :

The authentication schemes configured on the host (‘IntegratedWindowsAuthentication’) do not allow those configured on the binding ‘BasicHttpBinding’ (‘Anonymous’). Please ensure that the SecurityMode is set to Transport or TransportCredentialOnly. Additionally, this may be resolved by changing the authentication schemes for this application through the IIS management tool, through the ServiceHost.Authentication.AuthenticationSchemes property, in the application configuration file at the <serviceAuthenticationManager> element, by updating the ClientCredentialType property on the binding, or by adjusting the AuthenticationScheme property on the HttpTransportBindingElement.

La raison est que le endpoint MEX a besoin par défaut du mode anonyme. On va régler le problème en lui apposant le même binding que pour le service, avec l’authentification Windows en mode transport :

image

Enfin, on peut écrire notre implémentation de service, avec l’attribut qu’il faut pour pouvoir mettre en place l’impersonification :

using System.ServiceModel;
using Business;

namespace Service
{
    public class Service : IService
    {
        [OperationBehavior(Impersonation=ImpersonationOption.Required)]
        public void CreerDossier(string Libelle)
        {
            Dossier.AjouterDossier(Libelle);
        }
    }
}

 

Récupération de l’authentification dans le site web

Pour le reste, nous allons commencer par changer l’authentification pour le site web dans la console IIS, exactement de la même manière que pour le site de service, à part que nous ajoutons également l’emprunt d’identité ASP.NET :

image

Dans le fichier web.config du site WebForms, on voit le paramètre d’impersonification qui a été rajouté (quand vous revenez à Visual Studio, il propose de rafraîchir le fichier pour montrer les modifications effectuées par l’outil externe, à savoir la console IIS dans notre cas) :

image

Attention, encore quelque chose à connaître pour que ça fonctionne bien : l’impersonification refuse de fonctionner dans le mode pipeline ASP.NET intégré de IIS 7. Il faudra donc choisir pour ce répertoire virtuel l’exécution sur un autre pool d’application, comme par exemple ASP.NET v4.0 Classic :

image

Le point important étant que le pool d’application soit bien configuré en mode Classic pour le Mode pipeline géré :

image

Il faut également penser à rajouter le mode d’authentification qui va bien, dans ce site web applicatif WebForms, comme dans le site de service :

image

Mise en place du proxy dans le site web

L’étape suivante consiste à s’assurer que l’instance du proxy de service WCF que nous allons placer dans une page ASPX appellera correctement le service WCF, en tout cas d’une façon permettant l’envoi et la prise en compte correcte de l’authentification Windows. On devrait pouvoir enfin utiliser notre instance de proxy pour appeler le service web, là ? Oui… mais pas aussi simplement qu’on pourrait le croire. Il va encore falloir faire un petit ajout pour gérer la sécurité Windows :

using System;
using System.ServiceModel;
using System.Security.Principal;
using System.Net;

namespace SiteWeb
{
    public partial class Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {

        }

        protected void Button1_Click(object sender, EventArgs e)
        {
            BasicHttpBinding basicHttpBinding = new BasicHttpBinding();
            basicHttpBinding.Security.Mode = BasicHttpSecurityMode.TransportCredentialOnly;
            basicHttpBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Windows;

            EndpointAddress endpoint = new EndpointAddress("http://localhost/Service/Service.svc");

            Proxy.ServiceClient client = new Proxy.ServiceClient(basicHttpBinding, endpoint);
            client.ClientCredentials.Windows.AllowedImpersonationLevel 
                = TokenImpersonationLevel.Impersonation;
            client.ChannelFactory.Credentials.Windows.ClientCredential
                = CredentialCache.DefaultNetworkCredentials;

            client.CreerDossier("Coucou");
        }
    }
}

Arrivés là, vous croyez peut-être qu’on est au bout de nos peines ? Eh bien, non ! Il va déjà falloir refaire la référence de service, car on l’avait réalisée au début alors que le service n’utilisait pas l’authentification Windows. Pour une fois, cependant, les choses sont simples : lorsqu’on recrée la référence de service, le fichier web.config du site web est mis au propre, avec le bon more de transport Windows pour les crédentiels dans le binding :

image

Activation éventuelle de l’authentification intégrée dans le navigateur

Allez, il reste encore un tiers à gérer, mais c’est le plus simple : le client. Si vous utilisez Internet Explorer, tout est déjà fait pour vous, et l’authentification de votre session Windows sera automatiquement envoyé sur demande de IIS.

Si vous utilisez Firefox, il faudra faire un tour dans about:config et chercher NTLM, pour activer la bonne option.

Conclusion

Il faut un vache de bout de temps pour tout mettre en place, mais une fois passés tous ces obstacles, vous obtenez une authentification de bout en bout, qui vous permet, dans un LAN, de déléguer complètement le processus d’authentification à Windows, tout en vous donnant la possibilité à n’importe quel endroit de votre code C# de gérer la sécurité de manière déclarative, et sur la base de données d’avoir des audits ultra-fins.

En espérant que ça serve, et que ça évite à quelqu’un les deux heures de recherche pour arriver à faire fonctionner tout ensemble Sourire

About JP Gouigoux

Jean-Philippe Gouigoux est Architecte Logiciel, MVP Connected Systems Developer. Il intervient régulièrement à l'Université de Bretagne Sud ainsi qu'à l'Agile Tour. Plus de détails sur la page "Curriculum Vitae" de ce blog.
This entry was posted in .NET and tagged , . Bookmark the permalink.

2 Responses to Authentification Windows de bout en bout (IE > IIS > ASP.NET > WCF > C# > SQLServer)

  1. Richard Lacelle says:

    J’ai bien aimé vos informations.
    J’utilise Visual Studio Express 2013.
    Est ce que vous pouvez me dire ou IIS met le nom usager par défaut.
    Merci

    • JP Gouigoux says:

      Bonjour. Je ne comprends pas très bien la question. Est-ce que vous voulez parler de comment IIS met à disposition d’ASP.NET les crédentiels utilisateurs ? Ou quel utilisateur IIS renvoie s’il n’y a pas authentification (le “par défaut”) ?

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Captcha Captcha Reload