Freigeben über


Erstellen einer ASP.NET Core-Web-App mit Benutzerdaten, die durch Autorisierung geschützt sind

Von Rick Anderson und Joe Audette

In diesem Tutorial wird gezeigt, wie Sie eine ASP.NET Core-Web-App mit Benutzerdaten erstellen, die durch Autorisierung geschützt sind. Es enthält eine Liste der Kontakte, die authentifizierte (registrierte) Benutzer*innen erstellt haben. Es gibt drei Sicherheitsgruppen:

  • Registrierte Benutzer*innen können alle genehmigten Daten anzeigen und ihre eigenen Daten bearbeiten/löschen.
  • Manager*innen können Kontaktdaten genehmigen oder ablehnen. Nur genehmigte Kontakte sind für Benutzer*innen sichtbar.
  • Administrator*innen können alle Daten genehmigen/ablehnen und bearbeiten/löschen.

Die Abbildungen in diesem Dokument stimmen nicht genau mit den neuesten Vorlagen überein.

In der folgenden Abbildung ist Benutzer Rick (rick@example.com) angemeldet. Rick werden nur genehmigte Kontakte und Links zum Bearbeiten/Löschen/Neu erstellen für seine Kontakte angezeigt. Nur beim letzten Datensatz, der von Rick erstellt wurde, werden die Links zum Bearbeiten und Löschen angezeigt. Anderen Benutzer*innen wird der letzte Datensatz erst angezeigt, wenn sein Status durch Manager*innen oder Administrator*innen in „Genehmigt“ geändert wird.

Screenshot des angemeldeten Benutzers Rick

In der folgenden Abbildung ist manager@contoso.com mit der Rolle „Manager*in“ angemeldet:

Screenshot des angemeldeten Kontos manager@contoso.com

Die folgende Abbildung zeigt die Detailansicht eines Kontakts bei Manager*innen:

Ansicht eines Kontakts für Manager*innen

Die Schaltflächen Genehmigen und Ablehnen werden nur Manager*innen und Administrator*innen angezeigt.

In der folgenden Abbildung ist admin@contoso.com mit der Rolle „Administrator*in“ angemeldet:

Screenshot des angemeldeten Kontos admin@contoso.com

Administrator*innen verfügen über alle Berechtigungen. Sie können jeden Kontakt lesen, bearbeiten oder löschen und die Status von Kontakten ändern.

Die App wurde durch Gerüstbau des folgenden Contact-Modells erstellt:

public class Contact
{
    public int ContactId { get; set; }
    public string Name { get; set; }
    public string Address { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string Zip { get; set; }
    [DataType(DataType.EmailAddress)]
    public string Email { get; set; }
}

Das Beispiel enthält die folgenden Autorisierungshandler:

  • ContactIsOwnerAuthorizationHandler stellt sicher, dass Benutzer*innen nur ihre eigenen Daten bearbeiten können.
  • ContactManagerAuthorizationHandler ermöglicht Manager*innen, Kontakte zu genehmigen oder abzulehnen.
  • ContactAdministratorsAuthorizationHandler ermöglicht Administrator*innen das Genehmigen und Ablehnen von Kontakten und das Bearbeiten und Löschen von Kontakten.

Voraussetzungen

Dieses Tutorial ist für Fortgeschrittene konzipiert. Sie sollten mit den folgenden Punkten vertraut sein:

Die Starter-App und die fertige App

Laden Sie die fertige App herunter. Testen Sie die fertige App, um sich mit den Sicherheitsfeatures vertraut zu machen.

Tipp

Verwenden Sie git sparse-checkout, um nur den Beispielunterordner herunterzuladen. Zum Beispiel:

git clone --depth 1 --filter=blob:none https://github.com/dotnet/AspNetCore.Docs.git --sparse
cd AspNetCore.Docs
git sparse-checkout init --cone
git sparse-checkout set aspnetcore/security/authorization/secure-data/samples

Die Starter-App

Laden Sie die Starter-App herunter.

Führen Sie die App aus, tippen Sie auf den Link ContactManager, und überprüfen Sie, ob Sie einen Kontakt erstellen, bearbeiten und löschen können. Informationen zum Erstellen der Starter-App finden Sie unter Erstellen der Starter-App.

Schützen von Benutzerdaten

Die folgenden Abschnitte enthalten alle wichtigen Schritte zum Erstellen der App zum Schutz von Benutzerdaten. Es ist möglicherweise hilfreich, sich das fertige Projekt anzusehen.

Verknüpfen von Kontaktdaten mit Benutzer*innen

Verwenden Sie die ASP.NET Identity-Benutzer-ID, um sicherzustellen, dass Benutzer*innen ihre eigenen Daten bearbeiten können, aber nicht die Daten anderer Benutzer*innen. Fügen Sie OwnerID und ContactStatus dem Contact-Modell hinzu:

public class Contact
{
    public int ContactId { get; set; }

    // user ID from AspNetUser table.
    public string? OwnerID { get; set; }

    public string? Name { get; set; }
    public string? Address { get; set; }
    public string? City { get; set; }
    public string? State { get; set; }
    public string? Zip { get; set; }
    [DataType(DataType.EmailAddress)]
    public string? Email { get; set; }

    public ContactStatus Status { get; set; }
}

public enum ContactStatus
{
    Submitted,
    Approved,
    Rejected
}

OwnerID ist die Benutzer-ID aus der Tabelle AspNetUser in der Identity-Datenbank. Das Feld Status bestimmt, ob ein Kontakt für allgemeine Benutzer*innen sichtbar ist.

Erstellen Sie eine neue Migration, und aktualisieren Sie die Datenbank:

dotnet ef migrations add userID_Status
dotnet ef database update

Hinzufügen von Rollendiensten in Identity

Fügen Sie AddRoles an, um Rollendienste hinzuzufügen:

var builder = WebApplication.CreateBuilder(args);

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)
    .AddRoles<IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>();

Anfordern authentifizierter Benutzer*innen

Legen Sie die Fallbackautorisierungsrichtlinie so fest, dass Benutzer*innen authentifiziert werden müssen:

var builder = WebApplication.CreateBuilder(args);

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)
    .AddRoles<IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>();

builder.Services.AddRazorPages();

builder.Services.AddAuthorization(options =>
{
    options.FallbackPolicy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .Build();
});

Der oben hervorgehobene Code legt die Fallbackautorisierungsrichtlinie fest. Die Fallbackautorisierungsrichtlinie erfordert, dass alle Benutzer*innen authentifiziert werden, mit Ausnahme von Razor Pages, Controllern oder Aktionsmethoden mit einem Autorisierungsattribut. Beispielsweise verwenden Razor Pages, Controller oder Aktionsmethoden mit [AllowAnonymous] oder [Authorize(PolicyName="MyPolicy")] das angewendete Autorisierungsattribut anstelle der Autorisierungsrichtlinie für Fallbacks.

RequireAuthenticatedUser fügt der aktuellen Instanz DenyAnonymousAuthorizationRequirement hinzu. Dadurch wird die Authentifizierung des aktuellen Benutzers erzwungen.

Die Fallbackautorisierungsrichtlinie:

  • wird auf alle Anforderungen angewandt, die nicht explizit eine Autorisierungsrichtlinie angeben. Für Anforderungen, die über Endpunktrouting bereitgestellt werden, umfasst dies alle Endpunkte, die kein Autorisierungsattribut aufweisen. Für Anforderungen, die von anderer Middleware nach der Autorisierungsmiddleware bereitgestellt werden (z. B. statischen Dateien) wird die Richtlinie auf alle Anforderungen angewandt.

Durch Festlegen der Fallbackautorisierungsrichtlinie, durch die Benutzer*innen authentifiziert werden müssen, werden neu hinzugefügte Razor-Seiten und Controller geschützt. Wenn standardmäßig eine Autorisierung erzwungen wird, ist dies sicherer, als sich darauf zu verlassen, dass neue Controller und Razor Pages das [Authorize]-Attribut aufweisen.

Die AuthorizationOptions-Klasse enthält auch AuthorizationOptions.DefaultPolicy. DefaultPolicy ist die Richtlinie, die mit dem [Authorize]-Attribut verwendet wird, wenn keine Richtlinie angegeben wurde. [Authorize] enthält im Gegensatz zu [Authorize(PolicyName="MyPolicy")] keine benannte Richtlinie.

Weitere Informationen zu Richtlinien finden Sie unter Richtlinienbasierte Autorisierung in ASP.NET Core.

Eine alternative Möglichkeit für MVC-Controller und Razor Pages zum Erzwingen der Authentifizierung aller Benutzer*innen ist das Hinzufügen eines Autorisierungsfilters:

using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using ContactManager.Data;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.Authorization;

var builder = WebApplication.CreateBuilder(args);

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)
    .AddRoles<IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>();

builder.Services.AddRazorPages();

builder.Services.AddControllers(config =>
{
    var policy = new AuthorizationPolicyBuilder()
                     .RequireAuthenticatedUser()
                     .Build();
    config.Filters.Add(new AuthorizeFilter(policy));
});

var app = builder.Build();

Im obigen Code wird ein Autorisierungsfilter verwendet, der die Fallbackrichtlinie auf die Verwendung des Endpunktroutings festlegt. Das Festlegen der Fallbackrichtlinie ist die bevorzugte Methode, um die Authentifizierung aller Benutzer*innen zu erzwingen.

Fügen Sie AllowAnonymous den Seiten Index und Privacy hinzu, damit anonyme Benutzer*innen vor der Registrierung Informationen zur Website abfragen können:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace ContactManager.Pages;

[AllowAnonymous]
public class IndexModel : PageModel
{
    private readonly ILogger<IndexModel> _logger;

    public IndexModel(ILogger<IndexModel> logger)
    {
        _logger = logger;
    }

    public void OnGet()
    {

    }
}

Konfigurieren des Testkontos

Die SeedData-Klasse erstellt zwei Konten: „administrator“ und „manager“. Verwenden Sie das Tool Geheimnis-Manager, um ein Kennwort für diese Konten festzulegen. Legen Sie das Kennwort aus dem Projektverzeichnis (das Verzeichnis mit Program.cs) fest:

dotnet user-secrets set SeedUserPW <PW>

Wenn kein sicheres Kennwort angegeben ist, wird beim Aufrufen von SeedData.Initialize eine Ausnahme ausgelöst.

Aktualisieren Sie die App, damit das Testkennwort verwendet wird:

var builder = WebApplication.CreateBuilder(args);

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)
    .AddRoles<IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>();

builder.Services.AddRazorPages();

builder.Services.AddAuthorization(options =>
{
    options.FallbackPolicy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .Build();
});

// Authorization handlers.
builder.Services.AddScoped<IAuthorizationHandler,
                      ContactIsOwnerAuthorizationHandler>();

builder.Services.AddSingleton<IAuthorizationHandler,
                      ContactAdministratorsAuthorizationHandler>();

builder.Services.AddSingleton<IAuthorizationHandler,
                      ContactManagerAuthorizationHandler>();

var app = builder.Build();

using (var scope = app.Services.CreateScope())
{
    var services = scope.ServiceProvider;
    var context = services.GetRequiredService<ApplicationDbContext>();
    context.Database.Migrate();
    // requires using Microsoft.Extensions.Configuration;
    // Set password with the Secret Manager tool.
    // dotnet user-secrets set SeedUserPW <pw>

    var testUserPw = builder.Configuration.GetValue<string>("SeedUserPW");

   await SeedData.Initialize(services, testUserPw);
}

Erstellen der Testkonten und Aktualisieren der Kontakte

Aktualisieren Sie die Initialize-Methode in der SeedData-Klasse, um die Testkonten zu erstellen:

public static async Task Initialize(IServiceProvider serviceProvider, string testUserPw)
{
    using (var context = new ApplicationDbContext(
        serviceProvider.GetRequiredService<DbContextOptions<ApplicationDbContext>>()))
    {
        // For sample purposes seed both with the same password.
        // Password is set with the following:
        // dotnet user-secrets set SeedUserPW <pw>
        // The admin user can do anything

        var adminID = await EnsureUser(serviceProvider, testUserPw, "admin@contoso.com");
        await EnsureRole(serviceProvider, adminID, Constants.ContactAdministratorsRole);

        // allowed user can create and edit contacts that they create
        var managerID = await EnsureUser(serviceProvider, testUserPw, "manager@contoso.com");
        await EnsureRole(serviceProvider, managerID, Constants.ContactManagersRole);

        SeedDB(context, adminID);
    }
}

private static async Task<string> EnsureUser(IServiceProvider serviceProvider,
                                            string testUserPw, string UserName)
{
    var userManager = serviceProvider.GetService<UserManager<IdentityUser>>();

    var user = await userManager.FindByNameAsync(UserName);
    if (user == null)
    {
        user = new IdentityUser
        {
            UserName = UserName,
            EmailConfirmed = true
        };
        await userManager.CreateAsync(user, testUserPw);
    }

    if (user == null)
    {
        throw new Exception("The password is probably not strong enough!");
    }

    return user.Id;
}

private static async Task<IdentityResult> EnsureRole(IServiceProvider serviceProvider,
                                                              string uid, string role)
{
    var roleManager = serviceProvider.GetService<RoleManager<IdentityRole>>();

    if (roleManager == null)
    {
        throw new Exception("roleManager null");
    }

    IdentityResult IR;
    if (!await roleManager.RoleExistsAsync(role))
    {
        IR = await roleManager.CreateAsync(new IdentityRole(role));
    }

    var userManager = serviceProvider.GetService<UserManager<IdentityUser>>();

    //if (userManager == null)
    //{
    //    throw new Exception("userManager is null");
    //}

    var user = await userManager.FindByIdAsync(uid);

    if (user == null)
    {
        throw new Exception("The testUserPw password was probably not strong enough!");
    }

    IR = await userManager.AddToRoleAsync(user, role);

    return IR;
}

Fügen Sie den Kontakten die Administratorbenutzer-ID und ContactStatus hinzu. Legen Sie einen der Kontakte auf „Submitted“ (Übermittelt) und einen auf „Rejected“ (Abgelehnt) fest. Fügen Sie die Benutzer-ID und den Status allen Kontakten hinzu. Es wird nur ein Kontakt angezeigt:

public static void SeedDB(ApplicationDbContext context, string adminID)
{
    if (context.Contact.Any())
    {
        return;   // DB has been seeded
    }

    context.Contact.AddRange(
        new Contact
        {
            Name = "Debra Garcia",
            Address = "1234 Main St",
            City = "Redmond",
            State = "WA",
            Zip = "10999",
            Email = "debra@example.com",
            Status = ContactStatus.Approved,
            OwnerID = adminID
        },

Erstellen von Autorisierungshandlern für Besitzer*innen, Manager*innen und Administrator*innen

Erstellen Sie im Ordner ContactIsOwnerAuthorizationHandler eine -Klasse. Der ContactIsOwnerAuthorizationHandler überprüft, ob Benutzer*innen, die eine Ressource verwenden, Besitzer*in der Ressource sind.

using ContactManager.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization.Infrastructure;
using Microsoft.AspNetCore.Identity;
using System.Threading.Tasks;

namespace ContactManager.Authorization
{
    public class ContactIsOwnerAuthorizationHandler
                : AuthorizationHandler<OperationAuthorizationRequirement, Contact>
    {
        UserManager<IdentityUser> _userManager;

        public ContactIsOwnerAuthorizationHandler(UserManager<IdentityUser> 
            userManager)
        {
            _userManager = userManager;
        }

        protected override Task
            HandleRequirementAsync(AuthorizationHandlerContext context,
                                   OperationAuthorizationRequirement requirement,
                                   Contact resource)
        {
            if (context.User == null || resource == null)
            {
                return Task.CompletedTask;
            }

            // If not asking for CRUD permission, return.

            if (requirement.Name != Constants.CreateOperationName &&
                requirement.Name != Constants.ReadOperationName   &&
                requirement.Name != Constants.UpdateOperationName &&
                requirement.Name != Constants.DeleteOperationName )
            {
                return Task.CompletedTask;
            }

            if (resource.OwnerID == _userManager.GetUserId(context.User))
            {
                context.Succeed(requirement);
            }

            return Task.CompletedTask;
        }
    }
}

ContactIsOwnerAuthorizationHandler ruft context.Succeed auf, wenn der oder die aktuell authentifizierte Benutzer*in Kontaktbesitzer*in ist. Allgemeines zu Autorisierungshandlern:

  • Sie rufen context.Succeed auf, wenn die Anforderungen erfüllt sind.
  • Sie geben Task.CompletedTask zurück, wenn die Anforderungen nicht erfüllt sind. Die Rückgabe von Task.CompletedTask ohne einen vorherigen Aufruf von context.Success oder context.Fail stellt keinen Erfolg oder Fehler dar, sondern ermöglicht die Ausführung anderer Autorisierungshandler.

Wenn Sie Fehler explizit zurückgeben müssen, rufen Sie context.Fail auf.

Die App ermöglicht es Kontaktbesitzer*innen, ihre eigenen Daten zu bearbeiten, zu löschen und zu erstellen. ContactIsOwnerAuthorizationHandler muss den im Anforderungsparameter übergebenen Vorgang nicht überprüfen.

Erstellen eines Autorisierungshandlers für Manager*innen

Erstellen Sie im Ordner ContactManagerAuthorizationHandler eine -Klasse. Der ContactManagerAuthorizationHandler überprüft, ob Benutzer*innen, die diese Ressource verwenden, Manager*in sind. Nur Manager*innen können Inhaltsänderungen (neu oder geändert) genehmigen oder ablehnen.

using System.Threading.Tasks;
using ContactManager.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization.Infrastructure;
using Microsoft.AspNetCore.Identity;

namespace ContactManager.Authorization
{
    public class ContactManagerAuthorizationHandler :
        AuthorizationHandler<OperationAuthorizationRequirement, Contact>
    {
        protected override Task
            HandleRequirementAsync(AuthorizationHandlerContext context,
                                   OperationAuthorizationRequirement requirement,
                                   Contact resource)
        {
            if (context.User == null || resource == null)
            {
                return Task.CompletedTask;
            }

            // If not asking for approval/reject, return.
            if (requirement.Name != Constants.ApproveOperationName &&
                requirement.Name != Constants.RejectOperationName)
            {
                return Task.CompletedTask;
            }

            // Managers can approve or reject.
            if (context.User.IsInRole(Constants.ContactManagersRole))
            {
                context.Succeed(requirement);
            }

            return Task.CompletedTask;
        }
    }
}

Erstellen eines Autorisierungshandlers für Administrator*innen

Erstellen Sie im Ordner ContactAdministratorsAuthorizationHandler eine -Klasse. Der ContactAdministratorsAuthorizationHandler überprüft, ob Benutzer*innen, die diese Ressource verwenden, Administrator*in sind. Administrator*innen können alle Vorgänge ausführen.

using System.Threading.Tasks;
using ContactManager.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization.Infrastructure;

namespace ContactManager.Authorization
{
    public class ContactAdministratorsAuthorizationHandler
                    : AuthorizationHandler<OperationAuthorizationRequirement, Contact>
    {
        protected override Task HandleRequirementAsync(
                                              AuthorizationHandlerContext context,
                                    OperationAuthorizationRequirement requirement, 
                                     Contact resource)
        {
            if (context.User == null)
            {
                return Task.CompletedTask;
            }

            // Administrators can do anything.
            if (context.User.IsInRole(Constants.ContactAdministratorsRole))
            {
                context.Succeed(requirement);
            }

            return Task.CompletedTask;
        }
    }
}

Registrieren der Autorisierungshandler

Dienste, die Entity Framework Core verwenden, müssen mit bei der AddScoped registriert werden. Der ContactIsOwnerAuthorizationHandler verwendet ASP.NET Core Identity, das auf Entity Framework Core basiert. Registrieren Sie die Handler bei der Dienstsammlung, damit sie für den ContactsController über die Abhängigkeitsinjektion verfügbar sind. Fügen Sie am Ende von ConfigureServices folgenden Code hinzu:

var builder = WebApplication.CreateBuilder(args);

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)
    .AddRoles<IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>();

builder.Services.AddRazorPages();

builder.Services.AddAuthorization(options =>
{
    options.FallbackPolicy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .Build();
});

// Authorization handlers.
builder.Services.AddScoped<IAuthorizationHandler,
                      ContactIsOwnerAuthorizationHandler>();

builder.Services.AddSingleton<IAuthorizationHandler,
                      ContactAdministratorsAuthorizationHandler>();

builder.Services.AddSingleton<IAuthorizationHandler,
                      ContactManagerAuthorizationHandler>();

var app = builder.Build();

using (var scope = app.Services.CreateScope())
{
    var services = scope.ServiceProvider;
    var context = services.GetRequiredService<ApplicationDbContext>();
    context.Database.Migrate();
    // requires using Microsoft.Extensions.Configuration;
    // Set password with the Secret Manager tool.
    // dotnet user-secrets set SeedUserPW <pw>

    var testUserPw = builder.Configuration.GetValue<string>("SeedUserPW");

   await SeedData.Initialize(services, testUserPw);
}

ContactAdministratorsAuthorizationHandler und ContactManagerAuthorizationHandler werden als Singletons hinzugefügt. Sie sind Singletons, da sie das Entity Framework nicht verwenden und alle erforderlichen Informationen im Context-Parameter der HandleRequirementAsync-Methode enthalten sind.

Unterstützen der Autorisierung

In diesem Abschnitt aktualisieren Sie Razor Pages und fügen eine Betriebsanforderungenklasse hinzu.

Überprüfen der Betriebsanforderungsklasse für Kontakte

Überprüfen Sie die ContactOperations-Klasse. Diese Klasse enthält die Anforderungen, die von der App unterstützt werden:

using Microsoft.AspNetCore.Authorization.Infrastructure;

namespace ContactManager.Authorization
{
    public static class ContactOperations
    {
        public static OperationAuthorizationRequirement Create =   
          new OperationAuthorizationRequirement {Name=Constants.CreateOperationName};
        public static OperationAuthorizationRequirement Read = 
          new OperationAuthorizationRequirement {Name=Constants.ReadOperationName};  
        public static OperationAuthorizationRequirement Update = 
          new OperationAuthorizationRequirement {Name=Constants.UpdateOperationName}; 
        public static OperationAuthorizationRequirement Delete = 
          new OperationAuthorizationRequirement {Name=Constants.DeleteOperationName};
        public static OperationAuthorizationRequirement Approve = 
          new OperationAuthorizationRequirement {Name=Constants.ApproveOperationName};
        public static OperationAuthorizationRequirement Reject = 
          new OperationAuthorizationRequirement {Name=Constants.RejectOperationName};
    }

    public class Constants
    {
        public static readonly string CreateOperationName = "Create";
        public static readonly string ReadOperationName = "Read";
        public static readonly string UpdateOperationName = "Update";
        public static readonly string DeleteOperationName = "Delete";
        public static readonly string ApproveOperationName = "Approve";
        public static readonly string RejectOperationName = "Reject";

        public static readonly string ContactAdministratorsRole = 
                                                              "ContactAdministrators";
        public static readonly string ContactManagersRole = "ContactManagers";
    }
}

Erstellen einer Basisklasse für die Kontakte in Razor Pages

Erstellen Sie eine Basisklasse, die die in den Kontakten von Razor Pages verwendeten Dienste enthält. Die Basisklasse platziert den Initialisierungscode an einer Stelle:

using ContactManager.Data;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace ContactManager.Pages.Contacts
{
    public class DI_BasePageModel : PageModel
    {
        protected ApplicationDbContext Context { get; }
        protected IAuthorizationService AuthorizationService { get; }
        protected UserManager<IdentityUser> UserManager { get; }

        public DI_BasePageModel(
            ApplicationDbContext context,
            IAuthorizationService authorizationService,
            UserManager<IdentityUser> userManager) : base()
        {
            Context = context;
            UserManager = userManager;
            AuthorizationService = authorizationService;
        } 
    }
}

Der vorangehende Code:

  • Sie fügt den IAuthorizationService-Dienst für den Zugriff auf die Autorisierungshandler hinzu.
  • Sie fügt den IdentityUserManager-Dienst hinzu.
  • Fügen Sie die ApplicationDbContext hinzu.

Aktualisieren von CreateModel

Aktualisieren Sie das Modell für die Seitenerstellung:

  • Konstruktor zur Verwendung der DI_BasePageModel-Basisklasse.
  • OnPostAsync-Methode für:
    • Fügen Sie dem Contact-Modell die Benutzer-ID hinzu.
    • Rufen Sie den Autorisierungshandler auf, um zu überprüfen, ob Benutzer*innen über die Berechtigung zum Erstellen von Kontakten verfügen.
using ContactManager.Authorization;
using ContactManager.Data;
using ContactManager.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;

namespace ContactManager.Pages.Contacts
{
    public class CreateModel : DI_BasePageModel
    {
        public CreateModel(
            ApplicationDbContext context,
            IAuthorizationService authorizationService,
            UserManager<IdentityUser> userManager)
            : base(context, authorizationService, userManager)
        {
        }

        public IActionResult OnGet()
        {
            return Page();
        }

        [BindProperty]
        public Contact Contact { get; set; }

        public async Task<IActionResult> OnPostAsync()
        {
            if (!ModelState.IsValid)
            {
                return Page();
            }

            Contact.OwnerID = UserManager.GetUserId(User);

            var isAuthorized = await AuthorizationService.AuthorizeAsync(
                                                        User, Contact,
                                                        ContactOperations.Create);
            if (!isAuthorized.Succeeded)
            {
                return Forbid();
            }

            Context.Contact.Add(Contact);
            await Context.SaveChangesAsync();

            return RedirectToPage("./Index");
        }
    }
}

Aktualisieren von IndexModel

Aktualisieren Sie die OnGetAsync-Methode, damit allgemeinen Benutzer*innen nur genehmigte Kontakte angezeigt werden:

public class IndexModel : DI_BasePageModel
{
    public IndexModel(
        ApplicationDbContext context,
        IAuthorizationService authorizationService,
        UserManager<IdentityUser> userManager)
        : base(context, authorizationService, userManager)
    {
    }

    public IList<Contact> Contact { get; set; }

    public async Task OnGetAsync()
    {
        var contacts = from c in Context.Contact
                       select c;

        var isAuthorized = User.IsInRole(Constants.ContactManagersRole) ||
                           User.IsInRole(Constants.ContactAdministratorsRole);

        var currentUserId = UserManager.GetUserId(User);

        // Only approved contacts are shown UNLESS you're authorized to see them
        // or you are the owner.
        if (!isAuthorized)
        {
            contacts = contacts.Where(c => c.Status == ContactStatus.Approved
                                        || c.OwnerID == currentUserId);
        }

        Contact = await contacts.ToListAsync();
    }
}

Aktualisieren von EditModel

Fügen Sie einen Autorisierungshandler hinzu, um zu überprüfen, ob Benutzer*innen Besitzer*in des Kontakts sind. Da die Ressourcenautorisierung überprüft wird, reicht das [Authorize]-Attribut nicht aus. Die App hat keinen Zugriff auf die Ressource, wenn Attribute ausgewertet werden. Die ressourcenbasierte Autorisierung muss zwingend sein. Überprüfungen müssen durchgeführt werden, sobald die App Zugriff auf die Ressource hat, entweder durch Laden in das Seitenmodell oder durch Laden innerhalb des Handlers selbst. Sie greifen häufig auf die Ressource zu, indem Sie den Ressourcenschlüssel übergeben.

public class EditModel : DI_BasePageModel
{
    public EditModel(
        ApplicationDbContext context,
        IAuthorizationService authorizationService,
        UserManager<IdentityUser> userManager)
        : base(context, authorizationService, userManager)
    {
    }

    [BindProperty]
    public Contact Contact { get; set; }

    public async Task<IActionResult> OnGetAsync(int id)
    {
        Contact? contact = await Context.Contact.FirstOrDefaultAsync(
                                                         m => m.ContactId == id);
        if (contact == null)
        {
            return NotFound();
        }

        Contact = contact;

        var isAuthorized = await AuthorizationService.AuthorizeAsync(
                                                  User, Contact,
                                                  ContactOperations.Update);
        if (!isAuthorized.Succeeded)
        {
            return Forbid();
        }

        return Page();
    }

    public async Task<IActionResult> OnPostAsync(int id)
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        // Fetch Contact from DB to get OwnerID.
        var contact = await Context
            .Contact.AsNoTracking()
            .FirstOrDefaultAsync(m => m.ContactId == id);

        if (contact == null)
        {
            return NotFound();
        }

        var isAuthorized = await AuthorizationService.AuthorizeAsync(
                                                 User, contact,
                                                 ContactOperations.Update);
        if (!isAuthorized.Succeeded)
        {
            return Forbid();
        }

        Contact.OwnerID = contact.OwnerID;

        Context.Attach(Contact).State = EntityState.Modified;

        if (Contact.Status == ContactStatus.Approved)
        {
            // If the contact is updated after approval, 
            // and the user cannot approve,
            // set the status back to submitted so the update can be
            // checked and approved.
            var canApprove = await AuthorizationService.AuthorizeAsync(User,
                                    Contact,
                                    ContactOperations.Approve);

            if (!canApprove.Succeeded)
            {
                Contact.Status = ContactStatus.Submitted;
            }
        }

        await Context.SaveChangesAsync();

        return RedirectToPage("./Index");
    }
}

Aktualisieren von DeleteModel

Aktualisieren Sie das Modell zum Löschen von Seiten, um mithilfe des Autorisierungshandlers zu überprüfen, ob Benutzer*innen über die Berechtigung zum Löschen des Kontakts verfügen.

public class DeleteModel : DI_BasePageModel
{
    public DeleteModel(
        ApplicationDbContext context,
        IAuthorizationService authorizationService,
        UserManager<IdentityUser> userManager)
        : base(context, authorizationService, userManager)
    {
    }

    [BindProperty]
    public Contact Contact { get; set; }

    public async Task<IActionResult> OnGetAsync(int id)
    {
        Contact? _contact = await Context.Contact.FirstOrDefaultAsync(
                                             m => m.ContactId == id);

        if (_contact == null)
        {
            return NotFound();
        }
        Contact = _contact;

        var isAuthorized = await AuthorizationService.AuthorizeAsync(
                                                 User, Contact,
                                                 ContactOperations.Delete);
        if (!isAuthorized.Succeeded)
        {
            return Forbid();
        }

        return Page();
    }

    public async Task<IActionResult> OnPostAsync(int id)
    {
        var contact = await Context
            .Contact.AsNoTracking()
            .FirstOrDefaultAsync(m => m.ContactId == id);

        if (contact == null)
        {
            return NotFound();
        }

        var isAuthorized = await AuthorizationService.AuthorizeAsync(
                                                 User, contact,
                                                 ContactOperations.Delete);
        if (!isAuthorized.Succeeded)
        {
            return Forbid();
        }

        Context.Contact.Remove(contact);
        await Context.SaveChangesAsync();

        return RedirectToPage("./Index");
    }
}

Einschleusen des Autorisierungsdiensts in die Ansichten

Derzeit werden auf der Benutzeroberfläche Links zum Bearbeiten und Löschen für Kontakte angezeigt, die von Benutzer*innen nicht geändert werden können.

Schleusen Sie den Autorisierungsdiensts in die Datei Pages/_ViewImports.cshtml, damit er in allen Ansichten verfügbar ist:

@using Microsoft.AspNetCore.Identity
@using ContactManager
@using ContactManager.Data
@namespace ContactManager.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@using ContactManager.Authorization;
@using Microsoft.AspNetCore.Authorization
@using ContactManager.Models
@inject IAuthorizationService AuthorizationService

Das obige Markup fügt mehrere using-Anweisungen hinzu.

Aktualisieren Sie die Links zum Bearbeiten und Löschen in Pages/Contacts/Index.cshtml so, dass sie nur für Benutzer*innen mit den entsprechenden Berechtigungen gerendert werden:

@page
@model ContactManager.Pages.Contacts.IndexModel

@{
    ViewData["Title"] = "Index";
}

<h1>Index</h1>

<p>
    <a asp-page="Create">Create New</a>
</p>
<table class="table">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.Contact[0].Name)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Contact[0].Address)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Contact[0].City)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Contact[0].State)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Contact[0].Zip)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Contact[0].Email)
            </th>
             <th>
                @Html.DisplayNameFor(model => model.Contact[0].Status)
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
@foreach (var item in Model.Contact) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Name)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Address)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.City)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.State)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Zip)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Email)
            </td>
                           <td>
                    @Html.DisplayFor(modelItem => item.Status)
                </td>
                <td>
                    @if ((await AuthorizationService.AuthorizeAsync(
                     User, item,
                     ContactOperations.Update)).Succeeded)
                    {
                        <a asp-page="./Edit" asp-route-id="@item.ContactId">Edit</a>
                        <text> | </text>
                    }

                    <a asp-page="./Details" asp-route-id="@item.ContactId">Details</a>

                    @if ((await AuthorizationService.AuthorizeAsync(
                     User, item,
                     ContactOperations.Delete)).Succeeded)
                    {
                        <text> | </text>
                        <a asp-page="./Delete" asp-route-id="@item.ContactId">Delete</a>
                    }
                </td>
            </tr>
        }
    </tbody>
</table>

Warnung

Das Ausblenden von Links für Benutzer*innen, die keine Berechtigung zum Ändern von Daten haben, stellt keinen Schutz für die App dar. Durch das Ausblenden der Links wird die App benutzerfreundlicher, da nur gültige Links angezeigt werden. Benutzer*innen können die generierten URLs hacken, um Bearbeitungs- und Löschvorgänge für Daten aufzurufen, die sie nicht besitzen. Die Razor-Seite oder der Controller muss Zugriffsprüfungen erzwingen, um die Daten zu schützen.

Aktualisieren von Details

Aktualisieren Sie die Detailansicht, damit Manager*innen Kontakte genehmigen oder ablehnen können:

        @*Preceding markup omitted for brevity.*@
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Contact.Email)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Contact.Email)
        </dd>
    <dt>
            @Html.DisplayNameFor(model => model.Contact.Status)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.Contact.Status)
        </dd>
    </dl>
</div>

@if (Model.Contact.Status != ContactStatus.Approved)
{
    @if ((await AuthorizationService.AuthorizeAsync(
     User, Model.Contact, ContactOperations.Approve)).Succeeded)
    {
        <form style="display:inline;" method="post">
            <input type="hidden" name="id" value="@Model.Contact.ContactId" />
            <input type="hidden" name="status" value="@ContactStatus.Approved" />
            <button type="submit" class="btn btn-xs btn-success">Approve</button>
        </form>
    }
}

@if (Model.Contact.Status != ContactStatus.Rejected)
{
    @if ((await AuthorizationService.AuthorizeAsync(
     User, Model.Contact, ContactOperations.Reject)).Succeeded)
    {
        <form style="display:inline;" method="post">
            <input type="hidden" name="id" value="@Model.Contact.ContactId" />
            <input type="hidden" name="status" value="@ContactStatus.Rejected" />
            <button type="submit" class="btn btn-xs btn-danger">Reject</button>
        </form>
    }
}

<div>
    @if ((await AuthorizationService.AuthorizeAsync(
         User, Model.Contact,
         ContactOperations.Update)).Succeeded)
    {
        <a asp-page="./Edit" asp-route-id="@Model.Contact.ContactId">Edit</a>
        <text> | </text>
    }
    <a asp-page="./Index">Back to List</a>
</div>

Aktualisieren des Modells für Detailseiten

public class DetailsModel : DI_BasePageModel
{
    public DetailsModel(
        ApplicationDbContext context,
        IAuthorizationService authorizationService,
        UserManager<IdentityUser> userManager)
        : base(context, authorizationService, userManager)
    {
    }

    public Contact Contact { get; set; }

    public async Task<IActionResult> OnGetAsync(int id)
    {
        Contact? _contact = await Context.Contact.FirstOrDefaultAsync(m => m.ContactId == id);

        if (_contact == null)
        {
            return NotFound();
        }
        Contact = _contact;

        var isAuthorized = User.IsInRole(Constants.ContactManagersRole) ||
                           User.IsInRole(Constants.ContactAdministratorsRole);

        var currentUserId = UserManager.GetUserId(User);

        if (!isAuthorized
            && currentUserId != Contact.OwnerID
            && Contact.Status != ContactStatus.Approved)
        {
            return Forbid();
        }

        return Page();
    }

    public async Task<IActionResult> OnPostAsync(int id, ContactStatus status)
    {
        var contact = await Context.Contact.FirstOrDefaultAsync(
                                                  m => m.ContactId == id);

        if (contact == null)
        {
            return NotFound();
        }

        var contactOperation = (status == ContactStatus.Approved)
                                                   ? ContactOperations.Approve
                                                   : ContactOperations.Reject;

        var isAuthorized = await AuthorizationService.AuthorizeAsync(User, contact,
                                    contactOperation);
        if (!isAuthorized.Succeeded)
        {
            return Forbid();
        }
        contact.Status = status;
        Context.Contact.Update(contact);
        await Context.SaveChangesAsync();

        return RedirectToPage("./Index");
    }
}

Hinzufügen oder Entfernen von Benutzer*innen in einer Rolle

Unter diesem Problem finden Sie weitere Informationen zu:

  • Entfernen von Berechtigungen für Benutzer*innen. Beispiel: Stummschalten einzelner Benutzer*innen in einer Chat-App.
  • Hinzufügen von Berechtigungen für Benutzer*innen

Unterschiede zwischen Aufforderung und Unterbindung

Diese App legt die Standardrichtlinie so fest, dass authentifizierte Benutzer*innen erforderlich sind. Der folgende Code erlaubt anonyme Benutzer*innen. Anonyme Benutzer*innen dürfen die Unterschiede zwischen Aufforderung und Unterbindung anzeigen.

[AllowAnonymous]
public class Details2Model : DI_BasePageModel
{
    public Details2Model(
        ApplicationDbContext context,
        IAuthorizationService authorizationService,
        UserManager<IdentityUser> userManager)
        : base(context, authorizationService, userManager)
    {
    }

    public Contact Contact { get; set; }

    public async Task<IActionResult> OnGetAsync(int id)
    {
        Contact? _contact = await Context.Contact.FirstOrDefaultAsync(m => m.ContactId == id);

        if (_contact == null)
        {
            return NotFound();
        }
        Contact = _contact;

        if (!User.Identity!.IsAuthenticated)
        {
            return Challenge();
        }

        var isAuthorized = User.IsInRole(Constants.ContactManagersRole) ||
                           User.IsInRole(Constants.ContactAdministratorsRole);

        var currentUserId = UserManager.GetUserId(User);

        if (!isAuthorized
            && currentUserId != Contact.OwnerID
            && Contact.Status != ContactStatus.Approved)
        {
            return Forbid();
        }

        return Page();
    }
}

Für den Code oben gilt:

  • Wenn Benutzer*innen nicht authentifiziert sind, wird ein ChallengeResult zurückgegeben. Wenn ein ChallengeResult zurückgegeben wird, werden die Benutzer*innen zur Anmeldeseite umgeleitet.
  • Wenn die Benutzer*innen authentifiziert, aber nicht autorisiert sind, wird ein ForbidResult zurückgegeben. Wenn ein ForbidResult zurückgegeben wird, werden die Benutzer*innen zur Seite „Zugriff verweigert“ umgeleitet.

Testen der fertigen App

Warnung

In diesem Artikel wird das Tool Geheimnis-Manager verwendet, um das Kennwort für die Seeded-Benutzerkonten zu speichern. Das Tool „Geheimnis-Manager“ wird zum Speichern vertraulicher Daten während der lokalen Entwicklung verwendet. Informationen zu Authentifizierungsverfahren, die verwendet werden können, wenn eine App in einer Test- oder Produktionsumgebung bereitgestellt wird, finden Sie unter Sichere Authentifizierungsflows.

Falls Sie noch kein Kennwort für registrierte Benutzerkonten festgelegt haben, verwenden Sie das Tool Geheimnis-Manager, um ein Kennwort festzulegen:

  • Verwenden Sie ein sicheres Kennwort:

    • Es muss mindestens 12 Zeichen enthalten, aber 14 oder mehr sind besser.
    • Eine Kombination aus Großbuchstaben, Kleinbuchstaben, Zahlen und Symbolen.
    • Kein Wort, das in einem Wörterbuch oder im Namen einer Person, eines Zeichens, eines Produkts oder einer Organisation gefunden werden kann.
    • Es muss sich deutlich von früheren Kennwörtern unterscheiden.
    • Für Sie leicht zu merken, für andere jedoch schwer zu erraten. Erwägen Sie die Verwendung eines einprägsamen Ausdrucks wie „6MonkeysRLooking^“.
  • Führen Sie den folgenden Befehl im Ordner des Projekts aus. Dabei ist <PW> das Kennwort:

    dotnet user-secrets set SeedUserPW <PW>
    

Wenn die App über Kontakte verfügt:

  • Löschen Sie alle Datensätze in der Tabelle Contact.
  • Starten Sie die App neu, um das Seeding der Datenbank auszuführen.

Eine einfache Möglichkeit, die fertige App zu testen, besteht darin, drei verschiedene Browser (oder Inkognito-/InPrivate-Sitzungen) zu starten. Registrieren Sie in einem Browser eine*n neue*n Benutzer*in (z. B. test@example.com). Melden Sie sich bei jedem Browser als eine*m andere*n Benutzer*in an. Überprüfen Sie die folgenden Vorgänge:

  • Registrierte Benutzer*innen können alle genehmigten Kontaktdaten anzeigen.
  • Registrierte Benutzer*innen können ihre eigenen Daten bearbeiten und löschen.
  • Manager*innen können Kontaktdaten genehmigen und ablehnen. In der Ansicht Details werden die Schaltflächen Approve (Genehmigen) und Reject (Ablehnen) angezeigt.
  • Administrator*innen können alle Daten genehmigen/ablehnen und bearbeiten/löschen.
Benutzer Genehmigen oder Ablehnen von Kontakten Tastatur
test@example.com Nein Bearbeiten und löschen Sie ihre Daten.
manager@contoso.com Ja Bearbeiten und löschen Sie ihre Daten.
admin@contoso.com Ja Bearbeiten und löschen Sie alle Daten.

Erstellen Sie einen Kontakt im Administratorbrowser. Kopieren Sie die URL zum Löschen und Bearbeiten vom Administratorkontakt. Fügen Sie diese Links in den Browser von Testbenutzer*innen ein, um zu bestätigen, dass die Testbenutzer*innen diese Vorgänge nicht ausführen können.

Erstellen der Starter-App

  • Erstellen einer Razor Pages-App mit dem Namen „ContactManager“

    • Erstellen Sie die App mit einzelnen Benutzerkonten.
    • Geben Sie ihr den Namen „ContactManager“, damit der Namespace mit dem im Beispiel verwendeten Namespace übereinstimmt.
    • -uld gibt LocalDB anstelle von SQLite an.
    dotnet new webapp -o ContactManager -au Individual -uld
    
  • Fügen Sie die Datei Models/Contact.cs hinzu: secure-data\samples\starter6\ContactManager\Models\Contact.cs

    using System.ComponentModel.DataAnnotations;
    
    namespace ContactManager.Models
    {
        public class Contact
        {
            public int ContactId { get; set; }
            public string? Name { get; set; }
            public string? Address { get; set; }
            public string? City { get; set; }
            public string? State { get; set; }
            public string? Zip { get; set; }
            [DataType(DataType.EmailAddress)]
            public string? Email { get; set; }
        }
    }
    
  • Erstellen Sie ein Gerüst für das Contact-Modell.

  • Erstellen Sie eine anfängliche Migration, und aktualisieren Sie die Datenbank:

dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design
dotnet tool install -g dotnet-aspnet-codegenerator
dotnet-aspnet-codegenerator razorpage -m Contact -udl -dc ApplicationDbContext -outDir Pages\Contacts --referenceScriptLibraries
dotnet ef database drop -f
dotnet ef migrations add initial
dotnet ef database update

Hinweis

Standardmäßig stellt die Architektur der zu installierenden .NET-Binärdateien die derzeit ausgeführte Betriebssystemarchitektur dar. Informationen zum Angeben einer anderen Betriebssystemarchitektur finden Sie unter dotnet tool install, --arch option. Weitere Informationen finden Sie unter GitHub Issue dotnet/docs #29262.

  • Aktualisieren Sie den ContactManager-Anker in der Datei Pages/Shared/_Layout.cshtml:

    <a class="nav-link text-dark" asp-area="" asp-page="/Contacts/Index">Contact Manager</a>
    
  • Testen der App durch Erstellen, Bearbeiten und Löschen eines Kontakts

Ausführen eines Seedings für die Datenbank

Fügen Sie im Ordner Data die folgende SeedData-Klasse hinzu:

using ContactManager.Models;
using Microsoft.EntityFrameworkCore;

// dotnet aspnet-codegenerator razorpage -m Contact -dc ApplicationDbContext -udl -outDir Pages\Contacts --referenceScriptLibraries

namespace ContactManager.Data
{
    public static class SeedData
    {
        public static async Task Initialize(IServiceProvider serviceProvider, string testUserPw="")
        {
            using (var context = new ApplicationDbContext(
                serviceProvider.GetRequiredService<DbContextOptions<ApplicationDbContext>>()))
            {
                SeedDB(context, testUserPw);
            }
        }

        public static void SeedDB(ApplicationDbContext context, string adminID)
        {
            if (context.Contact.Any())
            {
                return;   // DB has been seeded
            }

            context.Contact.AddRange(
                new Contact
                {
                    Name = "Debra Garcia",
                    Address = "1234 Main St",
                    City = "Redmond",
                    State = "WA",
                    Zip = "10999",
                    Email = "debra@example.com"
                },
                new Contact
                {
                    Name = "Thorsten Weinrich",
                    Address = "5678 1st Ave W",
                    City = "Redmond",
                    State = "WA",
                    Zip = "10999",
                    Email = "thorsten@example.com"
                },
                new Contact
                {
                    Name = "Yuhong Li",
                    Address = "9012 State st",
                    City = "Redmond",
                    State = "WA",
                    Zip = "10999",
                    Email = "yuhong@example.com"
                },
                new Contact
                {
                    Name = "Jon Orton",
                    Address = "3456 Maple St",
                    City = "Redmond",
                    State = "WA",
                    Zip = "10999",
                    Email = "jon@example.com"
                },
                new Contact
                {
                    Name = "Diliana Alexieva-Bosseva",
                    Address = "7890 2nd Ave E",
                    City = "Redmond",
                    State = "WA",
                    Zip = "10999",
                    Email = "diliana@example.com"
                }
             );
            context.SaveChanges();
        }

    }
}

Rufen Sie SeedData.Initialize von Program.cs auf:

using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using ContactManager.Data;

var builder = WebApplication.CreateBuilder(args);

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>();
builder.Services.AddRazorPages();

var app = builder.Build();

using (var scope = app.Services.CreateScope())
{
    var services = scope.ServiceProvider;

    await SeedData.Initialize(services);
}

if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

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

app.MapRazorPages();

app.Run();

Testen Sie, ob die App das Seeding für die Datenbank ausgeführt hat. Wenn die Kontaktdatenbank Zeilen enthält, wird die Seed-Methode nicht ausgeführt.

In diesem Tutorial wird gezeigt, wie Sie eine ASP.NET Core-Web-App mit Benutzerdaten erstellen, die durch Autorisierung geschützt sind. Es enthält eine Liste der Kontakte, die authentifizierte (registrierte) Benutzer*innen erstellt haben. Es gibt drei Sicherheitsgruppen:

  • Registrierte Benutzer*innen können alle genehmigten Daten anzeigen und ihre eigenen Daten bearbeiten/löschen.
  • Manager*innen können Kontaktdaten genehmigen oder ablehnen. Nur genehmigte Kontakte sind für Benutzer*innen sichtbar.
  • Administrator*innen können alle Daten genehmigen/ablehnen und bearbeiten/löschen.

Die Abbildungen in diesem Dokument stimmen nicht genau mit den neuesten Vorlagen überein.

In der folgenden Abbildung ist Benutzer Rick (rick@example.com) angemeldet. Rick werden nur genehmigte Kontakte und Links zum Bearbeiten/Löschen/Neu erstellen für seine Kontakte angezeigt. Nur beim letzten Datensatz, der von Rick erstellt wurde, werden die Links zum Bearbeiten und Löschen angezeigt. Anderen Benutzer*innen wird der letzte Datensatz erst angezeigt, wenn sein Status durch Manager*innen oder Administrator*innen in „Genehmigt“ geändert wird.

Screenshot des angemeldeten Benutzers Rick

In der folgenden Abbildung ist manager@contoso.com mit der Rolle „Manager*in“ angemeldet:

Screenshot des angemeldeten Kontos manager@contoso.com

Die folgende Abbildung zeigt die Detailansicht eines Kontakts bei Manager*innen:

Ansicht eines Kontakts für Manager*innen

Die Schaltflächen Genehmigen und Ablehnen werden nur Manager*innen und Administrator*innen angezeigt.

In der folgenden Abbildung ist admin@contoso.com mit der Rolle „Administrator*in“ angemeldet:

Screenshot des angemeldeten Kontos admin@contoso.com

Administrator*innen verfügen über alle Berechtigungen. Sie können jeden Kontakt lesen, bearbeiten oder löschen und die Status von Kontakten ändern.

Die App wurde durch Gerüstbau des folgenden Contact-Modells erstellt:

public class Contact
{
    public int ContactId { get; set; }
    public string Name { get; set; }
    public string Address { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string Zip { get; set; }
    [DataType(DataType.EmailAddress)]
    public string Email { get; set; }
}

Das Beispiel enthält die folgenden Autorisierungshandler:

  • ContactIsOwnerAuthorizationHandler stellt sicher, dass Benutzer*innen nur ihre eigenen Daten bearbeiten können.
  • ContactManagerAuthorizationHandler ermöglicht Manager*innen, Kontakte zu genehmigen oder abzulehnen.
  • ContactAdministratorsAuthorizationHandler ermöglicht Administrator*innen Folgendes:
    • Genehmigen oder Ablehnen von Kontakten
    • Bearbeiten und Löschen von Kontakten

Voraussetzungen

Dieses Tutorial ist für Fortgeschrittene konzipiert. Sie sollten mit den folgenden Punkten vertraut sein:

Die Starter-App und die fertige App

Laden Sie die fertige App herunter. Testen Sie die fertige App, um sich mit den Sicherheitsfeatures vertraut zu machen.

Die Starter-App

Laden Sie die Starter-App herunter.

Führen Sie die App aus, tippen Sie auf den Link ContactManager, und überprüfen Sie, ob Sie einen Kontakt erstellen, bearbeiten und löschen können. Informationen zum Erstellen der Starter-App finden Sie unter Erstellen der Starter-App.

Schützen von Benutzerdaten

Die folgenden Abschnitte enthalten alle wichtigen Schritte zum Erstellen der App zum Schutz von Benutzerdaten. Es ist möglicherweise hilfreich, sich das fertige Projekt anzusehen.

Verknüpfen von Kontaktdaten mit Benutzer*innen

Verwenden Sie die ASP.NET Identity-Benutzer-ID, um sicherzustellen, dass Benutzer*innen ihre eigenen Daten bearbeiten können, aber nicht die Daten anderer Benutzer*innen. Fügen Sie OwnerID und ContactStatus dem Contact-Modell hinzu:

public class Contact
{
    public int ContactId { get; set; }

    // user ID from AspNetUser table.
    public string OwnerID { get; set; }

    public string Name { get; set; }
    public string Address { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string Zip { get; set; }
    [DataType(DataType.EmailAddress)]
    public string Email { get; set; }

    public ContactStatus Status { get; set; }
}

public enum ContactStatus
{
    Submitted,
    Approved,
    Rejected
}

OwnerID ist die Benutzer-ID aus der Tabelle AspNetUser in der Identity-Datenbank. Das Feld Status bestimmt, ob ein Kontakt für allgemeine Benutzer*innen sichtbar ist.

Erstellen Sie eine neue Migration, und aktualisieren Sie die Datenbank:

dotnet ef migrations add userID_Status
dotnet ef database update

Hinzufügen von Rollendiensten in Identity

Fügen Sie AddRoles an, um Rollendienste hinzuzufügen:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(
            Configuration.GetConnectionString("DefaultConnection")));
    services.AddDefaultIdentity<IdentityUser>(
        options => options.SignIn.RequireConfirmedAccount = true)
        .AddRoles<IdentityRole>()
        .AddEntityFrameworkStores<ApplicationDbContext>();

Anfordern authentifizierter Benutzer*innen

Legen Sie die Fallbackauthentifizierungsrichtlinie so fest, dass Benutzer*innen authentifiziert werden müssen:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(
            Configuration.GetConnectionString("DefaultConnection")));
    services.AddDefaultIdentity<IdentityUser>(
        options => options.SignIn.RequireConfirmedAccount = true)
        .AddRoles<IdentityRole>()
        .AddEntityFrameworkStores<ApplicationDbContext>();

    services.AddRazorPages();

    services.AddAuthorization(options =>
    {
        options.FallbackPolicy = new AuthorizationPolicyBuilder()
            .RequireAuthenticatedUser()
            .Build();
    });

Der oben hervorgehobene Code legt die Fallbackauthentifizierungsrichtlinie fest. Die Fallbackauthentifizierungsrichtlinie erfordert, dass alle Benutzer*innen authentifiziert werden, mit Ausnahme von Razor Pages, Controllern oder Aktionsmethoden mit einem Authentifizierungsattribut. Beispielsweise verwenden Razor Pages, Controller oder Aktionsmethoden mit [AllowAnonymous] oder [Authorize(PolicyName="MyPolicy")] das angewandte Authentifizierungsattribut anstelle der Fallbackauthentifizierungsrichtlinie.

RequireAuthenticatedUser fügt der aktuellen Instanz DenyAnonymousAuthorizationRequirement hinzu. Dadurch wird die Authentifizierung des aktuellen Benutzers erzwungen.

Die Fallbackauthentifizierungsrichtlinie:

  • wird auf alle Anforderungen angewandt, die nicht explizit eine Authentifizierungsrichtlinie angeben. Für Anforderungen, die über Endpunktrouting bereitgestellt werden, umfasst dies alle Endpunkte, die kein Autorisierungsattribut aufweisen. Für Anforderungen, die von anderer Middleware nach der Autorisierungsmiddleware bereitgestellt werden (z. B. statischen Dateien) wird die Richtlinie auf alle Anforderungen angewandt.

Durch Festlegen der Fallbackauthentifizierungsrichtlinie, durch die Benutzer*innen authentifiziert werden müssen, werden neu hinzugefügte Razor-Seiten und Controller geschützt. Wenn standardmäßig eine Authentifizierung erzwungen wird, ist dies sicherer, als sich darauf zu verlassen, dass neue Controller und Razor Pages das [Authorize]-Attribut aufweisen.

Die AuthorizationOptions-Klasse enthält auch AuthorizationOptions.DefaultPolicy. DefaultPolicy ist die Richtlinie, die mit dem [Authorize]-Attribut verwendet wird, wenn keine Richtlinie angegeben wurde. [Authorize] enthält im Gegensatz zu [Authorize(PolicyName="MyPolicy")] keine benannte Richtlinie.

Weitere Informationen zu Richtlinien finden Sie unter Richtlinienbasierte Autorisierung in ASP.NET Core.

Eine alternative Möglichkeit für MVC-Controller und Razor Pages zum Erzwingen der Authentifizierung aller Benutzer*innen ist das Hinzufügen eines Autorisierungsfilters:

public void ConfigureServices(IServiceCollection services)
{

    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(
            Configuration.GetConnectionString("DefaultConnection")));
    services.AddDefaultIdentity<IdentityUser>(
        options => options.SignIn.RequireConfirmedAccount = true)
        .AddRoles<IdentityRole>()
        .AddEntityFrameworkStores<ApplicationDbContext>();

    services.AddRazorPages();

    services.AddControllers(config =>
    {
        // using Microsoft.AspNetCore.Mvc.Authorization;
        // using Microsoft.AspNetCore.Authorization;
        var policy = new AuthorizationPolicyBuilder()
                         .RequireAuthenticatedUser()
                         .Build();
        config.Filters.Add(new AuthorizeFilter(policy));
    });

Im obigen Code wird ein Autorisierungsfilter verwendet, der die Fallbackrichtlinie auf die Verwendung des Endpunktroutings festlegt. Das Festlegen der Fallbackrichtlinie ist die bevorzugte Methode, um die Authentifizierung aller Benutzer*innen zu erzwingen.

Fügen Sie AllowAnonymous den Seiten Index und Privacy hinzu, damit anonyme Benutzer*innen vor der Registrierung Informationen zur Website abfragen können:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;

namespace ContactManager.Pages
{
    [AllowAnonymous]
    public class IndexModel : PageModel
    {
        private readonly ILogger<IndexModel> _logger;

        public IndexModel(ILogger<IndexModel> logger)
        {
            _logger = logger;
        }

        public void OnGet()
        {

        }
    }
}

Konfigurieren des Testkontos

Die SeedData-Klasse erstellt zwei Konten: „administrator“ und „manager“. Verwenden Sie das Tool Geheimnis-Manager, um ein Kennwort für diese Konten festzulegen. Legen Sie das Kennwort aus dem Projektverzeichnis (das Verzeichnis mit Program.cs) fest:

dotnet user-secrets set SeedUserPW <PW>

Wenn kein sicheres Kennwort angegeben ist, wird beim Aufrufen von SeedData.Initialize eine Ausnahme ausgelöst.

Aktualisieren Sie Main, um das Testkennwort zu verwenden:

public class Program
{
    public static void Main(string[] args)
    {
        var host = CreateHostBuilder(args).Build();

        using (var scope = host.Services.CreateScope())
        {
            var services = scope.ServiceProvider;

            try
            {
                var context = services.GetRequiredService<ApplicationDbContext>();
                context.Database.Migrate();

                // requires using Microsoft.Extensions.Configuration;
                var config = host.Services.GetRequiredService<IConfiguration>();
                // Set password with the Secret Manager tool.
                // dotnet user-secrets set SeedUserPW <pw>

                var testUserPw = config["SeedUserPW"];

                SeedData.Initialize(services, testUserPw).Wait();
            }
            catch (Exception ex)
            {
                var logger = services.GetRequiredService<ILogger<Program>>();
                logger.LogError(ex, "An error occurred seeding the DB.");
            }
        }

        host.Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

Erstellen der Testkonten und Aktualisieren der Kontakte

Aktualisieren Sie die Initialize-Methode in der SeedData-Klasse, um die Testkonten zu erstellen:

public static async Task Initialize(IServiceProvider serviceProvider, string testUserPw)
{
    using (var context = new ApplicationDbContext(
        serviceProvider.GetRequiredService<DbContextOptions<ApplicationDbContext>>()))
    {
        // For sample purposes seed both with the same password.
        // Password is set with the following:
        // dotnet user-secrets set SeedUserPW <pw>
        // The admin user can do anything

        var adminID = await EnsureUser(serviceProvider, testUserPw, "admin@contoso.com");
        await EnsureRole(serviceProvider, adminID, Constants.ContactAdministratorsRole);

        // allowed user can create and edit contacts that they create
        var managerID = await EnsureUser(serviceProvider, testUserPw, "manager@contoso.com");
        await EnsureRole(serviceProvider, managerID, Constants.ContactManagersRole);

        SeedDB(context, adminID);
    }
}

private static async Task<string> EnsureUser(IServiceProvider serviceProvider,
                                            string testUserPw, string UserName)
{
    var userManager = serviceProvider.GetService<UserManager<IdentityUser>>();

    var user = await userManager.FindByNameAsync(UserName);
    if (user == null)
    {
        user = new IdentityUser
        {
            UserName = UserName,
            EmailConfirmed = true
        };
        await userManager.CreateAsync(user, testUserPw);
    }

    if (user == null)
    {
        throw new Exception("The password is probably not strong enough!");
    }

    return user.Id;
}

private static async Task<IdentityResult> EnsureRole(IServiceProvider serviceProvider,
                                                              string uid, string role)
{
    var roleManager = serviceProvider.GetService<RoleManager<IdentityRole>>();

    if (roleManager == null)
    {
        throw new Exception("roleManager null");
    }

    IdentityResult IR;
    if (!await roleManager.RoleExistsAsync(role))
    {
        IR = await roleManager.CreateAsync(new IdentityRole(role));
    }

    var userManager = serviceProvider.GetService<UserManager<IdentityUser>>();

    //if (userManager == null)
    //{
    //    throw new Exception("userManager is null");
    //}

    var user = await userManager.FindByIdAsync(uid);

    if (user == null)
    {
        throw new Exception("The testUserPw password was probably not strong enough!");
    }

    IR = await userManager.AddToRoleAsync(user, role);

    return IR;
}

Fügen Sie den Kontakten die Administratorbenutzer-ID und ContactStatus hinzu. Legen Sie einen der Kontakte auf „Submitted“ (Übermittelt) und einen auf „Rejected“ (Abgelehnt) fest. Fügen Sie die Benutzer-ID und den Status allen Kontakten hinzu. Es wird nur ein Kontakt angezeigt:

public static void SeedDB(ApplicationDbContext context, string adminID)
{
    if (context.Contact.Any())
    {
        return;   // DB has been seeded
    }

    context.Contact.AddRange(
        new Contact
        {
            Name = "Debra Garcia",
            Address = "1234 Main St",
            City = "Redmond",
            State = "WA",
            Zip = "10999",
            Email = "debra@example.com",
            Status = ContactStatus.Approved,
            OwnerID = adminID
        },

Erstellen von Autorisierungshandlern für Besitzer*innen, Manager*innen und Administrator*innen

Erstellen Sie im Ordner ContactIsOwnerAuthorizationHandler eine -Klasse. Der ContactIsOwnerAuthorizationHandler überprüft, ob Benutzer*innen, die eine Ressource verwenden, Besitzer*in der Ressource sind.

using ContactManager.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization.Infrastructure;
using Microsoft.AspNetCore.Identity;
using System.Threading.Tasks;

namespace ContactManager.Authorization
{
    public class ContactIsOwnerAuthorizationHandler
                : AuthorizationHandler<OperationAuthorizationRequirement, Contact>
    {
        UserManager<IdentityUser> _userManager;

        public ContactIsOwnerAuthorizationHandler(UserManager<IdentityUser> 
            userManager)
        {
            _userManager = userManager;
        }

        protected override Task
            HandleRequirementAsync(AuthorizationHandlerContext context,
                                   OperationAuthorizationRequirement requirement,
                                   Contact resource)
        {
            if (context.User == null || resource == null)
            {
                return Task.CompletedTask;
            }

            // If not asking for CRUD permission, return.

            if (requirement.Name != Constants.CreateOperationName &&
                requirement.Name != Constants.ReadOperationName   &&
                requirement.Name != Constants.UpdateOperationName &&
                requirement.Name != Constants.DeleteOperationName )
            {
                return Task.CompletedTask;
            }

            if (resource.OwnerID == _userManager.GetUserId(context.User))
            {
                context.Succeed(requirement);
            }

            return Task.CompletedTask;
        }
    }
}

ContactIsOwnerAuthorizationHandler ruft context.Succeed auf, wenn der oder die aktuell authentifizierte Benutzer*in Kontaktbesitzer*in ist. Allgemeines zu Autorisierungshandlern:

  • Sie rufen context.Succeed auf, wenn die Anforderungen erfüllt sind.
  • Sie geben Task.CompletedTask zurück, wenn die Anforderungen nicht erfüllt sind. Die Rückgabe von Task.CompletedTask ohne einen vorherigen Aufruf von context.Success oder context.Fail stellt keinen Erfolg oder Fehler dar, sondern ermöglicht die Ausführung anderer Autorisierungshandler.

Wenn Sie Fehler explizit zurückgeben müssen, rufen Sie context.Fail auf.

Die App ermöglicht es Kontaktbesitzer*innen, ihre eigenen Daten zu bearbeiten, zu löschen und zu erstellen. ContactIsOwnerAuthorizationHandler muss den im Anforderungsparameter übergebenen Vorgang nicht überprüfen.

Erstellen eines Autorisierungshandlers für Manager*innen

Erstellen Sie im Ordner ContactManagerAuthorizationHandler eine -Klasse. Der ContactManagerAuthorizationHandler überprüft, ob Benutzer*innen, die diese Ressource verwenden, Manager*in sind. Nur Manager*innen können Inhaltsänderungen (neu oder geändert) genehmigen oder ablehnen.

using System.Threading.Tasks;
using ContactManager.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization.Infrastructure;
using Microsoft.AspNetCore.Identity;

namespace ContactManager.Authorization
{
    public class ContactManagerAuthorizationHandler :
        AuthorizationHandler<OperationAuthorizationRequirement, Contact>
    {
        protected override Task
            HandleRequirementAsync(AuthorizationHandlerContext context,
                                   OperationAuthorizationRequirement requirement,
                                   Contact resource)
        {
            if (context.User == null || resource == null)
            {
                return Task.CompletedTask;
            }

            // If not asking for approval/reject, return.
            if (requirement.Name != Constants.ApproveOperationName &&
                requirement.Name != Constants.RejectOperationName)
            {
                return Task.CompletedTask;
            }

            // Managers can approve or reject.
            if (context.User.IsInRole(Constants.ContactManagersRole))
            {
                context.Succeed(requirement);
            }

            return Task.CompletedTask;
        }
    }
}

Erstellen eines Autorisierungshandlers für Administrator*innen

Erstellen Sie im Ordner ContactAdministratorsAuthorizationHandler eine -Klasse. Der ContactAdministratorsAuthorizationHandler überprüft, ob Benutzer*innen, die diese Ressource verwenden, Administrator*in sind. Administrator*innen können alle Vorgänge ausführen.

using System.Threading.Tasks;
using ContactManager.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization.Infrastructure;

namespace ContactManager.Authorization
{
    public class ContactAdministratorsAuthorizationHandler
                    : AuthorizationHandler<OperationAuthorizationRequirement, Contact>
    {
        protected override Task HandleRequirementAsync(
                                              AuthorizationHandlerContext context,
                                    OperationAuthorizationRequirement requirement, 
                                     Contact resource)
        {
            if (context.User == null)
            {
                return Task.CompletedTask;
            }

            // Administrators can do anything.
            if (context.User.IsInRole(Constants.ContactAdministratorsRole))
            {
                context.Succeed(requirement);
            }

            return Task.CompletedTask;
        }
    }
}

Registrieren der Autorisierungshandler

Dienste, die Entity Framework Core verwenden, müssen mit bei der AddScoped registriert werden. Der ContactIsOwnerAuthorizationHandler verwendet ASP.NET Core Identity, das auf Entity Framework Core basiert. Registrieren Sie die Handler bei der Dienstsammlung, damit sie für den ContactsController über die Abhängigkeitsinjektion verfügbar sind. Fügen Sie am Ende von ConfigureServices folgenden Code hinzu:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(
            Configuration.GetConnectionString("DefaultConnection")));
    services.AddDefaultIdentity<IdentityUser>(
        options => options.SignIn.RequireConfirmedAccount = true)
        .AddRoles<IdentityRole>()
        .AddEntityFrameworkStores<ApplicationDbContext>();

    services.AddRazorPages();

    services.AddAuthorization(options =>
    {
        options.FallbackPolicy = new AuthorizationPolicyBuilder()
            .RequireAuthenticatedUser()
            .Build();
    });

    // Authorization handlers.
    services.AddScoped<IAuthorizationHandler,
                          ContactIsOwnerAuthorizationHandler>();

    services.AddSingleton<IAuthorizationHandler,
                          ContactAdministratorsAuthorizationHandler>();

    services.AddSingleton<IAuthorizationHandler,
                          ContactManagerAuthorizationHandler>();
}

ContactAdministratorsAuthorizationHandler und ContactManagerAuthorizationHandler werden als Singletons hinzugefügt. Sie sind Singletons, da sie das Entity Framework nicht verwenden und alle erforderlichen Informationen im Context-Parameter der HandleRequirementAsync-Methode enthalten sind.

Unterstützen der Autorisierung

In diesem Abschnitt aktualisieren Sie Razor Pages und fügen eine Betriebsanforderungenklasse hinzu.

Überprüfen der Betriebsanforderungsklasse für Kontakte

Überprüfen Sie die ContactOperations-Klasse. Diese Klasse enthält die Anforderungen, die von der App unterstützt werden:

using Microsoft.AspNetCore.Authorization.Infrastructure;

namespace ContactManager.Authorization
{
    public static class ContactOperations
    {
        public static OperationAuthorizationRequirement Create =   
          new OperationAuthorizationRequirement {Name=Constants.CreateOperationName};
        public static OperationAuthorizationRequirement Read = 
          new OperationAuthorizationRequirement {Name=Constants.ReadOperationName};  
        public static OperationAuthorizationRequirement Update = 
          new OperationAuthorizationRequirement {Name=Constants.UpdateOperationName}; 
        public static OperationAuthorizationRequirement Delete = 
          new OperationAuthorizationRequirement {Name=Constants.DeleteOperationName};
        public static OperationAuthorizationRequirement Approve = 
          new OperationAuthorizationRequirement {Name=Constants.ApproveOperationName};
        public static OperationAuthorizationRequirement Reject = 
          new OperationAuthorizationRequirement {Name=Constants.RejectOperationName};
    }

    public class Constants
    {
        public static readonly string CreateOperationName = "Create";
        public static readonly string ReadOperationName = "Read";
        public static readonly string UpdateOperationName = "Update";
        public static readonly string DeleteOperationName = "Delete";
        public static readonly string ApproveOperationName = "Approve";
        public static readonly string RejectOperationName = "Reject";

        public static readonly string ContactAdministratorsRole = 
                                                              "ContactAdministrators";
        public static readonly string ContactManagersRole = "ContactManagers";
    }
}

Erstellen einer Basisklasse für die Kontakte in Razor Pages

Erstellen Sie eine Basisklasse, die die in den Kontakten von Razor Pages verwendeten Dienste enthält. Die Basisklasse platziert den Initialisierungscode an einer Stelle:

using ContactManager.Data;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace ContactManager.Pages.Contacts
{
    public class DI_BasePageModel : PageModel
    {
        protected ApplicationDbContext Context { get; }
        protected IAuthorizationService AuthorizationService { get; }
        protected UserManager<IdentityUser> UserManager { get; }

        public DI_BasePageModel(
            ApplicationDbContext context,
            IAuthorizationService authorizationService,
            UserManager<IdentityUser> userManager) : base()
        {
            Context = context;
            UserManager = userManager;
            AuthorizationService = authorizationService;
        } 
    }
}

Der vorangehende Code:

  • Sie fügt den IAuthorizationService-Dienst für den Zugriff auf die Autorisierungshandler hinzu.
  • Sie fügt den IdentityUserManager-Dienst hinzu.
  • Fügen Sie die ApplicationDbContext hinzu.

Aktualisieren von CreateModel

Aktualisieren Sie den Konstruktor für das Seitenerstellungsmodell, um die DI_BasePageModel-Basisklasse zu verwenden:

public class CreateModel : DI_BasePageModel
{
    public CreateModel(
        ApplicationDbContext context,
        IAuthorizationService authorizationService,
        UserManager<IdentityUser> userManager)
        : base(context, authorizationService, userManager)
    {
    }

Aktualisieren Sie die CreateModel.OnPostAsync-Methode wie folgt:

  • Fügen Sie dem Contact-Modell die Benutzer-ID hinzu.
  • Rufen Sie den Autorisierungshandler auf, um zu überprüfen, ob Benutzer*innen über die Berechtigung zum Erstellen von Kontakten verfügen.
public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    Contact.OwnerID = UserManager.GetUserId(User);

    // requires using ContactManager.Authorization;
    var isAuthorized = await AuthorizationService.AuthorizeAsync(
                                                User, Contact,
                                                ContactOperations.Create);
    if (!isAuthorized.Succeeded)
    {
        return Forbid();
    }

    Context.Contact.Add(Contact);
    await Context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

Aktualisieren von IndexModel

Aktualisieren Sie die OnGetAsync-Methode, damit allgemeinen Benutzer*innen nur genehmigte Kontakte angezeigt werden:

public class IndexModel : DI_BasePageModel
{
    public IndexModel(
        ApplicationDbContext context,
        IAuthorizationService authorizationService,
        UserManager<IdentityUser> userManager)
        : base(context, authorizationService, userManager)
    {
    }

    public IList<Contact> Contact { get; set; }

    public async Task OnGetAsync()
    {
        var contacts = from c in Context.Contact
                       select c;

        var isAuthorized = User.IsInRole(Constants.ContactManagersRole) ||
                           User.IsInRole(Constants.ContactAdministratorsRole);

        var currentUserId = UserManager.GetUserId(User);

        // Only approved contacts are shown UNLESS you're authorized to see them
        // or you are the owner.
        if (!isAuthorized)
        {
            contacts = contacts.Where(c => c.Status == ContactStatus.Approved
                                        || c.OwnerID == currentUserId);
        }

        Contact = await contacts.ToListAsync();
    }
}

Aktualisieren von EditModel

Fügen Sie einen Autorisierungshandler hinzu, um zu überprüfen, ob Benutzer*innen Besitzer*in des Kontakts sind. Da die Ressourcenautorisierung überprüft wird, reicht das [Authorize]-Attribut nicht aus. Die App hat keinen Zugriff auf die Ressource, wenn Attribute ausgewertet werden. Die ressourcenbasierte Autorisierung muss zwingend sein. Überprüfungen müssen durchgeführt werden, sobald die App Zugriff auf die Ressource hat, entweder durch Laden in das Seitenmodell oder durch Laden innerhalb des Handlers selbst. Sie greifen häufig auf die Ressource zu, indem Sie den Ressourcenschlüssel übergeben.

public class EditModel : DI_BasePageModel
{
    public EditModel(
        ApplicationDbContext context,
        IAuthorizationService authorizationService,
        UserManager<IdentityUser> userManager)
        : base(context, authorizationService, userManager)
    {
    }

    [BindProperty]
    public Contact Contact { get; set; }

    public async Task<IActionResult> OnGetAsync(int id)
    {
        Contact = await Context.Contact.FirstOrDefaultAsync(
                                             m => m.ContactId == id);

        if (Contact == null)
        {
            return NotFound();
        }

        var isAuthorized = await AuthorizationService.AuthorizeAsync(
                                                  User, Contact,
                                                  ContactOperations.Update);
        if (!isAuthorized.Succeeded)
        {
            return Forbid();
        }

        return Page();
    }

    public async Task<IActionResult> OnPostAsync(int id)
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        // Fetch Contact from DB to get OwnerID.
        var contact = await Context
            .Contact.AsNoTracking()
            .FirstOrDefaultAsync(m => m.ContactId == id);

        if (contact == null)
        {
            return NotFound();
        }

        var isAuthorized = await AuthorizationService.AuthorizeAsync(
                                                 User, contact,
                                                 ContactOperations.Update);
        if (!isAuthorized.Succeeded)
        {
            return Forbid();
        }

        Contact.OwnerID = contact.OwnerID;

        Context.Attach(Contact).State = EntityState.Modified;

        if (Contact.Status == ContactStatus.Approved)
        {
            // If the contact is updated after approval, 
            // and the user cannot approve,
            // set the status back to submitted so the update can be
            // checked and approved.
            var canApprove = await AuthorizationService.AuthorizeAsync(User,
                                    Contact,
                                    ContactOperations.Approve);

            if (!canApprove.Succeeded)
            {
                Contact.Status = ContactStatus.Submitted;
            }
        }

        await Context.SaveChangesAsync();

        return RedirectToPage("./Index");
    }
}

Aktualisieren von DeleteModel

Aktualisieren Sie das Modell zum Löschen von Seiten, um mithilfe des Autorisierungshandlers zu überprüfen, ob Benutzer*innen über die Berechtigung zum Löschen des Kontakts verfügen.

public class DeleteModel : DI_BasePageModel
{
    public DeleteModel(
        ApplicationDbContext context,
        IAuthorizationService authorizationService,
        UserManager<IdentityUser> userManager)
        : base(context, authorizationService, userManager)
    {
    }

    [BindProperty]
    public Contact Contact { get; set; }

    public async Task<IActionResult> OnGetAsync(int id)
    {
        Contact = await Context.Contact.FirstOrDefaultAsync(
                                             m => m.ContactId == id);

        if (Contact == null)
        {
            return NotFound();
        }

        var isAuthorized = await AuthorizationService.AuthorizeAsync(
                                                 User, Contact,
                                                 ContactOperations.Delete);
        if (!isAuthorized.Succeeded)
        {
            return Forbid();
        }

        return Page();
    }

    public async Task<IActionResult> OnPostAsync(int id)
    {
        var contact = await Context
            .Contact.AsNoTracking()
            .FirstOrDefaultAsync(m => m.ContactId == id);

        if (contact == null)
        {
            return NotFound();
        }

        var isAuthorized = await AuthorizationService.AuthorizeAsync(
                                                 User, contact,
                                                 ContactOperations.Delete);
        if (!isAuthorized.Succeeded)
        {
            return Forbid();
        }

        Context.Contact.Remove(contact);
        await Context.SaveChangesAsync();

        return RedirectToPage("./Index");
    }
}

Einschleusen des Autorisierungsdiensts in die Ansichten

Derzeit werden auf der Benutzeroberfläche Links zum Bearbeiten und Löschen für Kontakte angezeigt, die von Benutzer*innen nicht geändert werden können.

Schleusen Sie den Autorisierungsdiensts in die Datei Pages/_ViewImports.cshtml, damit er in allen Ansichten verfügbar ist:

@using Microsoft.AspNetCore.Identity
@using ContactManager
@using ContactManager.Data
@namespace ContactManager.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@using ContactManager.Authorization;
@using Microsoft.AspNetCore.Authorization
@using ContactManager.Models
@inject IAuthorizationService AuthorizationService

Das obige Markup fügt mehrere using-Anweisungen hinzu.

Aktualisieren Sie die Links zum Bearbeiten und Löschen in Pages/Contacts/Index.cshtml so, dass sie nur für Benutzer*innen mit den entsprechenden Berechtigungen gerendert werden:

@page
@model ContactManager.Pages.Contacts.IndexModel

@{
    ViewData["Title"] = "Index";
}

<h2>Index</h2>

<p>
    <a asp-page="Create">Create New</a>
</p>
<table class="table">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.Contact[0].Name)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Contact[0].Address)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Contact[0].City)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Contact[0].State)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Contact[0].Zip)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Contact[0].Email)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Contact[0].Status)
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model.Contact)
        {
            <tr>
                <td>
                    @Html.DisplayFor(modelItem => item.Name)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Address)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.City)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.State)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Zip)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Email)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Status)
                </td>
                <td>
                    @if ((await AuthorizationService.AuthorizeAsync(
                     User, item,
                     ContactOperations.Update)).Succeeded)
                    {
                        <a asp-page="./Edit" asp-route-id="@item.ContactId">Edit</a>
                        <text> | </text>
                    }

                    <a asp-page="./Details" asp-route-id="@item.ContactId">Details</a>

                    @if ((await AuthorizationService.AuthorizeAsync(
                     User, item,
                     ContactOperations.Delete)).Succeeded)
                    {
                        <text> | </text>
                        <a asp-page="./Delete" asp-route-id="@item.ContactId">Delete</a>
                    }
                </td>
            </tr>
        }
    </tbody>
</table>

Warnung

Das Ausblenden von Links für Benutzer*innen, die keine Berechtigung zum Ändern von Daten haben, stellt keinen Schutz für die App dar. Durch das Ausblenden der Links wird die App benutzerfreundlicher, da nur gültige Links angezeigt werden. Benutzer*innen können die generierten URLs hacken, um Bearbeitungs- und Löschvorgänge für Daten aufzurufen, die sie nicht besitzen. Die Razor-Seite oder der Controller muss Zugriffsprüfungen erzwingen, um die Daten zu schützen.

Aktualisieren von Details

Aktualisieren Sie die Detailansicht, damit Manager*innen Kontakte genehmigen oder ablehnen können:

        @*Precedng markup omitted for brevity.*@
        <dt>
            @Html.DisplayNameFor(model => model.Contact.Email)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.Contact.Email)
        </dd>
        <dt>
            @Html.DisplayNameFor(model => model.Contact.Status)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.Contact.Status)
        </dd>
    </dl>
</div>

@if (Model.Contact.Status != ContactStatus.Approved)
{
    @if ((await AuthorizationService.AuthorizeAsync(
     User, Model.Contact, ContactOperations.Approve)).Succeeded)
    {
        <form style="display:inline;" method="post">
            <input type="hidden" name="id" value="@Model.Contact.ContactId" />
            <input type="hidden" name="status" value="@ContactStatus.Approved" />
            <button type="submit" class="btn btn-xs btn-success">Approve</button>
        </form>
    }
}

@if (Model.Contact.Status != ContactStatus.Rejected)
{
    @if ((await AuthorizationService.AuthorizeAsync(
     User, Model.Contact, ContactOperations.Reject)).Succeeded)
    {
        <form style="display:inline;" method="post">
            <input type="hidden" name="id" value="@Model.Contact.ContactId" />
            <input type="hidden" name="status" value="@ContactStatus.Rejected" />
            <button type="submit" class="btn btn-xs btn-danger">Reject</button>
        </form>
    }
}

<div>
    @if ((await AuthorizationService.AuthorizeAsync(
         User, Model.Contact,
         ContactOperations.Update)).Succeeded)
    {
        <a asp-page="./Edit" asp-route-id="@Model.Contact.ContactId">Edit</a>
        <text> | </text>
    }
    <a asp-page="./Index">Back to List</a>
</div>

Aktualisieren Sie das Modell für Detailseiten:

public class DetailsModel : DI_BasePageModel
{
    public DetailsModel(
        ApplicationDbContext context,
        IAuthorizationService authorizationService,
        UserManager<IdentityUser> userManager)
        : base(context, authorizationService, userManager)
    {
    }

    public Contact Contact { get; set; }

    public async Task<IActionResult> OnGetAsync(int id)
    {
        Contact = await Context.Contact.FirstOrDefaultAsync(m => m.ContactId == id);

        if (Contact == null)
        {
            return NotFound();
        }

        var isAuthorized = User.IsInRole(Constants.ContactManagersRole) ||
                           User.IsInRole(Constants.ContactAdministratorsRole);

        var currentUserId = UserManager.GetUserId(User);

        if (!isAuthorized
            && currentUserId != Contact.OwnerID
            && Contact.Status != ContactStatus.Approved)
        {
            return Forbid();
        }

        return Page();
    }

    public async Task<IActionResult> OnPostAsync(int id, ContactStatus status)
    {
        var contact = await Context.Contact.FirstOrDefaultAsync(
                                                  m => m.ContactId == id);

        if (contact == null)
        {
            return NotFound();
        }

        var contactOperation = (status == ContactStatus.Approved)
                                                   ? ContactOperations.Approve
                                                   : ContactOperations.Reject;

        var isAuthorized = await AuthorizationService.AuthorizeAsync(User, contact,
                                    contactOperation);
        if (!isAuthorized.Succeeded)
        {
            return Forbid();
        }
        contact.Status = status;
        Context.Contact.Update(contact);
        await Context.SaveChangesAsync();

        return RedirectToPage("./Index");
    }
}

Hinzufügen oder Entfernen von Benutzer*innen in einer Rolle

Unter diesem Problem finden Sie weitere Informationen zu:

  • Entfernen von Berechtigungen für Benutzer*innen. Beispiel: Stummschalten einzelner Benutzer*innen in einer Chat-App.
  • Hinzufügen von Berechtigungen für Benutzer*innen

Unterschiede zwischen Aufforderung und Unterbindung

Diese App legt die Standardrichtlinie so fest, dass authentifizierte Benutzer*innen erforderlich sind. Der folgende Code erlaubt anonyme Benutzer*innen. Anonyme Benutzer*innen dürfen die Unterschiede zwischen Aufforderung und Unterbindung anzeigen.

[AllowAnonymous]
public class Details2Model : DI_BasePageModel
{
    public Details2Model(
        ApplicationDbContext context,
        IAuthorizationService authorizationService,
        UserManager<IdentityUser> userManager)
        : base(context, authorizationService, userManager)
    {
    }

    public Contact Contact { get; set; }

    public async Task<IActionResult> OnGetAsync(int id)
    {
        Contact = await Context.Contact.FirstOrDefaultAsync(m => m.ContactId == id);

        if (Contact == null)
        {
            return NotFound();
        }

        if (!User.Identity.IsAuthenticated)
        {
            return Challenge();
        }

        var isAuthorized = User.IsInRole(Constants.ContactManagersRole) ||
                           User.IsInRole(Constants.ContactAdministratorsRole);

        var currentUserId = UserManager.GetUserId(User);

        if (!isAuthorized
            && currentUserId != Contact.OwnerID
            && Contact.Status != ContactStatus.Approved)
        {
            return Forbid();
        }

        return Page();
    }
}

Für den Code oben gilt:

  • Wenn Benutzer*innen nicht authentifiziert sind, wird ein ChallengeResult zurückgegeben. Wenn ein ChallengeResult zurückgegeben wird, werden die Benutzer*innen zur Anmeldeseite umgeleitet.
  • Wenn die Benutzer*innen authentifiziert, aber nicht autorisiert sind, wird ein ForbidResult zurückgegeben. Wenn ein ForbidResult zurückgegeben wird, werden die Benutzer*innen zur Seite „Zugriff verweigert“ umgeleitet.

Testen der fertigen App

Falls Sie noch kein Kennwort für registrierte Benutzerkonten festgelegt haben, verwenden Sie das Tool Geheimnis-Manager, um ein Kennwort festzulegen:

  • Wählen Sie ein sicheres Kennwort aus: Verwenden Sie acht oder mehr Zeichen und mindestens einen Großbuchstaben, eine Zahl und ein Symbol. Passw0rd! erfüllt beispielsweise die Anforderungen an sichere Kennwörter.

  • Führen Sie den folgenden Befehl im Ordner des Projekts aus. Dabei ist <PW> das Kennwort:

    dotnet user-secrets set SeedUserPW <PW>
    

Wenn die App über Kontakte verfügt:

  • Löschen Sie alle Datensätze in der Tabelle Contact.
  • Starten Sie die App neu, um das Seeding der Datenbank auszuführen.

Eine einfache Möglichkeit, die fertige App zu testen, besteht darin, drei verschiedene Browser (oder Inkognito-/InPrivate-Sitzungen) zu starten. Registrieren Sie in einem Browser eine*n neue*n Benutzer*in (z. B. test@example.com). Melden Sie sich bei jedem Browser als eine*m andere*n Benutzer*in an. Überprüfen Sie die folgenden Vorgänge:

  • Registrierte Benutzer*innen können alle genehmigten Kontaktdaten anzeigen.
  • Registrierte Benutzer*innen können ihre eigenen Daten bearbeiten und löschen.
  • Manager*innen können Kontaktdaten genehmigen und ablehnen. In der Ansicht Details werden die Schaltflächen Approve (Genehmigen) und Reject (Ablehnen) angezeigt.
  • Administrator*innen können alle Daten genehmigen/ablehnen und bearbeiten/löschen.
Benutzer Seeding durch die App Tastatur
test@example.com Nein Bearbeiten/Löschen der eigenen Daten
manager@contoso.com Ja Genehmigen/Ablehnen und Bearbeiten/Löschen eigener Daten
admin@contoso.com Ja Genehmigen/Ablehnen und Bearbeiten/Löschen aller Daten

Erstellen Sie einen Kontakt im Administratorbrowser. Kopieren Sie die URL zum Löschen und Bearbeiten vom Administratorkontakt. Fügen Sie diese Links in den Browser von Testbenutzer*innen ein, um zu bestätigen, dass die Testbenutzer*innen diese Vorgänge nicht ausführen können.

Erstellen der Starter-App

  • Erstellen einer Razor Pages-App mit dem Namen „ContactManager“

    • Erstellen Sie die App mit einzelnen Benutzerkonten.
    • Geben Sie ihr den Namen „ContactManager“, damit der Namespace mit dem im Beispiel verwendeten Namespace übereinstimmt.
    • -uld gibt LocalDB anstelle von SQLite an.
    dotnet new webapp -o ContactManager -au Individual -uld
    
  • Fügen Sie Models/Contact.cs hinzu:

    public class Contact
    {
        public int ContactId { get; set; }
        public string Name { get; set; }
        public string Address { get; set; }
        public string City { get; set; }
        public string State { get; set; }
        public string Zip { get; set; }
        [DataType(DataType.EmailAddress)]
        public string Email { get; set; }
    }
    
  • Erstellen Sie ein Gerüst für das Contact-Modell.

  • Erstellen Sie eine anfängliche Migration, und aktualisieren Sie die Datenbank:

dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design
dotnet tool install -g dotnet-aspnet-codegenerator
dotnet aspnet-codegenerator razorpage -m Contact -udl -dc ApplicationDbContext -outDir Pages\Contacts --referenceScriptLibraries
dotnet ef database drop -f
dotnet ef migrations add initial
dotnet ef database update

Hinweis

Standardmäßig stellt die Architektur der zu installierenden .NET-Binärdateien die derzeit ausgeführte Betriebssystemarchitektur dar. Informationen zum Angeben einer anderen Betriebssystemarchitektur finden Sie unter dotnet tool install, --arch option. Weitere Informationen finden Sie unter GitHub Issue dotnet/docs #29262.

Wenn beim Befehl dotnet aspnet-codegenerator razorpage ein Fehler auftritt, lesen Sie unter diesem GitHub-Issue nach.

  • Aktualisieren Sie den ContactManager-Anker in der Datei Pages/Shared/_Layout.cshtml:
<a class="navbar-brand" asp-area="" asp-page="/Contacts/Index">ContactManager</a>
  • Testen der App durch Erstellen, Bearbeiten und Löschen eines Kontakts

Ausführen eines Seedings für die Datenbank

Fügen Sie im Ordner Data die folgende SeedData-Klasse hinzu:

using ContactManager.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Linq;
using System.Threading.Tasks;

// dotnet aspnet-codegenerator razorpage -m Contact -dc ApplicationDbContext -udl -outDir Pages\Contacts --referenceScriptLibraries

namespace ContactManager.Data
{
    public static class SeedData
    {
        public static async Task Initialize(IServiceProvider serviceProvider, string testUserPw)
        {
            using (var context = new ApplicationDbContext(
                serviceProvider.GetRequiredService<DbContextOptions<ApplicationDbContext>>()))
            {              
                SeedDB(context, "0");
            }
        }        

        public static void SeedDB(ApplicationDbContext context, string adminID)
        {
            if (context.Contact.Any())
            {
                return;   // DB has been seeded
            }

            context.Contact.AddRange(
                new Contact
                {
                    Name = "Debra Garcia",
                    Address = "1234 Main St",
                    City = "Redmond",
                    State = "WA",
                    Zip = "10999",
                    Email = "debra@example.com"
                },
                new Contact
                {
                    Name = "Thorsten Weinrich",
                    Address = "5678 1st Ave W",
                    City = "Redmond",
                    State = "WA",
                    Zip = "10999",
                    Email = "thorsten@example.com"
                },
                new Contact
                {
                    Name = "Yuhong Li",
                    Address = "9012 State st",
                    City = "Redmond",
                    State = "WA",
                    Zip = "10999",
                    Email = "yuhong@example.com"
                },
                new Contact
                {
                    Name = "Jon Orton",
                    Address = "3456 Maple St",
                    City = "Redmond",
                    State = "WA",
                    Zip = "10999",
                    Email = "jon@example.com"
                },
                new Contact
                {
                    Name = "Diliana Alexieva-Bosseva",
                    Address = "7890 2nd Ave E",
                    City = "Redmond",
                    State = "WA",
                    Zip = "10999",
                    Email = "diliana@example.com"
                }
             );
            context.SaveChanges();
        }

    }
}

Rufen Sie SeedData.Initialize von Main auf:

using ContactManager.Data;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;

namespace ContactManager
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var host = CreateHostBuilder(args).Build();

            using (var scope = host.Services.CreateScope())
            {
                var services = scope.ServiceProvider;

                try
                {
                    var context = services.GetRequiredService<ApplicationDbContext>();
                    context.Database.Migrate();
                    SeedData.Initialize(services, "not used");
                }
                catch (Exception ex)
                {
                    var logger = services.GetRequiredService<ILogger<Program>>();
                    logger.LogError(ex, "An error occurred seeding the DB.");
                }
            }

            host.Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }
}

Testen Sie, ob die App das Seeding für die Datenbank ausgeführt hat. Wenn die Kontaktdatenbank Zeilen enthält, wird die Seed-Methode nicht ausgeführt.

Zusätzliche Ressourcen