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

Od Rick Anderson a Jan Audette

Zobrazit Tento PDF

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

  • Registrovaní uživatelé můžou zobrazit všechna schválená data a můžou upravovat nebo odstraňovat vlastní data.
  • Manažeři mohou schvalovat nebo odmítat kontaktní data. Uživatelé vidí jenom schválené kontakty.
  • Správci mohou schvalovat nebo odmítat a upravovat nebo odstraňovat jakákoli data.

Obrázky v tomto dokumentu se přesně neshodují s nejnovějšími šablonami.

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

Snímek obrazovky zobrazující přihlášený Rick

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

Snímek obrazovky zobrazující manager@contoso.com přihlášený

Následující obrázek ukazuje zobrazení podrobností o kontaktu pro správce:

Zobrazení kontaktu manažera

Tlačítka schválení a odmítnutí se zobrazují pouze pro manažery 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 zobrazující admin@contoso.com přihlášený

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

Aplikace byla vytvořena pomocí generování uživatelského rozhraní tohoto 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: Zajistí, že uživatel může upravovat pouze svá data.
  • ContactManagerAuthorizationHandler: Umožňuje správců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 s:

Úvodní a dokončená aplikace

Stáhněte dokončenou aplikaci. Otestujte dokončenou aplikaci, abyste se seznámili se svými 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á data

V následujících částech najdete všechny hlavní kroky k vytvoření aplikace zabezpečených uživatelských dat. Může to být užitečné pro odkazování na dokončený projekt.

Spojit kontaktní údaje s uživatelem

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

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. StatusPole určuje, jestli je kontakt viditelný pro obecné uživatele.

Vytvořte novou migraci a aktualizujte databázi:

dotnet ef migrations add userID_Status
dotnet ef database update

Přidat služby rolí do Identity

Připojit Přidat role pro přidání služeb role:

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žadovat ověřené uživatele

Nastavte zásady nouzového 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ásady nouzového ověřování. Zásada záložního ověřování vyžaduje, aby Všichni uživatelé byli ověřeni, s výjimkou Razor stránek, řadičů nebo metod akcí s atributem ověřování. Například Razor stránky, řadiče nebo metody akcí pomocí [AllowAnonymous] nebo [Authorize(PolicyName="MyPolicy")] použijte použitý atribut ověřování, nikoli zásady nouzového ověřování.

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

Zásady nouzového ověřování:

  • Se použije u všech požadavků, které explicitně neurčují zásady ověřování. V případě požadavků poskytovaných směrováním koncových bodů by to zahrnovalo koncový bod, který nespecifikuje autorizační atribut. Pro požadavky poskytované jiným middlewarem middleware po autorizačním middlewaru, jako jsou třeba statické soubory, by se tato zásada použila u všech požadavků.

Nastavení zásady nouzového ověřování, aby vyžadovala ověření uživatelů, chrání nově přidané Razor stránky a řadiče. Pokud je ve výchozím nastavení vyžadováno ověřování, je bezpečnější než spoléhání na nové řadiče a Razor stránky pro zahrnutí [Authorize] atributu.

AuthorizationOptionsTřída také obsahuje AuthorizationOptions.DefaultPolicy . DefaultPolicyJe zásada použitá u [Authorize] atributu, pokud není zadána žádná zásada. [Authorize] neobsahuje pojmenovanou zásadu (na rozdíl od) [Authorize(PolicyName="MyPolicy")] .

Další informace o zásadách najdete v tématu Autorizace na základě zásad v ASP.NET Core .

Alternativní způsob, jakým se pro řadiče a Razor stránky MVC vyžaduje 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á filtr autorizace a nastavení záložní Zásady používá směrování koncového bodu. Nastavení záložní zásady je upřednostňovaným způsobem, jak vyžadovat ověření všech uživatelů.

Přidat AllowAnonymous na Index stránky a Privacy , aby anonymní uživatelé mohli získat informace o lokalitě před jejich 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

SeedDataTřída vytvoří dva účty: správce a manažer. Pomocí nástroje Správce tajných klíčů nastavte heslo pro tyto účty. Nastavte heslo z adresáře projektu (adresář obsahující program. cs):

dotnet user-secrets set SeedUserPW <PW>

Pokud není zadáno silné heslo, je vyvolána výjimka, když SeedData.Initialize je volána metoda.

Aktualizace Main pro použití testovacího hesla:

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 Initialize metodu ve SeedData třídě pro vytvoření testovacích účtů:

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)
{
    IdentityResult IR = null;
    var roleManager = serviceProvider.GetService<RoleManager<IdentityRole>>();

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

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

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

    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 ke kontaktům. Poznamenejte si jednu z kontaktů "odeslané" a jednu "zamítnutou". Přidejte ke všem kontaktům ID a stav uživatele. Zobrazí se pouze 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 autorizace vlastníka, správce a správce

ContactIsOwnerAuthorizationHandlerVe složce pro autorizaci vytvořte třídu. ContactIsOwnerAuthorizationHandlerOvěřuje, že uživatel, který pracuje na prostředku, je vlastníkem prostředku.

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

ContactIsOwnerAuthorizationHandlerKontext volání . Úspěšné , pokud je aktuální ověřený uživatel vlastníkem kontaktu. Obslužné rutiny autorizace obecně:

  • Volá se context.Succeed , když jsou splněné požadavky.
  • Vrátí se Task.CompletedTask , pokud nejsou splněné požadavky. Vrácení Task.CompletedTask bez předchozího volání context.Success nebo context.Fail není úspěšné nebo neúspěšné, umožňuje spuštění dalších obslužných rutin autorizace.

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

Aplikace umožňuje vlastníkům kontaktů upravit/odstranit/vytvořit vlastní data. ContactIsOwnerAuthorizationHandler není nutné kontrolovat operaci předanou parametrem požadavku.

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

ContactManagerAuthorizationHandlerVe složce pro autorizaci vytvořte třídu. Ověří uživatele, který pracuje ContactManagerAuthorizationHandler na prostředku, jako správce. Pouze správci mohou schvalovat nebo odmítat změny v obsahu (nové nebo změněné).

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

ContactAdministratorsAuthorizationHandlerVe složce pro autorizaci vytvořte třídu. ContactAdministratorsAuthorizationHandlerOvěří uživatele, který působí na prostředku, jako 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 používající Entity Framework Core musí být registrovány pro vkládání závislostí pomocí AddScoped. rozhraní ContactIsOwnerAuthorizationHandler používá ASP.NET Core Identity , které je postavené na Entity Framework Core. Zaregistrujte obslužné rutiny u kolekce služeb, aby byly k dispozici pro ContactsController vkládání závislostí. Přidejte následující kód na konec ConfigureServices :

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 singleton. Jsou typu Singleton, protože nepoužívají EF a všechny potřebné informace jsou v Context parametru HandleRequirementAsync metody.

Autorizace podpory

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

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

Zkontrolujte ContactOperations třídu. 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 Razor stránky kontaktů

Vytvořte základní třídu, která obsahuje služby používané na Razor stránkách kontaktů. 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:

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

Aktualizace CreateModel

Aktualizujte konstruktor Create Page model 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 CreateModel.OnPostAsync metodu na:

  • Přidejte ID uživatele do Contact modelu.
  • Voláním obslužné rutiny autorizace ověřte, zda má uživatel oprávnění k vytváření kontaktů.
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 IndexModel

Aktualizujte OnGetAsync metodu tak, aby se pro obecné uživatele 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 EditModel

Přidejte obslužnou rutinu autorizace pro ověření, že uživatel vlastní kontakt. Vzhledem k tomu, že je ověřována autorizace prostředků, není [Authorize] atribut dostatečně. Aplikace nemá při vyhodnocování atributů přístup k prostředku. Ověřování na základě prostředků musí být nezbytné. Kontroly musí být provedeny, jakmile aplikace má přístup k prostředku, a to buď načtením v modelu stránky, nebo jejich 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 DeleteModel

Aktualizujte odstranit model stránky tak, aby používal obslužnou rutinu autorizace k ověření, jestli má uživatel 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 upravit a odstranit pro kontakty, které uživatel nemůže upravovat.

Vložení autorizační služby do souboru Pages/_ViewImports. cshtml , aby bylo 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 odstranění na stránkách/kontakty/index. cshtml , aby byly vygenerovány pouze pro uživatele 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>

Upozornění

Skrytím odkazů uživatelů, kteří nemají oprávnění ke změně dat, nebude aplikace zabezpečená. Skrytím odkazů je aplikace uživatelsky přívětivější zobrazením pouze platných odkazů. Uživatelé mohou napadení vygenerovaných adres URL vyvolávat a vyvolat operace úpravy a odstranění na data, která nevlastní. RazorStránka nebo kontroler musí vymáhat kontroly přístupu pro zabezpečení dat.

Aktualizovat podrobnosti

Aktualizujte zobrazení podrobností tak, 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 k roli

Informace o tomto problému najdete zde :

  • Odebírají se oprávnění od uživatele. Například ztlumení uživatele v aplikaci chatu.
  • Přidávání oprávnění uživateli.

Rozdíly mezi výzvou a zakazující

Tato aplikace nastaví výchozí zásadu, která bude vyžadovat ověřené uživatele. Následující kód povoluje anonymní uživatele. Anonymní uživatelé mají povoleno zobrazovat rozdíly mezi výzvou a zakázáním.

[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 je vrácena. Když ChallengeResult se vrátí, uživatel se přesměruje na přihlašovací stránku.
  • Pokud je uživatel ověřený, ale není autorizovaný, ForbidResult vrátí se. Když ForbidResult se vrátí, uživatel se přesměruje na stránku přístup byl odepřen.

Testování dokončené aplikace

Pokud jste ještě nenastavili heslo pro osazené uživatelské účty, nastavte heslo pomocí nástroje Správce tajných klíčů :

  • Vyberte silné heslo: použijte osm nebo více znaků a alespoň jedno velké písmeno, číslici a symbol. Například Passw0rd! splňuje požadavky na silné heslo.

  • Spusťte následující příkaz ze složky projektu, kde <PW> je heslo:

    dotnet user-secrets set SeedUserPW <PW>
    

Pokud má aplikace kontakty:

  • Odstraní všechny záznamy v Contact tabulce.
  • Restartujte aplikaci a dosadíte databázi.

Snadný způsob, jak otestovat dokončenou aplikaci, je spustit tři různé prohlížeče (nebo relace anonymním/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é mohou zobrazit všechna schválená kontaktní data.
  • Registrovaní uživatelé můžou upravovat nebo odstraňovat svá vlastní data.
  • Manažeři mohou schvalovat nebo odmítat kontaktní data. DetailsZobrazení zobrazuje tlačítka schválení a odmítnutí .
  • Správci mohou schvalovat nebo odmítat a upravovat nebo odstraňovat všechna data.
Uživatel Podsazený aplikací Možnosti
test@example.com Ne Upravit nebo odstranit vlastní data.
manager@contoso.com Ano Schvalovat nebo odmítat a upravovat/odstraňovat vlastní data.
admin@contoso.com Ano Schválí nebo odmítne a upraví nebo odstraní všechna data.

V prohlížeči správce vytvořte kontakt. Zkopírujte adresu URL pro odstranění a úpravy v kontaktní osobě správce. Vložením těchto odkazů do prohlížeče testovacího uživatele ověříte, že testovací uživatel nemůže tyto operace provést.

Vytvoření úvodní aplikace

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

    • Vytvořte aplikaci pomocí individuálních uživatelských účtů.
    • Pojmenujte ho "ContactManager", aby se obor názvů shodoval s oborem názvů použitým v ukázce.
    • -uld Určuje LocalDB místo SQLite.
    dotnet new webapp -o ContactManager -au Individual -uld
    
  • Přidat modely/kontakt. 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; }
    }
    
  • Generování uživatelského rozhraní Contact modelu

  • Vytvořit počáteční migraci a aktualizovat 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

pokud dojde k chybě s dotnet aspnet-codegenerator razorpage příkazem, přečtěte si tento GitHub problém.

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

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

    }
}

Zavolat 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 dosazený databázi. Pokud je ve službě Contact DB nějaký řádek, metoda počáteční hodnoty 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í. Zobrazuje seznam kontaktů ověřovaných (registrovaných) uživatelů. Existují tři skupiny zabezpečení:

  • Registrovaní uživatelé můžou zobrazit všechna schválená data a můžou upravovat nebo odstraňovat vlastní data.
  • Manažeři mohou schvalovat nebo odmítat kontaktní data. Uživatelé vidí jenom schválené kontakty.
  • Správci mohou schvalovat nebo odmítat a upravovat nebo odstraňovat jakákoli data.

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

Snímek obrazovky zobrazující přihlášený Rick

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

Snímek obrazovky zobrazující manager@contoso.com přihlášený

Následující obrázek ukazuje zobrazení podrobností o kontaktu pro správce:

Zobrazení kontaktu manažera

Tlačítka schválení a odmítnutí se zobrazují pouze pro manažery 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 zobrazující admin@contoso.com přihlášený

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

Aplikace byla vytvořena pomocí generování uživatelského rozhraní tohoto 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: Zajistí, že uživatel může upravovat pouze svá data.
  • ContactManagerAuthorizationHandler: Umožňuje správců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 s:

Úvodní a dokončená aplikace

Stáhněte dokončenou aplikaci. Otestujte dokončenou aplikaci, abyste se seznámili se svými 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.

Zabezpečená uživatelská data

V následujících částech najdete všechny hlavní kroky k vytvoření aplikace zabezpečených uživatelských dat. Může to být užitečné pro odkazování na dokončený projekt.

Spojit kontaktní údaje s uživatelem

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

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. StatusPole určuje, jestli je kontakt viditelný pro obecné uživatele.

Vytvořte novou migraci a aktualizujte databázi:

dotnet ef migrations add userID_Status
dotnet ef database update

Přidat služby rolí do Identity

Připojit Přidat role pro přidání služeb role:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<CookiePolicyOptions>(options =>
    {
        options.CheckConsentNeeded = context => true;
        options.MinimumSameSitePolicy = SameSiteMode.None;
    });

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

Vyžadovat ověřené uživatele

Nastavte výchozí zásadu ověřování tak, aby vyžadovala ověření uživatelů:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<CookiePolicyOptions>(options =>
    {
        options.CheckConsentNeeded = context => true;
        options.MinimumSameSitePolicy = SameSiteMode.None;
    });

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

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

Ověřování můžete na Razor úrovni stránky, řadiče nebo akce pomocí atributu odsouhlasit [AllowAnonymous] . Nastavení výchozích zásad ověřování, aby vyžadovaly ověření uživatelů, chrání nově přidané Razor stránky a řadiče. Pokud je ve výchozím nastavení vyžadováno ověřování, je bezpečnější než spoléhání na nové řadiče a Razor stránky pro zahrnutí [Authorize] atributu.

Přidejte AllowAnonymous na stránky rejstřík, o a kontakt, aby anonymní uživatelé mohli získat informace o lokalitě před jejich registrací.

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

namespace ContactManager.Pages
{
    [AllowAnonymous]
    public class IndexModel : PageModel
    {
        public void OnGet()
        {

        }
    }
}

Konfigurace testovacího účtu

SeedDataTřída vytvoří dva účty: správce a manažer. Pomocí nástroje Správce tajných klíčů nastavte heslo pro tyto účty. Nastavte heslo z adresáře projektu (adresář obsahující program. cs):

dotnet user-secrets set SeedUserPW <PW>

Pokud není zadáno silné heslo, je vyvolána výjimka, když SeedData.Initialize je volána metoda.

Aktualizace Main pro použití testovacího hesla:

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

        using (var scope = host.Services.CreateScope())
        {
            var services = scope.ServiceProvider;
            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"];
            try
            {
                SeedData.Initialize(services, testUserPw).Wait();
            }
            catch (Exception ex)
            {
                var logger = services.GetRequiredService<ILogger<Program>>();
                logger.LogError(ex.Message, "An error occurred seeding the DB.");
            }
        }

        host.Run();
    }

    public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseStartup<Startup>();
}

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

Aktualizujte Initialize metodu ve SeedData třídě pro vytvoření testovacích účtů:

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 };
        await userManager.CreateAsync(user, testUserPw);
    }

    return user.Id;
}

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

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

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

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

    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 ke kontaktům. Poznamenejte si jednu z kontaktů "odeslané" a jednu "zamítnutou". Přidejte ke všem kontaktům ID a stav uživatele. Zobrazí se pouze 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 autorizace vlastníka, správce a správce

Vytvořte složku autorizace a ContactIsOwnerAuthorizationHandler v ní vytvořte třídu. ContactIsOwnerAuthorizationHandlerOvěřuje, že uživatel, který pracuje na prostředku, je vlastníkem prostředku.

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

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.FromResult(0) if targeting a version of
                // .NET Framework older than 4.6:
                return Task.CompletedTask;
            }

            // If we're 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;
        }
    }
}

ContactIsOwnerAuthorizationHandlerKontext volání . Úspěšné , pokud je aktuální ověřený uživatel vlastníkem kontaktu. Obslužné rutiny autorizace obecně:

  • Volá se context.Succeed , když jsou splněné požadavky.
  • Vrátí se Task.CompletedTask , pokud nejsou splněné požadavky. Vrácení Task.CompletedTask bez předchozího volání context.Success nebo context.Fail není úspěšné nebo neúspěšné, umožňuje spuštění dalších obslužných rutin autorizace.

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

Aplikace umožňuje vlastníkům kontaktů upravit/odstranit/vytvořit vlastní data. ContactIsOwnerAuthorizationHandler není nutné kontrolovat operaci předanou parametrem požadavku.

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

ContactManagerAuthorizationHandlerVe složce pro autorizaci vytvořte třídu. Ověří uživatele, který pracuje ContactManagerAuthorizationHandler na prostředku, jako správce. Pouze správci mohou schvalovat nebo odmítat změny v obsahu (nové nebo změněné).

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

Ve ContactAdministratorsAuthorizationHandler složce Authorization vytvořte třídu . Ověřuje, ContactAdministratorsAuthorizationHandler že uživatel, který na prostředku působí, je správcem. 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 zaregistrované pro injektáž závislostí pomocí AddScoped. Používá ContactIsOwnerAuthorizationHandler ASP.NET Core , který je postaven na Identity Entity Framework Core. Zaregistrujte obslužné rutiny v kolekci služby, aby byly dostupné prostřednictvím injektáže ContactsController závislostí. Na konec souboru přidejte následující ConfigureServices kód:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<CookiePolicyOptions>(options =>
    {
        options.CheckConsentNeeded = context => true;
        options.MinimumSameSitePolicy = SameSiteMode.None;
    });

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

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

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

    services.AddSingleton<IAuthorizationHandler,
                          ContactAdministratorsAuthorizationHandler>();

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

ContactAdministratorsAuthorizationHandler a ContactManagerAuthorizationHandler se přidávají jako jednosměnné položky. Jsou jednosměnné, protože nepouží ef a všechny potřebné informace jsou v Context parametru HandleRequirementAsync metody .

Podpora autorizace

V této části aktualizujete pages Razor a přidáte třídu provozních požadavků.

Kontrola třídy požadavků na kontaktní operace

Zkontrolujte ContactOperations třídu . 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 Razor kontaktů

Vytvořte základní třídu, která obsahuje služby používané v contacts Razor Pages. Základní třída umístěte 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:

  • Přidá službu IAuthorizationService pro přístup k autorizačním obslužnám rutinám.
  • Přidá Identity UserManager službu .
  • Přidejte ApplicationDbContext .

Aktualizace modelu CreateModel

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

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

Aktualizujte CreateModel.OnPostAsync metodu na:

  • Přidejte ID uživatele do Contact modelu.
  • Zavolejte obslužnou rutinu autorizace a ověřte, že uživatel má oprávnění k vytváření kontaktů.
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 new ChallengeResult();
    }

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

    return RedirectToPage("./Index");
}

Aktualizace modelu IndexModel

Aktualizujte OnGetAsync metodu tak, aby se obecným uživatelům zobrazují 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 obslužnou rutinu autorizace, která ověří, že kontakt vlastní uživatel. Vzhledem k tomu, že se ověřuje autorizace prostředků, [Authorize] atribut nestačí. Aplikace nemá při vyhodnocení atributů přístup k prostředku. Autorizace na základě prostředků musí být imperativní. Kontroly se musí provést, jakmile má aplikace přístup k prostředku, buď načtením v modelu stránky, nebo načtením v rámci samotné obslužné rutiny. K prostředku se často přistupuje 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 new ChallengeResult();
        }

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

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

    private bool ContactExists(int id)
    {
        return Context.Contact.Any(e => e.ContactId == id);
    }
}

Aktualizace modelu DeleteModel

Aktualizujte model odstranění stránky tak, aby pomocí autorizační obslužné rutiny ověřil, že má uživatel pro kontakt oprávnění k odstranění.

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

        return Page();
    }

    public async Task<IActionResult> OnPostAsync(int id)
    {
        Contact = await Context.Contact.FindAsync(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 new ChallengeResult();
        }

        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.

Vloží autorizační službu do souboru Views/_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 odstranění v Pages/Contacts/Index.cshtml tak, aby se vykresloval jenom pro uživatele 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>

Upozornění

Skrytím odkazů před uživateli, kteří nemají oprávnění ke změně dat, se aplikace nezabezpečí. Když skryji odkazy, bude aplikace uživatelsky přívětivější tím, že zobrazí jenom platné odkazy. Uživatelé mohou vygenerované adresy URL proniknout do systému a vyvolat operace úprav a odstranění dat, která vlastní nejsou. Stránka Razor nebo kontroler musí vynutit kontroly přístupu k zabezpečení dat.

Aktualizace podrobností

Aktualizujte zobrazení podrobností tak, aby manažeři mohli schvalovat nebo zamí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-success">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 s podrobnostmi:

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

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

Otestování dokončené aplikace

Pokud jste ještě nenastavíte heslo pro předávatelné uživatelské účty, použijte nástroj Secret Manager k nastavení hesla:

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

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

    dotnet user-secrets set SeedUserPW <PW>
    
  • Vypustit a aktualizovat databázi

    dotnet ef database drop -f
    dotnet ef database update  
    
  • Restartujte aplikaci, aby se databáze dosála.

Dokončenou aplikaci můžete snadno otestovat spuštěním tří různých prohlížečů (nebo anonymních relací nebo relací 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é mohou zobrazit všechna schválená kontaktní data.
  • Registrovaní uživatelé mohou upravovat nebo odstraňovat svá vlastní data.
  • Manažeři mohou schvalovat/zamítat kontaktní data. V Details zobrazení se zobrazují tlačítka Schválit a Odmítnout.
  • Správci můžou schválit, odmítnout a upravit nebo odstranit všechna data.
Uživatel Dosycené aplikací Možnosti
test@example.com Ne Upravte nebo odstraňte vlastní data.
manager@contoso.com Ano Schválit/odmítnout a upravit/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 z kontaktní osoby 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ést.

Vytvoření úvodní aplikace

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

    • Vytvořte aplikaci pomocí jednotlivých uživatelských účtů.
    • Pojmechte 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řidejte 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; }
    }
    
  • Vylepšete Contact model.

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

    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
    
  • Aktualizujte ukotvení ContactManager v souboru Pages/_Layout.cshtml:

    <a asp-page="/Contacts/Index" class="navbar-brand">ContactManager</a>
    
  • Testování aplikace vytvořením, úpravou a odstraněním kontaktu

Nasycené databáze

Přidejte třídu SeedData do složky Data.

Volání SeedData.Initialize z Main :

public class Program
{
    public static void Main(string[] args)
    {
        var host = CreateWebHostBuilder(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 IWebHostBuilder CreateWebHostBuilder(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseStartup<Startup>();
}

Otestujte, že aplikace dosála databáze. Pokud jsou v databázi kontaktů nějaké řádky, metoda seed se nespouštěla.

Další zdroje informací