Sdílet prostřednictvím


Vytvoření webové aplikace ASP.NET Core s uživatelskými daty chráněnými autorizací

Autor: Rick Anderson a Joe Audette

V tomto kurzu se dozvíte, jak vytvořit webovou aplikaci ASP.NET Core s uživatelskými daty chráněnými autorizací. Zobrazí seznam kontaktů, které vytvořili ověření (zaregistrovaní uživatelé). Existují tři skupiny zabezpečení:

  • Registrovaní uživatelé můžou zobrazit všechna schválená data a mohou upravovat nebo odstraňovat vlastní data.
  • Správci můžou schvalovat nebo odmítat kontaktní údaje. Uživatelům se zobrazují jenom schválené kontakty.
  • Správci můžou schvalovat, odmítat a upravovat nebo odstraňovat všechna data.

Obrázky v tomto dokumentu přesně neodpovídají nejnovějším šabloně.

Na následujícím obrázku je uživatel Rick (rick@example.com) přihlášený. Rick může zobrazit jenom schválené kontakty a upravit//nové odkazy pro své kontakty. Jenom poslední záznam vytvořený Rickem zobrazí odkazy Pro úpravy a odstranění . Ostatní uživatelé neuvidí poslední záznam, dokud správce nebo správce nezmění stav Na Schváleno.

Snímek obrazovky znázorňující přihlášení Ricka

Na následujícím obrázku manager@contoso.com je přihlášený a v roli manažera:

Snímek obrazovky znázorňující manager@contoso.com přihlášení

Následující obrázek znázorňuje zobrazení podrobností správců kontaktu:

Zobrazení kontaktu nadřízeným

Tlačítka Schválit a odmítnout se zobrazují jenom pro správce a správce.

Na následujícím obrázku admin@contoso.com je přihlášený a v roli správce:

Snímek obrazovky znázorňující admin@contoso.com přihlášení

Správce má všechna oprávnění. Může číst, upravovat nebo odstraňovat jakýkoli kontakt a měnit stav kontaktů.

Aplikace byla vytvořena vygenerováním následujícího Contact modelu:

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; }
}

Ukázka obsahuje následující obslužné rutiny autorizace:

  • ContactIsOwnerAuthorizationHandler: Zajišťuje, aby uživatel mohl upravovat pouze svá data.
  • ContactManagerAuthorizationHandler: Umožňuje manažerům schvalovat nebo odmítat kontakty.
  • ContactAdministratorsAuthorizationHandler: Umožňuje správcům schvalovat nebo odmítat kontakty a upravovat nebo odstraňovat kontakty.

Požadavky

Tento kurz je pokročilý. Měli byste být obeznámeni:

Úvodní a dokončená aplikace

Stáhněte si dokončenou aplikaci. Otestujte dokončenou aplikaci, abyste se seznámili s jeho funkcemi zabezpečení.

Spropitné

Pomocí git sparse-checkout stáhněte jen podsložku s ukázkami. Například:

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

Úvodní aplikace

Stáhněte si úvodníaplikaci.

Spusťte aplikaci, klepněte na odkaz ContactManager a ověřte, že můžete vytvořit, upravit a odstranit kontakt. Pokud chcete vytvořit úvodní aplikaci, přečtěte si téma Vytvoření úvodní aplikace.

Zabezpečení uživatelských dat

Následující části obsahují všechny hlavní kroky k vytvoření zabezpečené aplikace pro uživatelská data. Může být užitečné se podívat na dokončený projekt.

Svázání kontaktních údajů s uživatelem

Pomocí ASP.NET Identity ID uživatele zajistěte, aby uživatelé mohli upravovat data, ale ne jiná data uživatelů. Přidejte OwnerID model a ContactStatus do modelu Contact :

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 je ID uživatele z AspNetUser tabulky v Identity databázi. Pole Status určuje, jestli je kontakt zobrazitelný obecnými uživateli.

Vytvořte novou migraci a aktualizujte databázi:

dotnet ef migrations add userID_Status
dotnet ef database update

Přidání služeb rolí do Identity

Připojení AddRoles k přidání služeb rolí:

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>();

Vyžadování ověřených uživatelů

Nastavte záložní zásady autorizace tak, aby vyžadovaly ověření uživatelů:

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();
});

Předchozí zvýrazněný kód nastaví záložní autorizační zásady. Záložní zásady autorizace vyžadují , aby se všichni uživatelé ověřili, s výjimkou Razor stránek, kontrolerů nebo metod akcí s atributem autorizace. Například Razor Pages, controllers, or action methods with [AllowAnonymous] or [Authorize(PolicyName="MyPolicy")] use the applied authorization attribute, than the fallback authorization policy.

RequireAuthenticatedUser přidá DenyAnonymousAuthorizationRequirement do aktuální instance, která vynucuje ověření aktuálního uživatele.

Záložní zásady autorizace:

  • Použije se u všech požadavků, které explicitně nezadávají zásady autorizace. Pro požadavky obsluhované směrováním koncového bodu to zahrnuje jakýkoli koncový bod, který nezadá atribut autorizace. Pro požadavky obsluhované jiným middlewarem po autorizačním middlewaru, jako jsou statické soubory, se tato zásada vztahuje na všechny požadavky.

Nastavení záložních zásad autorizace tak, aby vyžadovalo ověření uživatelů, chrání nově přidané Razor stránky a kontrolery. Autorizace požadovaná ve výchozím nastavení je bezpečnější než spoléhání na nové kontrolery a Razor stránky k zahrnutí atributu [Authorize] .

Třída AuthorizationOptions také obsahuje AuthorizationOptions.DefaultPolicy. Jedná se DefaultPolicy o zásadu použitou s atributem [Authorize] , pokud není zadána žádná zásada. [Authorize] neobsahuje pojmenovanou zásadu, na rozdíl od [Authorize(PolicyName="MyPolicy")].

Další informace ozásadách ASP.NET ch

Alternativní způsob, jak pro kontrolery MVC a Razor stránky vyžadovat ověření všech uživatelů, je přidání autorizačního filtru:

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();

Předchozí kód používá autorizační filtr a nastavení záložních zásad používá směrování koncového bodu. Nastavení záložních zásad je upřednostňovaným způsobem, jak vyžadovat ověření všech uživatelů.

Přidejte na stránky Index AllowAnonymousPrivacy, aby anonymní uživatelé mohli získat informace o webu před registrací:

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()
    {

    }
}

Konfigurace testovacího účtu

Třída SeedData vytvoří dva účty: správce a správce. K nastavení hesla pro tyto účty použijte nástroj Secret Manager. Nastavte heslo z adresáře projektu (adresář obsahující Program.cs):

dotnet user-secrets set SeedUserPW <PW>

Pokud je zadáno slabé heslo, při zavolání se vyvolá SeedData.Initialize výjimka.

Aktualizujte aplikaci tak, aby používala testovací heslo:

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);
}

Vytvoření testovacích účtů a aktualizace kontaktů

Aktualizujte metodu InitializeSeedData ve třídě a vytvořte testovací účty:

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;
}

Přidejte ID uživatele správce a ContactStatus do kontaktů. Vytvořte jeden z kontaktů "Odesláno" a jeden "Odmítnuto". Přidejte ID uživatele a stav do všech kontaktů. Zobrazí se jenom jeden kontakt:

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
        },

Vytváření obslužných rutin pro autorizaci vlastníka, manažera a správce

Vytvořte ContactIsOwnerAuthorizationHandler třídu ve složce Authorization . Ověřuje ContactIsOwnerAuthorizationHandler , že uživatel působící na prostředku vlastní prostředek.

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;
        }
    }
}

Kontext ContactIsOwnerAuthorizationHandler volání . Úspěšné , pokud je aktuálním ověřeným uživatelem vlastník kontaktu. Autorizační obslužné rutiny obecně:

  • Zavolejte context.Succeed , když jsou splněny požadavky.
  • Vraťte Task.CompletedTask se, když nejsou splněné požadavky. Task.CompletedTask Vrácení bez předchozího volání context.Success nebo context.Fail, není úspěch nebo selhání, umožňuje spustit jiné autorizační obslužné rutiny.

Pokud potřebujete explicitně selhat, kontext volání . Selhání.

Aplikace umožňuje vlastníkům kontaktů upravovat, odstraňovat nebo vytvářet vlastní data. ContactIsOwnerAuthorizationHandler nemusí zkontrolovat operaci předanou v parametru požadavku.

Vytvoření obslužné rutiny autorizace správce

Vytvořte ContactManagerAuthorizationHandler třídu ve složce Authorization . Ověří ContactManagerAuthorizationHandler , že uživatel působící na prostředku je manažer. Změny obsahu (nové nebo změněné) můžou schválit nebo odmítnout jenom manažeři.

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;
        }
    }
}

Vytvoření obslužné rutiny autorizace správce

Vytvořte ContactAdministratorsAuthorizationHandler třídu ve složce Authorization . Ověří ContactAdministratorsAuthorizationHandler , že uživatel působící na prostředku je správce. Správce může provádět všechny operace.

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;
        }
    }
}

Registrace obslužných rutin autorizace

Služby využívající Entity Framework Core musí být registrovány pro injektáž závislostí pomocí AddScoped. Používá ContactIsOwnerAuthorizationHandler ASP.NET Core Identity, která je založená na Entity Framework Core. Zaregistrujte obslužné rutiny v kolekci služeb, aby byly dostupné ContactsControllerprostřednictvím injektáže závislostí. Na konec ConfigureServicespřidejte následující kód:

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 a ContactManagerAuthorizationHandler jsou přidány jako singletony. Jsou to singletony, protože nepoužívají EF a všechny potřebné informace jsou v Context parametru HandleRequirementAsync metody.

Podpora autorizace

V této části aktualizujete Razor stránky a přidáte třídu požadavků na operace.

Kontrola třídy požadavků na provoz kontaktů

ContactOperations Zkontrolujte předmět. Tato třída obsahuje požadavky, které aplikace podporuje:

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";
    }
}

Vytvoření základní třídy pro stránky kontaktů Razor

Vytvořte základní třídu, která obsahuje služby používané na stránkách kontaktů Razor . Základní třída umístí inicializační kód do jednoho umístění:

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;
        } 
    }
}

Předchozí kód:

  • IAuthorizationService Přidá službu pro přístup k obslužné rutině autorizace.
  • Identity UserManager Přidá službu.
  • Přidejte .ApplicationDbContext

Aktualizace modelu CreateModel

Aktualizujte model vytvoření stránky:

  • Konstruktor pro použití DI_BasePageModel základní třídy.
  • OnPostAsync metodou pro:
    • Přidejte ID uživatele do Contact modelu.
    • Zavolejte obslužnou rutinu autorizace, abyste ověřili, že má uživatel oprávnění vytvářet kontakty.
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");
        }
    }
}

Aktualizace modelu IndexModel

Aktualizujte metodu OnGetAsync tak, aby se obecné uživatelům zobrazovaly jenom schválené kontakty:

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();
    }
}

Aktualizace modelu EditModel

Přidejte autorizační obslužnou rutinu pro ověření, že uživatel kontakt vlastní. Vzhledem k tomu, že se ověřuje autorizace prostředků, [Authorize] atribut nestačí. Aplikace nemá přístup k prostředku při vyhodnocování atributů. Autorizace založená na prostředcích musí být imperativní. Kontroly se musí provést, jakmile má aplikace přístup k prostředku, a to buď načtením do stránkového modelu, nebo načtením v rámci samotné obslužné rutiny. K prostředku často přistupujete předáním klíče prostředku.

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");
    }
}

Aktualizace modelu DeleteModel

Aktualizujte model stránky pro odstranění tak, aby používal obslužnou rutinu autorizace k ověření, že uživatel má oprávnění k odstranění kontaktu.

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");
    }
}

Vložení autorizační služby do zobrazení

V současné době uživatelské rozhraní zobrazuje odkazy pro úpravy a odstranění kontaktů, které uživatel nemůže upravit.

Do souboru vložíte autorizační službu Pages/_ViewImports.cshtml , aby byla dostupná pro všechna zobrazení:

@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

Předchozí kód přidá několik using příkazů.

Aktualizujte odkazy Pro úpravy a Pages/Contacts/Index.cshtml tak, aby se vykreslovaly jenom uživatelům s příslušnými oprávněními:

@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>

Upozorňující

Skrytí odkazů od uživatelů, kteří nemají oprávnění ke změnám dat, aplikaci nezabezpečí. Když skryjete odkazy, bude aplikace uživatelsky přívětivější zobrazením jenom platných odkazů. Uživatelé můžou vygenerované adresy URL hacknout a vyvolat operace úprav a odstranění dat, která nevlastní. Stránka Razor nebo kontroler musí vynucovat kontroly přístupu k zabezpečení dat.

Aktualizace podrobností

Aktualizujte zobrazení podrobností, aby manažeři mohli schvalovat nebo odmítat kontakty:

        @*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>

Aktualizace modelu stránky podrobností

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");
    }
}

Přidání nebo odebrání uživatele do role

Informace o tomto problému najdete tady:

  • Odebrání oprávnění od uživatele Například ztlumení uživatele v chatovací aplikaci.
  • Přidání oprávnění uživateli

Rozdíly mezi výzvou a zákazem

Tato aplikace nastaví výchozí zásadu tak, aby vyžadovala ověřené uživatele. Následující kód umožňuje anonymním uživatelům. Anonymním uživatelům se můžou zobrazovat rozdíly mezi výzvou a zákazem.

[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();
    }
}

V předchozím kódu:

  • Pokud uživatel není ověřený, ChallengeResult vrátí se hodnota. ChallengeResult Po vrácení se uživatel přesměruje na přihlašovací stránku.
  • Když se uživatel ověří, ale neautorizuje, ForbidResult vrátí se. ForbidResult Po vrácení se uživatel přesměruje na stránku odepření přístupu.

Otestování dokončené aplikace

Upozorňující

Tento článek používá nástroj Secret Manager k uložení hesla pro počáteční uživatelské účty. Nástroj Secret Manager slouží k ukládání citlivých dat během místního vývoje. Informace o postupech ověřování, které je možné použít při nasazení aplikace do testovacího nebo produkčního prostředí, najdete v tématu Zabezpečené ověřovací toky.

Pokud jste ještě nenastavili heslo pro počáteční uživatelské účty, nastavte heslo pomocí nástroje Secret Manager:

  • Zvolte silné heslo:

    • Alespoň 12 znaků dlouhých, ale 14 nebo více je lepší.
    • Kombinace velkých písmen, malých písmen, číslic a symbolů.
    • Není to slovo, které lze najít ve slovníku nebo jméno osoby, znaku, produktu nebo organizace.
    • Výrazně se liší od předchozích hesel.
    • Snadno si pamatujete, ale pro ostatní je obtížné hádat. Zvažte použití zapamatovatelné fráze jako "6MonkeysRLooking^".
  • Ve složce projektu spusťte následující příkaz, kde <PW> je heslo:

    dotnet user-secrets set SeedUserPW <PW>
    

Pokud má aplikace kontakty:

  • Odstraňte všechny záznamy v Contact tabulce.
  • Restartujte aplikaci, aby se databáze osílala.

Jednoduchým způsobem, jak otestovat dokončenou aplikaci, je spustit tři různé prohlížeče (nebo anonymní relace nebo relace InPrivate). V jednom prohlížeči zaregistrujte nového uživatele (například test@example.com). Přihlaste se ke každému prohlížeči pomocí jiného uživatele. Ověřte následující operace:

  • Registrovaní uživatelé můžou zobrazit všechna schválená kontaktní data.
  • Registrovaní uživatelé můžou upravovat nebo odstraňovat vlastní data.
  • Správci můžou schvalovat nebo odmítat kontaktní údaje. V zobrazení se Details zobrazují tlačítka Schválit a Odmítnout .
  • Správci můžou schválit, odmítnout a upravit nebo odstranit všechna data.
Uživatelská Schválení nebo odmítnutí kontaktů Možnosti
test@example.com No Upravte a odstraňte jejich data.
manager@contoso.com Ano Upravte a odstraňte jejich data.
admin@contoso.com Ano Umožňuje upravit a odstranit všechna data.

Vytvořte kontakt v prohlížeči správce. Zkopírujte adresu URL pro odstranění a úpravy od kontaktu správce. Vložte tyto odkazy do prohlížeče testovacího uživatele a ověřte, že testovací uživatel nemůže tyto operace provádět.

Vytvoření úvodní aplikace

  • Vytvoření Razor aplikace Pages s názvem ContactManager

    • Vytvořte aplikaci s individuálními uživatelskými účty.
    • Pojmenujte ho ContactManager, aby obor názvů odpovídal oboru názvů použitému v ukázce.
    • -uld určuje LocalDB místo SQLite.
    dotnet new webapp -o ContactManager -au Individual -uld
    
  • Přidání Models/Contact.cs: 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; }
        }
    }
    
  • Vygenerování Contact modelu

  • Vytvořte počáteční migraci a aktualizujte databázi:

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

Poznámka:

Ve výchozím nastavení architektura binárních souborů .NET, které se mají nainstalovat, představuje aktuálně spuštěnou architekturu operačního systému. Pokud chcete zadat jinou architekturu operačního systému, přečtěte si téma instalace nástroje dotnet, možnost --arch. Další informace najdete v tématu o problému GitHubu dotnet/AspNetCore.Docs #29262.

  • Aktualizujte ukotvení Pages/Shared/_Layout.cshtml v souboru:

    <a class="nav-link text-dark" asp-area="" asp-page="/Contacts/Index">Contact Manager</a>
    
  • Otestování aplikace vytvořením, úpravou a odstraněním kontaktu

Počáteční hodnota databáze

Do složky Data přidejte třídu SeedData:

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();
        }

    }
}

Volání SeedData.Initialize z Program.cs:

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();

Otestujte, že aplikace zasadila databázi. Pokud jsou v databázi kontaktu nějaké řádky, počáteční metoda se nespustí.

V tomto kurzu se dozvíte, jak vytvořit webovou aplikaci ASP.NET Core s uživatelskými daty chráněnými autorizací. Zobrazí seznam kontaktů, které vytvořili ověření (zaregistrovaní uživatelé). Existují tři skupiny zabezpečení:

  • Registrovaní uživatelé můžou zobrazit všechna schválená data a mohou upravovat nebo odstraňovat vlastní data.
  • Správci můžou schvalovat nebo odmítat kontaktní údaje. Uživatelům se zobrazují jenom schválené kontakty.
  • Správci můžou schvalovat, odmítat a upravovat nebo odstraňovat všechna data.

Obrázky v tomto dokumentu přesně neodpovídají nejnovějším šabloně.

Na následujícím obrázku je uživatel Rick (rick@example.com) přihlášený. Rick může zobrazit jenom schválené kontakty a upravit//nové odkazy pro své kontakty. Jenom poslední záznam vytvořený Rickem zobrazí odkazy Pro úpravy a odstranění . Ostatní uživatelé neuvidí poslední záznam, dokud správce nebo správce nezmění stav Na Schváleno.

Snímek obrazovky znázorňující přihlášení Ricka

Na následujícím obrázku manager@contoso.com je přihlášený a v roli manažera:

Snímek obrazovky znázorňující manager@contoso.com přihlášení

Následující obrázek znázorňuje zobrazení podrobností správců kontaktu:

Zobrazení kontaktu nadřízeným

Tlačítka Schválit a odmítnout se zobrazují jenom pro správce a správce.

Na následujícím obrázku admin@contoso.com je přihlášený a v roli správce:

Snímek obrazovky znázorňující admin@contoso.com přihlášení

Správce má všechna oprávnění. Může číst, upravovat nebo odstraňovat libovolný kontakt a měnit stav kontaktů.

Aplikace byla vytvořena vygenerováním následujícího Contact modelu:

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; }
}

Ukázka obsahuje následující obslužné rutiny autorizace:

  • ContactIsOwnerAuthorizationHandler: Zajišťuje, aby uživatel mohl upravovat pouze svá data.
  • ContactManagerAuthorizationHandler: Umožňuje manažerům schvalovat nebo odmítat kontakty.
  • ContactAdministratorsAuthorizationHandler: Umožňuje správcům:
    • Schválení nebo odmítnutí kontaktů
    • Úprava a odstranění kontaktů

Požadavky

Tento kurz je pokročilý. Měli byste být obeznámeni:

Úvodní a dokončená aplikace

Stáhněte si dokončenou aplikaci. Otestujte dokončenou aplikaci, abyste se seznámili s jeho funkcemi zabezpečení.

Úvodní aplikace

Stáhněte si úvodníaplikaci.

Spusťte aplikaci, klepněte na odkaz ContactManager a ověřte, že můžete vytvořit, upravit a odstranit kontakt. Pokud chcete vytvořit úvodní aplikaci, přečtěte si téma Vytvoření úvodní aplikace.

Zabezpečení uživatelských dat

Následující části obsahují všechny hlavní kroky k vytvoření zabezpečené aplikace pro uživatelská data. Může být užitečné se podívat na dokončený projekt.

Svázání kontaktních údajů s uživatelem

Pomocí ASP.NET Identity ID uživatele zajistěte, aby uživatelé mohli upravovat data, ale ne jiná data uživatelů. Přidejte OwnerID model a ContactStatus do modelu Contact :

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 je ID uživatele z AspNetUser tabulky v Identity databázi. Pole Status určuje, jestli je kontakt zobrazitelný obecnými uživateli.

Vytvořte novou migraci a aktualizujte databázi:

dotnet ef migrations add userID_Status
dotnet ef database update

Přidání služeb rolí do Identity

Připojení AddRoles k přidání služeb rolí:

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>();

Vyžadování ověřených uživatelů

Nastavte záložní zásady ověřování tak, aby vyžadovaly ověření uživatelů:

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();
    });

Předchozí zvýrazněný kód nastaví záložní zásady ověřování. Záložní zásady ověřování vyžadují , aby se všichni uživatelé ověřili s výjimkou Razor metod pages, kontrolerů nebo akcí s atributem ověřování. Například Razor pages, controllers, or action methods with [AllowAnonymous] or [Authorize(PolicyName="MyPolicy")] use the applied authentication attribute, than the fallback authentication policy.

RequireAuthenticatedUser přidá DenyAnonymousAuthorizationRequirement do aktuální instance, která vynucuje ověření aktuálního uživatele.

Záložní zásady ověřování:

  • Použije se u všech požadavků, které explicitně nezadávají zásady ověřování. Pro požadavky obsluhované směrováním koncového bodu by to zahrnovalo jakýkoli koncový bod, který nezadá atribut autorizace. U požadavků, které obsluhuje jiný middleware po autorizačním middlewaru, jako jsou statické soubory, by se tato zásada použila na všechny požadavky.

Nastavení záložních zásad ověřování tak, aby vyžadovalo ověření uživatelů, chrání nově přidané Razor stránky a kontrolery. Ověřování vyžadované ve výchozím nastavení je bezpečnější než spoléhání na nové kontrolery a Razor stránky, které mají atribut zahrnout [Authorize] .

Třída AuthorizationOptions také obsahuje AuthorizationOptions.DefaultPolicy. Jedná se DefaultPolicy o zásadu použitou s atributem [Authorize] , pokud není zadána žádná zásada. [Authorize] neobsahuje pojmenovanou zásadu, na rozdíl od [Authorize(PolicyName="MyPolicy")].

Další informace ozásadách ASP.NET ch

Alternativní způsob, jak pro kontrolery MVC a Razor stránky vyžadovat ověření všech uživatelů, je přidání autorizačního filtru:

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));
    });

Předchozí kód používá autorizační filtr a nastavení záložních zásad používá směrování koncového bodu. Nastavení záložních zásad je upřednostňovaným způsobem, jak vyžadovat ověření všech uživatelů.

Přidejte na stránky Index AllowAnonymousPrivacy, aby anonymní uživatelé mohli získat informace o webu před registrací:

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()
        {

        }
    }
}

Konfigurace testovacího účtu

Třída SeedData vytvoří dva účty: správce a správce. K nastavení hesla pro tyto účty použijte nástroj Secret Manager. Nastavte heslo z adresáře projektu (adresář obsahující Program.cs):

dotnet user-secrets set SeedUserPW <PW>

Pokud není zadáno silné heslo, při zavolání se vyvolá SeedData.Initialize výjimka.

Aktualizujte Main , aby používalo testovací heslo:

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>();
            });
}

Vytvoření testovacích účtů a aktualizace kontaktů

Aktualizujte metodu InitializeSeedData ve třídě a vytvořte testovací účty:

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;
}

Přidejte ID uživatele správce a ContactStatus do kontaktů. Vytvořte jeden z kontaktů "Odesláno" a jeden "Odmítnuto". Přidejte ID uživatele a stav do všech kontaktů. Zobrazí se jenom jeden kontakt:

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
        },

Vytváření obslužných rutin pro autorizaci vlastníka, manažera a správce

Vytvořte ContactIsOwnerAuthorizationHandler třídu ve složce Authorization . Ověřuje ContactIsOwnerAuthorizationHandler , že uživatel působící na prostředku vlastní prostředek.

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;
        }
    }
}

Kontext ContactIsOwnerAuthorizationHandler volání . Úspěšné , pokud je aktuálním ověřeným uživatelem vlastník kontaktu. Autorizační obslužné rutiny obecně:

  • Zavolejte context.Succeed , když jsou splněny požadavky.
  • Vraťte Task.CompletedTask se, když nejsou splněné požadavky. Task.CompletedTask Vrácení bez předchozího volání context.Success nebo context.Fail, není úspěch nebo selhání, umožňuje spustit jiné autorizační obslužné rutiny.

Pokud potřebujete explicitně selhat, kontext volání . Selhání.

Aplikace umožňuje vlastníkům kontaktů upravovat, odstraňovat nebo vytvářet vlastní data. ContactIsOwnerAuthorizationHandler nemusí zkontrolovat operaci předanou v parametru požadavku.

Vytvoření obslužné rutiny autorizace správce

Vytvořte ContactManagerAuthorizationHandler třídu ve složce Authorization . Ověří ContactManagerAuthorizationHandler , že uživatel působící na prostředku je manažer. Změny obsahu (nové nebo změněné) můžou schválit nebo odmítnout jenom manažeři.

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;
        }
    }
}

Vytvoření obslužné rutiny autorizace správce

Vytvořte ContactAdministratorsAuthorizationHandler třídu ve složce Authorization . Ověří ContactAdministratorsAuthorizationHandler , že uživatel působící na prostředku je správce. Správce může provádět všechny operace.

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;
        }
    }
}

Registrace obslužných rutin autorizace

Služby využívající Entity Framework Core musí být registrovány pro injektáž závislostí pomocí AddScoped. Používá ContactIsOwnerAuthorizationHandler ASP.NET Core Identity, která je založená na Entity Framework Core. Zaregistrujte obslužné rutiny v kolekci služeb, aby byly dostupné ContactsControllerprostřednictvím injektáže závislostí. Na konec ConfigureServicespřidejte následující kód:

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 a ContactManagerAuthorizationHandler jsou přidány jako singletony. Jsou to singletony, protože nepoužívají EF a všechny potřebné informace jsou v Context parametru HandleRequirementAsync metody.

Podpora autorizace

V této části aktualizujete Razor stránky a přidáte třídu požadavků na operace.

Kontrola třídy požadavků na provoz kontaktů

ContactOperations Zkontrolujte předmět. Tato třída obsahuje požadavky, které aplikace podporuje:

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";
    }
}

Vytvoření základní třídy pro stránky kontaktů Razor

Vytvořte základní třídu, která obsahuje služby používané na stránkách kontaktů Razor . Základní třída umístí inicializační kód do jednoho umístění:

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;
        } 
    }
}

Předchozí kód:

  • IAuthorizationService Přidá službu pro přístup k obslužné rutině autorizace.
  • Identity UserManager Přidá službu.
  • Přidejte .ApplicationDbContext

Aktualizace modelu CreateModel

Aktualizujte konstruktor modelu vytvoření stránky tak, aby používal DI_BasePageModel základní třídu:

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

Aktualizujte metodu CreateModel.OnPostAsync na:

  • Přidejte ID uživatele do Contact modelu.
  • Zavolejte obslužnou rutinu autorizace, abyste ověřili, že má uživatel oprávnění vytvářet kontakty.
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");
}

Aktualizace modelu IndexModel

Aktualizujte metodu OnGetAsync tak, aby se obecné uživatelům zobrazovaly jenom schválené kontakty:

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();
    }
}

Aktualizace modelu EditModel

Přidejte autorizační obslužnou rutinu pro ověření, že uživatel kontakt vlastní. Vzhledem k tomu, že se ověřuje autorizace prostředků, [Authorize] atribut nestačí. Aplikace nemá přístup k prostředku při vyhodnocování atributů. Autorizace založená na prostředcích musí být imperativní. Kontroly se musí provést, jakmile má aplikace přístup k prostředku, a to buď načtením do stránkového modelu, nebo načtením v rámci samotné obslužné rutiny. K prostředku často přistupujete předáním klíče prostředku.

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");
    }
}

Aktualizace modelu DeleteModel

Aktualizujte model stránky pro odstranění tak, aby používal obslužnou rutinu autorizace k ověření, že uživatel má oprávnění k odstranění kontaktu.

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");
    }
}

Vložení autorizační služby do zobrazení

V současné době uživatelské rozhraní zobrazuje odkazy pro úpravy a odstranění kontaktů, které uživatel nemůže upravit.

Do souboru vložíte autorizační službu Pages/_ViewImports.cshtml , aby byla dostupná pro všechna zobrazení:

@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

Předchozí kód přidá několik using příkazů.

Aktualizujte odkazy Pro úpravy a Pages/Contacts/Index.cshtml tak, aby se vykreslovaly jenom uživatelům s příslušnými oprávněními:

@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>

Upozorňující

Skrytí odkazů od uživatelů, kteří nemají oprávnění ke změnám dat, aplikaci nezabezpečí. Když skryjete odkazy, bude aplikace uživatelsky přívětivější zobrazením jenom platných odkazů. Uživatelé můžou vygenerované adresy URL hacknout a vyvolat operace úprav a odstranění dat, která nevlastní. Stránka Razor nebo kontroler musí vynucovat kontroly přístupu k zabezpečení dat.

Aktualizace podrobností

Aktualizujte zobrazení podrobností, aby manažeři mohli schvalovat nebo odmítat kontakty:

        @*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>

Aktualizujte model stránky podrobností:

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");
    }
}

Přidání nebo odebrání uživatele do role

Informace o tomto problému najdete tady:

  • Odebrání oprávnění od uživatele Například ztlumení uživatele v chatovací aplikaci.
  • Přidání oprávnění uživateli

Rozdíly mezi výzvou a zákazem

Tato aplikace nastaví výchozí zásadu tak, aby vyžadovala ověřené uživatele. Následující kód umožňuje anonymním uživatelům. Anonymním uživatelům se můžou zobrazovat rozdíly mezi výzvou a zákazem.

[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();
    }
}

V předchozím kódu:

  • Pokud uživatel není ověřený, ChallengeResult vrátí se hodnota. ChallengeResult Po vrácení se uživatel přesměruje na přihlašovací stránku.
  • Když se uživatel ověří, ale neautorizuje, ForbidResult vrátí se. ForbidResult Po vrácení se uživatel přesměruje na stránku odepření přístupu.

Otestování dokončené aplikace

Pokud jste ještě nenastavili heslo pro počáteční uživatelské účty, nastavte heslo pomocí nástroje Secret Manager:

  • Zvolte silné heslo: Použijte osm nebo více znaků a aspoň jeden velké písmeno, číslo a symbol. Například Passw0rd! splňuje silné požadavky na heslo.

  • Ve složce projektu spusťte následující příkaz, kde <PW> je heslo:

    dotnet user-secrets set SeedUserPW <PW>
    

Pokud má aplikace kontakty:

  • Odstraňte všechny záznamy v Contact tabulce.
  • Restartujte aplikaci, aby se databáze osílala.

Jednoduchým způsobem, jak otestovat dokončenou aplikaci, je spustit tři různé prohlížeče (nebo anonymní relace nebo relace InPrivate). V jednom prohlížeči zaregistrujte nového uživatele (například test@example.com). Přihlaste se ke každému prohlížeči pomocí jiného uživatele. Ověřte následující operace:

  • Registrovaní uživatelé můžou zobrazit všechna schválená kontaktní data.
  • Registrovaní uživatelé můžou upravovat nebo odstraňovat vlastní data.
  • Správci můžou schvalovat nebo odmítat kontaktní údaje. V zobrazení se Details zobrazují tlačítka Schválit a Odmítnout .
  • Správci můžou schválit, odmítnout a upravit nebo odstranit všechna data.
Uživatelská Seeded by the app Možnosti
test@example.com No Upravte nebo odstraňte vlastní data.
manager@contoso.com Ano Schválit, odmítnout a upravit nebo odstranit vlastní data.
admin@contoso.com Ano Schválit,odmítnout a upravit/odstranit všechna data

Vytvořte kontakt v prohlížeči správce. Zkopírujte adresu URL pro odstranění a úpravy od kontaktu správce. Vložte tyto odkazy do prohlížeče testovacího uživatele a ověřte, že testovací uživatel nemůže tyto operace provádět.

Vytvoření úvodní aplikace

  • Vytvoření Razor aplikace Pages s názvem ContactManager

    • Vytvořte aplikaci s individuálními uživatelskými účty.
    • Pojmenujte ho ContactManager, aby obor názvů odpovídal oboru názvů použitému v ukázce.
    • -uld určuje LocalDB místo SQLite.
    dotnet new webapp -o ContactManager -au Individual -uld
    
  • Přidat Models/Contact.cs:

    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; }
    }
    
  • Vygenerování Contact modelu

  • Vytvořte počáteční migraci a aktualizujte databázi:

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

Poznámka:

Ve výchozím nastavení architektura binárních souborů .NET, které se mají nainstalovat, představuje aktuálně spuštěnou architekturu operačního systému. Pokud chcete zadat jinou architekturu operačního systému, přečtěte si téma instalace nástroje dotnet, možnost --arch. Další informace najdete v tématu o problému GitHubu dotnet/AspNetCore.Docs #29262.

Pokud u příkazu dojde k chybě, podívejte se na dotnet aspnet-codegenerator razorpage tento problém na GitHubu.

  • Aktualizujte ukotvení Pages/Shared/_Layout.cshtml v souboru:
<a class="navbar-brand" asp-area="" asp-page="/Contacts/Index">ContactManager</a>
  • Otestování aplikace vytvořením, úpravou a odstraněním kontaktu

Počáteční hodnota databáze

Do složky Data přidejte třídu SeedData:

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();
        }

    }
}

Volání SeedData.Initialize z Main:

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>();
                });
    }
}

Otestujte, že aplikace zasadila databázi. Pokud jsou v databázi kontaktu nějaké řádky, počáteční metoda se nespustí.

Další materiály