Share via


Sécurité : Authentification et autorisation dans ASP.NET Web Forms et Blazor

Conseil

Ce contenu est un extrait du livre électronique, Blazor pour les développeurs ASP NET Web Forms pour Azure, disponible dans la documentation .NET ou au format PDF à télécharger gratuitement pour le lire hors connexion.

Blazor-for-ASP-NET-Web-Forms-Developers eBook cover thumbnail.

La migration d’une application Web Forms ASP.NET vers Blazor nécessite très probablement une mise à jour de la façon dont l’authentification et l’autorisation sont effectuées, en supposant que l’application avait configuré l’authentification. Ce chapitre explique comment migrer à partir du modèle de fournisseur universel ASP.NET Web Forms (pour l’appartenance, les rôles et les profils utilisateur) et comment utiliser ASP.NET Core Identity à partir d’applications Blazor. Bien que ce chapitre traite des étapes et des considérations générales, les étapes détaillées et les scripts sont disponibles dans la documentation référencée.

Fournisseurs universels ASP.NET

Depuis ASP.NET 2.0, la plateforme ASP.NET Web Forms a pris en charge un modèle fournisseur pour diverses fonctionnalités, y compris l’appartenance. Le fournisseur d’appartenance universel, ainsi que le fournisseur de rôles facultatif, est généralement déployé avec des applications ASP.NET Web Forms. Il offre un moyen robuste et sécurisé de gérer l’authentification et l’autorisation qui continue de fonctionner correctement aujourd’hui. L’offre la plus récente de ces fournisseurs universels est disponible en tant que package NuGet, Microsoft.AspNet.Providers.

Les fournisseurs universels fonctionnent avec un schéma de base de données SQL qui inclut des tables telles que aspnet_Applications, aspnet_Membership, aspnet_Roles et aspnet_Users. Lorsqu’il est configuré en exécutant la commande aspnet_regsql.exe, les fournisseurs installent des tables et des procédures stockées qui fournissent toutes les requêtes et commandes nécessaires pour fonctionner avec les données sous-jacentes. Le schéma de base de données et ces procédures stockées ne sont pas compatibles avec les systèmes ASP.NET Identity et ASP.NET Core Identity plus récents. Les données existantes doivent donc être migrées vers le nouveau système. La figure 1 montre un exemple de schéma de table configuré pour les fournisseurs universels.

universal providers schema

Le fournisseur universel gère les utilisateurs, l’appartenance, les rôles et les profils. Les utilisateurs reçoivent des identificateurs globaux uniques et des informations de base telles que userId, userName, etc. sont stockées dans la table aspnet_Users. Les informations d’authentification, telles que le mot de passe, le format de mot de passe, le sel de mot de passe, les compteurs de verrouillage et les détails, etc. sont stockées dans la table aspnet_Membership. Les rôles se composent simplement de noms et d’identificateurs uniques, qui sont attribués aux utilisateurs via la table d’association aspnet_UsersInRoles, fournissant une relation plusieurs-à-plusieurs.

Si votre système existant utilise des rôles en plus de l’appartenance, vous devez migrer les comptes d’utilisateur, les mots de passe associés, les rôles et l’appartenance au rôle dans ASP.NET Core Identity. Vous devrez également probablement mettre à jour votre code où vous effectuez actuellement des vérifications de rôle à l’aide des instructions if pour exploiter à la place des filtres déclaratifs, des attributs et/ou de l’assistance des balises. Nous examinerons plus en détail les considérations relatives à la migration à la fin de ce chapitre.

Configuration d’autorisation dans Web Forms

Pour configurer l’accès autorisé à certaines pages dans une application ASP.NET Web Forms, vous spécifiez généralement que certaines pages ou dossiers sont inaccessibles aux utilisateurs anonymes. Cette configuration est effectuée dans le fichier web.config :

<?xml version="1.0"?>
<configuration>
    <system.web>
      <authentication mode="Forms">
        <forms defaultUrl="~/home.aspx" loginUrl="~/login.aspx"
          slidingExpiration="true" timeout="2880"></forms>
      </authentication>

      <authorization>
        <deny users="?" />
      </authorization>
    </system.web>
</configuration>

La section de configuration authentication configure l’authentification par formulaire pour l’application. La section authorization est utilisée pour interdire les utilisateurs anonymes pour l’ensemble de l’application. Toutefois, vous pouvez fournir des règles d’autorisation plus granulaires par emplacement et appliquer des vérifications d’autorisation basées sur des rôles.

<location path="login.aspx">
  <system.web>
    <authorization>
      <allow users="*" />
    </authorization>
  </system.web>
</location>

La configuration ci-dessus, lorsqu’elle est combinée à la première, permet aux utilisateurs anonymes d’accéder à la page de connexion, en remplaçant la restriction à l’échelle du site sur les utilisateurs non authentifiés.

<location path="/admin">
  <system.web>
    <authorization>
      <allow roles="Administrators" />
      <deny users="*" />
    </authorization>
  </system.web>
</location>

La configuration ci-dessus, lorsqu’elle est combinée aux autres, limite l’accès au dossier /admin et toutes les ressources qu’il contient aux membres du rôle « Administrateurs ». Cette restriction peut également être appliquée en plaçant un fichier web.config distinct dans la racine du dossier /admin.

Code d’autorisation dans Web Forms

Outre la configuration de l’accès à l’aide de web.config, vous pouvez également configurer par programmation l’accès et le comportement dans votre application Web Forms. Par exemple, vous pouvez restreindre la possibilité d’effectuer certaines opérations ou d’afficher certaines données en fonction du rôle de l’utilisateur.

Ce code peut être utilisé à la fois dans la logique code-behind et dans la page elle-même :

<% if (HttpContext.Current.User.IsInRole("Administrators")) { %>
  <a href="/admin">Go To Admin</a>
<% } %>

En plus de vérifier l’appartenance au rôle d’utilisateur, vous pouvez également déterminer s’ils sont authentifiés (bien que cela soit souvent mieux fait à l’aide de la configuration basée sur l’emplacement décrite ci-dessus). Voici un exemple de cette approche.

protected void Page_Load(object sender, EventArgs e)
{
    if (!User.Identity.IsAuthenticated)
    {
        FormsAuthentication.RedirectToLoginPage();
    }
    if (!Roles.IsUserInRole(User.Identity.Name, "Administrators"))
    {
        MessageLabel.Text = "Only administrators can view this.";
        SecretPanel.Visible = false;
    }
}

Dans le code ci-dessus, le contrôle d’accès en fonction du rôle (RBAC) est utilisé pour déterminer si certains éléments de la page, tels qu’un SecretPanel, sont visibles en fonction du rôle de l’utilisateur actuel.

En règle générale, les applications ASP.NET Web Forms configurent la sécurité dans le fichier web.config, puis ajoutent des vérifications supplémentaires si nécessaire dans les pages .aspx et leurs fichiers code-behind .aspx.cs associés. La plupart des applications tirent parti du fournisseur d’appartenance universel, fréquemment avec le fournisseur de rôles supplémentaires.

Identité ASP.NET Core

Bien que toujours chargé de l’authentification et de l’autorisation, ASP.NET Core Identity utilise un ensemble différent d’abstractions et d’hypothèses par rapport aux fournisseurs universels. Par exemple, le nouveau modèle Identity prend en charge l’authentification tierce, ce qui permet aux utilisateurs de s’authentifier à l’aide d’un compte de réseaux sociaux ou d’un autre fournisseur d’authentification approuvé. ASP.NET Core Identity prend en charge l’interface utilisateur pour les pages couramment nécessaires, telles que la connexion, la déconnexion et l’inscription. L’API tire parti d’EF Core pour son accès aux données et utilise les migrations EF Core pour générer le schéma nécessaire pour prendre en charge son modèle de données. Cette introduction à Identity on ASP.NET Core fournit une bonne vue d’ensemble de ce qui est inclus avec ASP.NET Core Identity et comment commencer à l’utiliser. Si vous n’avez pas encore configuré ASP.NET Core Identity dans votre application et sa base de données, cela vous aidera à commencer.

Rôles, revendications et stratégies

Les fournisseurs universels et ASP.NET Core Identity prennent en charge le concept de rôles. Vous pouvez créer des rôles pour les utilisateurs et affecter des utilisateurs à des rôles. Les utilisateurs peuvent appartenir à un nombre quelconque de rôles, et vous pouvez vérifier l’appartenance aux rôles dans le cadre de votre implémentation d’autorisation.

En plus des rôles, ASP.NET Core Identity prend en charge les concepts des revendications et des stratégies. Bien qu’un rôle corresponde spécifiquement à un ensemble de ressources auxquelles un utilisateur dans ce rôle doit pouvoir accéder, une revendication fait simplement partie de l’identité d’un utilisateur. Une revendication est une paire valeur de nom qui représente ce que l’objet est, et non ce que le sujet peut faire.

Il est possible d’inspecter directement les revendications d’un utilisateur et de déterminer en fonction de ces valeurs si un utilisateur doit avoir accès à une ressource. Toutefois, ces vérifications sont souvent répétitives et dispersées dans tout le système. Une meilleure approche consiste à définir une stratégie.

Une stratégie d’autorisation se compose d’une ou plusieurs exigences. Les stratégies sont inscrites dans le cadre de la configuration du service d’autorisation dans la méthode ConfigureServices de Startup.cs. Par exemple, l’extrait de code suivant configure une stratégie appelée « CanadiensOnly », qui a l’exigence que l’utilisateur a la revendication Country avec la valeur « Canada ».

services.AddAuthorization(options =>
{
    options.AddPolicy("CanadiansOnly", policy => policy.RequireClaim(ClaimTypes.Country, "Canada"));
});

Vous pouvez en savoir plus sur la création de stratégies personnalisées dans la documentation.

Que vous utilisiez des stratégies ou des rôles, vous pouvez spécifier qu’une page particulière dans votre application Blazor nécessite ce rôle ou cette stratégie avec l’attribut [Authorize], appliqué avec la directive @attribute.

Exiger un rôle :

@attribute [Authorize(Roles ="administrators")]

Exiger qu’une stratégie soit satisfaite :

@attribute [Authorize(Policy ="CanadiansOnly")]

Si vous avez besoin d’accéder à l’état d’authentification, aux rôles ou aux revendications d’un utilisateur dans votre code, il existe deux façons principales d’obtenir cette fonctionnalité. La première consiste à recevoir l’état d’authentification en tant que paramètre en cascade. La seconde consiste à accéder à l’état à l’aide d’un AuthenticationStateProvider injecté. Les détails de chacune de ces approches sont décrits dans la documentation de sécurité Blazor.

Le code suivant montre comment recevoir AuthenticationState en tant que paramètre en cascade :

[CascadingParameter]
private Task<AuthenticationState> authenticationStateTask { get; set; }

Avec ce paramètre en place, vous pouvez obtenir l’utilisateur à l’aide de ce code :

var authState = await authenticationStateTask;
var user = authState.User;

Le code suivant montre comment injecter AuthenticationStateProvider :

@using Microsoft.AspNetCore.Components.Authorization
@inject AuthenticationStateProvider AuthenticationStateProvider

Avec le fournisseur en place, vous pouvez accéder à l’utilisateur avec le code suivant :

AuthenticationState authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
ClaimsPrincipal user = authState.User;

if (user.Identity.IsAuthenticated)
{
  // work with user.Claims and/or user.Roles
}

Remarque : Le composant AuthorizeView, abordé plus loin dans ce chapitre, fournit un moyen déclaratif de contrôler ce qu’un utilisateur voit sur une page ou un composant.

Pour travailler avec les utilisateurs et les revendications (dans des applications Blazor Server) vous devrez peut-être également injecter un UserManager<T> (utilisez IdentityUser par défaut) que vous pouvez utiliser pour énumérer et modifier les revendications d’un utilisateur. Commencez par injecter le type et l’affecter à une propriété :

@inject UserManager<IdentityUser> MyUserManager

Utilisez-la ensuite pour utiliser les revendications de l’utilisateur. L’exemple suivant montre comment ajouter et conserver une revendication sur un utilisateur :

private async Task AddCountryClaim()
{
    var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
    var user = authState.User;
    var identityUser = await MyUserManager.FindByNameAsync(user.Identity.Name);

    if (!user.HasClaim(c => c.Type == ClaimTypes.Country))
    {
        // stores the claim in the cookie
        ClaimsIdentity id = new ClaimsIdentity();
        id.AddClaim(new Claim(ClaimTypes.Country, "Canada"));
        user.AddIdentity(id);

        // save the claim in the database
        await MyUserManager.AddClaimAsync(identityUser, new Claim(ClaimTypes.Country, "Canada"));
    }
}

Si vous avez besoin d’utiliser des rôles, suivez la même approche. Vous devrez peut-être injecter unRoleManager<T> (utilisez IdentityRole pour le type par défaut) pour répertorier et gérer les rôles eux-mêmes.

Note : Dans les projets Blazor WebAssembly, vous devez fournir des API serveur pour effectuer ces opérations (au lieu d’utiliser UserManager<T> ou RoleManager<T> directement). Une application cliente Blazor WebAssembly gérerait les revendications et/ou les rôles en appelant en toute sécurité les points de terminaison d’API exposés à cet effet.

Guide de migration

La migration d’ASP.NET Web Forms et de fournisseurs universels vers ASP.NET Core Identity nécessite plusieurs étapes :

  1. Créer un schéma de base de données ASP.NET Core Identity dans la base de données de destination
  2. Migrer des données d’un schéma de fournisseur universel vers un schéma ASP.NET Core Identity
  3. Migrer la configuration de web.config vers les intergiciels et les services, généralement dans Program.cs (ou une classe Startup)
  4. Mettez à jour des pages individuelles à l’aide de contrôles et de conditions pour utiliser l’assistance des balises et de nouvelles API d’identité.

Chacune de ces étapes est décrite en détail dans les sections suivantes.

Création du schéma ASP.NET Core Identity

Il existe plusieurs façons de créer la structure de table nécessaire pour ASP.NET Core Identity. Le plus simple est de créer une application web ASP.NET Core. Choisissez Application web, puis modifiez le type d’authentification pour utiliser Comptes individuels.

new project with individual accounts

À partir de la ligne de commande, vous pouvez faire la même chose en exécutant dotnet new webapp -au Individual. Une fois l’application créée, exécutez-la et inscrivez-la sur le site. Vous devez déclencher une page comme celle illustrée ci-dessous :

apply migrations page

Cliquez sur le bouton « Appliquer des migrations » et les tables de base de données nécessaires doivent être créées pour vous. En outre, les fichiers de migration doivent apparaître dans votre projet, comme indiqué :

migration files

Vous pouvez exécuter la migration vous-même, sans exécuter l’application web, à l’aide de cet outil en ligne de commande :

dotnet ef database update

Si vous préférez exécuter un script pour appliquer le nouveau schéma à une base de données existante, vous pouvez scripter ces migrations à partir de la ligne de commande. Exécutez cette commande pour générer le script :

dotnet ef migrations script -o auth.sql

La commande ci-dessus génère un script SQL dans le fichier de sortie auth.sql, qui peut ensuite être exécuté sur la base de données souhaitée. Si vous rencontrez des problèmes lors de l’exécution des commandes dotnet ef, assurez-vous que les outils EF Core sont installés sur votre système.

Si vous avez des colonnes supplémentaires sur vos tables sources, vous devez identifier le meilleur emplacement pour ces colonnes dans le nouveau schéma. En règle générale, les colonnes trouvées sur la table aspnet_Membership doivent être mappées à la table AspNetUsers. Les colonnes sur aspnet_Roles doivent être mappées à AspNetRoles. Toutes les colonnes supplémentaires de la table aspnet_UsersInRoles sont ajoutées à la table AspNetUserRoles.

Cela vaut également la peine d’envisager de placer des colonnes supplémentaires sur des tables distinctes. Ainsi, les migrations futures n’auront pas besoin de tenir compte de ces personnalisations du schéma d’identité par défaut.

Migration de données de fournisseurs universels vers ASP.NET Core Identity

Une fois le schéma de table de destination en place, l’étape suivante consiste à migrer vos enregistrements d’utilisateur et de rôle vers le nouveau schéma. Une liste complète des différences de schéma, y compris les colonnes mappées aux nouvelles colonnes, se trouve ici.

Pour migrer vos utilisateurs de l’appartenance aux nouvelles tables d’identité, vous devez suivre les étapes décrites dans la documentation. Après avoir suivi ces étapes et le script fourni, vos utilisateurs devront modifier leur mot de passe la prochaine fois qu’ils se connecteront.

Il est possible de migrer les mots de passe utilisateur, mais le processus est beaucoup plus impliqué. Exiger des utilisateurs de mettre à jour leurs mots de passe dans le cadre du processus de migration et les encourager à utiliser de nouveaux mots de passe uniques est susceptible d’améliorer la sécurité globale de l’application.

Migration des paramètres de sécurité de web.config vers le démarrage de l’application

Comme indiqué ci-dessus, les fournisseurs d’appartenance et de rôle ASP.NET sont configurés dans le fichier web.config. Étant donné que les applications ASP.NET Core ne sont pas liées à IIS et utilisent un système distinct pour la configuration, ces paramètres doivent être configurés ailleurs. Dans la plupart des cas, ASP.NET Core Identity est configuré dans le fichier Program.cs. Ouvrez le projet web créé précédemment (pour générer le schéma de la table d’identité) et passez en revue son fichier Program.cs (ou Startup.cs).

Ce code ajoute la prise en charge d’EF Core et Identity :

// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options =>
    options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();

La méthode d’extension AddDefaultIdentity est utilisée pour configurer Identity pour utiliser la classe ApplicationDbContext et le type IdentityUser de l’infrastructure. Si vous utilisez un IdentityUser personnalisé, veillez à spécifier son type ici. Si ces méthodes d’extension ne fonctionnent pas dans votre application, vérifiez que vous disposez des instructions appropriées et que vous disposez des références de package NuGet nécessaires. Par exemple, votre projet doit avoir une version des packages Microsoft.AspNetCore.Identity.EntityFrameworkCore et Microsoft.AspNetCore.Identity.UI référencés.

En outre, dans Program.cs vous devez voir l’intergiciel nécessaire configuré pour le site. Plus précisément, UseAuthentication et UseAuthorization doivent être configurés et à l’emplacement approprié.

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();

app.UseStaticFiles();

app.UseRouting();

app.UseAuthentication();
app.UseAuthorization();

//app.MapControllers();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");

ASP.NET Identity ne configure pas l’accès anonyme ou en fonction du rôle aux emplacements à partir de Program.cs. Vous devez migrer les données de configuration d’autorisation spécifiques à l’emplacement vers des filtres dans ASP.NET Core. Notez quels dossiers et pages nécessitent ces mises à jour. Vous allez apporter ces modifications dans la section suivante.

Mise à jour de pages individuelles pour utiliser des abstractions ASP.NET Core Identity

Dans votre application ASP.NET Web Forms, si vous aviez des paramètres web.config pour refuser l’accès à certaines pages ou certains dossiers à des utilisateurs anonymes, vous migreriez ces modifications en ajoutant l’attribut [Authorize] à ces pages :

@attribute [Authorize]

Si, de plus, vous refusiez l’accès sauf aux utilisateurs appartenant à un certain rôle, vous migreriez pareillement ce comportement en ajoutant un attribut spécifiant un rôle :

@attribute [Authorize(Roles ="administrators")]

L’attribut [Authorize] fonctionne uniquement sur des composants @page qui sont atteints via le routeur Blazor. L’attribut ne fonctionne pas avec les composants enfants, qui doivent utiliser à la place AuthorizeView.

Si vous avez une logique dans la balise de page pour déterminer s’il faut afficher du code à un utilisateur donné, vous pouvez la remplacer par le composant AuthorizeView. Le composant AuthorizeView affiche sélectivement l’interface utilisateur en fonction de l’autorisation que l’utilisateur a pour l’afficher. Il expose également une variable context qui peut être utilisée pour accéder aux informations utilisateur.

<AuthorizeView>
    <Authorized>
        <h1>Hello, @context.User.Identity.Name!</h1>
        <p>You can only see this content if you are authenticated.</p>
    </Authorized>
    <NotAuthorized>
        <h1>Authentication Failure!</h1>
        <p>You are not signed in.</p>
    </NotAuthorized>
</AuthorizeView>

Vous pouvez accéder à l’état d’authentification dans la logique procédurale en accédant à l’utilisateur à partir d’une classe Task<AuthenticationState configurée avec l’attribut [CascadingParameter]. Cette configuration vous permet d’accéder à l’utilisateur, ce qui vous permet de déterminer s’il est authentifié et s’il appartient à un rôle particulier. Si vous devez évaluer une stratégie de façon procédurale, vous pouvez injecter une instance de IAuthorizationService et appeler la méthode AuthorizeAsync sur celle-ci. L’exemple de code suivant montre comment obtenir des informations utilisateur et autoriser un utilisateur autorisé à effectuer une tâche restreinte par la stratégie content-editor.

@using Microsoft.AspNetCore.Authorization
@inject IAuthorizationService AuthorizationService

<button @onclick="@DoSomething">Do something important</button>

@code {
    [CascadingParameter]
    private Task<AuthenticationState> authenticationStateTask { get; set; }

    private async Task DoSomething()
    {
        var user = (await authenticationStateTask).User;

        if (user.Identity.IsAuthenticated)
        {
            // Perform an action only available to authenticated (signed-in) users.
        }

        if (user.IsInRole("admin"))
        {
            // Perform an action only available to users in the 'admin' role.
        }

        if ((await AuthorizationService.AuthorizeAsync(user, "content-editor"))
            .Succeeded)
        {
            // Perform an action only available to users satisfying the
            // 'content-editor' policy.
        }
    }
}

AuthenticationState doit d’abord être configuré comme valeur en cascade avant de pouvoir être lié à un paramètre en cascade comme suit. Cela est généralement fait à l’aide du composant CascadingAuthenticationState. Cette configuration est généralement effectuée dans App.razor :

<CascadingAuthenticationState>
    <Router AppAssembly="@typeof(Program).Assembly">
        <Found Context="routeData">
            <AuthorizeRouteView RouteData="@routeData"
                DefaultLayout="@typeof(MainLayout)" />
        </Found>
        <NotFound>
            <LayoutView Layout="@typeof(MainLayout)">
                <p>Sorry, there's nothing at this address.</p>
            </LayoutView>
        </NotFound>
    </Router>
</CascadingAuthenticationState>

Résumé

Blazor utilise le même modèle de sécurité qu’ASP.NET Core, qui est ASP.NET Core Identity. La migration de fournisseurs universels vers ASP.NET Core Identity est relativement simple, en supposant qu’aucune personnalisation n’a été appliquée au schéma de données d’origine. Une fois les données migrées, l’utilisation de l’authentification et de l’autorisation dans les applications Blazor est bien documentée, avec une prise en charge configurable et de programmation pour la plupart des exigences de sécurité.

References