Cvičení – použití deklarací identity s autorizací na základě zásad

Dokončeno

V předchozí lekci jste zjistili rozdíl mezi ověřováním a autorizací. Dozvěděli jste se také, jak se deklarace identity používají zásadami pro autorizaci. V této lekci použijete identitu k ukládání deklarací identity a použití zásad pro podmíněný přístup.

Zabezpečení seznamu pizzy

Obdrželi jste nový požadavek, aby stránka Seznam pizzy měla být viditelná jenom ověřeným uživatelům. Kromě toho mohou pizzy vytvářet a odstraňovat jenom správci. Pojďme to uzamknout.

  1. V části Stránky/Pizza.cshtml.cs použijte následující změny:

    1. [Authorize] Přidejte do třídy atributPizzaModel.

      [Authorize]
      public class PizzaModel : PageModel
      

      Atribut popisuje požadavky na autorizaci uživatelů pro stránku. V tomto případě neexistují kromě ověření uživatele žádné požadavky. Anonymní uživatelé nemůžou stránku zobrazit a přesměrují se na přihlašovací stránku.

    2. Přeložte odkaz Authorize přidáním následujícího řádku do using direktiv v horní části souboru:

      using Microsoft.AspNetCore.Authorization;
      
    3. Do třídy PizzaModel přidejte následující vlastnost:

      [Authorize]
      public class PizzaModel : PageModel
      {
          public bool IsAdmin => HttpContext.User.HasClaim("IsAdmin", bool.TrueString);
      
          public List<Pizza> pizzas = new();
      

      Předchozí kód určuje, jestli má ověřený uživatel deklaraci identity IsAdmin s hodnotou True. Výsledek tohoto vyhodnocení je přístupný přes vlastnost jen pro čtení s názvem IsAdmin.

    4. Přidejte if (!IsAdmin) return Forbid(); na začátek obou OnPost metod a OnPostDelete metod:

      public IActionResult OnPost()
      {
          if (!IsAdmin) return Forbid();
          if (!ModelState.IsValid)
          {
              return Page();
          }
          PizzaService.Add(NewPizza);
          return RedirectToAction("Get");
      }
      
      public IActionResult OnPostDelete(int id)
      {
          if (!IsAdmin) return Forbid();
          PizzaService.Delete(id);
          return RedirectToAction("Get");
      }
      

      V dalším kroku skryjete prvky uživatelského rozhraní pro vytváření a odstraňování pro uživatele, kteří nejsou správci. To nezabrání nežádoucímu člověku v přímém přístupu k těmto koncovým bodům pomocí nástroje, jako je HttpRepl nebo curl. Přidáním této kontroly se zajistí, že pokud se o to pokusíte, vrátí se stavový kód HTTP 403.

  2. V pages/Pizza.cshtml přidejte kontroly pro skrytí prvků uživatelského rozhraní správce před nesprávci:

    Skrýt nový formulář pizzy

    <h1>Pizza List 🍕</h1>
    @if (Model.IsAdmin)
    {
    <form method="post" class="card p-3">
        <div class="row">
            <div asp-validation-summary="All"></div>
        </div>
        <div class="form-group mb-0 align-middle">
            <label asp-for="NewPizza.Name">Name</label>
            <input type="text" asp-for="NewPizza.Name" class="mr-5">
            <label asp-for="NewPizza.Size">Size</label>
            <select asp-for="NewPizza.Size" asp-items="Html.GetEnumSelectList<PizzaSize>()" class="mr-5"></select>
            <label asp-for="NewPizza.Price"></label>
            <input asp-for="NewPizza.Price" class="mr-5" />
            <label asp-for="NewPizza.IsGlutenFree">Gluten Free</label>
            <input type="checkbox" asp-for="NewPizza.IsGlutenFree" class="mr-5">
            <button class="btn btn-primary">Add</button>
        </div>
    </form>
    }
    

    Skrýt tlačítko Odstranit pizzu

    <table class="table mt-5">
        <thead>
            <tr>
                <th scope="col">Name</th>
                <th scope="col">Price</th>
                <th scope="col">Size</th>
                <th scope="col">Gluten Free</th>
                @if (Model.IsAdmin)
                {
                <th scope="col">Delete</th>
                }
            </tr>
        </thead>
        @foreach (var pizza in Model.pizzas)
        {
            <tr>
                <td>@pizza.Name</td>
                <td>@($"{pizza.Price:C}")</td>
                <td>@pizza.Size</td>
                <td>@Model.GlutenFreeText(pizza)</td>
                @if (Model.IsAdmin)
                {
                <td>
                    <form method="post" asp-page-handler="Delete" asp-route-id="@pizza.Id">
                        <button class="btn btn-danger">Delete</button>
                    </form>
                </td>
                }
            </tr>
        }
    </table>
    

    Předchozí změny způsobují, že prvky uživatelského rozhraní, které by měly být přístupné pouze správcům, se vykreslují pouze tehdy, když je ověřený uživatel správcem.

Použití zásad autorizace

Je tu ještě jedna věc, kterou byste měli zamknout. Je tu stránka, která by měla být přístupná pouze správcům, pohodlně pojmenovaným Pages/AdminsOnly.cshtml. Pojďme vytvořit zásadu IsAdmin=True pro kontrolu deklarace identity.

  1. V Program.cs proveďte následující změny:

    1. Začleňte následující zvýrazněný kód:

      // Add services to the container.
      builder.Services.AddRazorPages();
      builder.Services.AddTransient<IEmailSender, EmailSender>();
      builder.Services.AddSingleton(new QRCodeService(new QRCodeGenerator()));
      builder.Services.AddAuthorization(options =>
          options.AddPolicy("Admin", policy =>
              policy.RequireAuthenticatedUser()
                  .RequireClaim("IsAdmin", bool.TrueString)));
      
      var app = builder.Build();
      

      Předchozí kód definuje zásady autorizace s názvem Admin. Tyto zásady vyžadují, aby byl uživatel ověřený a deklaraci identity IsAdmin měl nastavenou na True.

    2. Upravte volání AddRazorPages následujícím způsobem:

      builder.Services.AddRazorPages(options =>
          options.Conventions.AuthorizePage("/AdminsOnly", "Admin"));
      

      Volání AuthorizePage metody zabezpečuje trasu /AdminsOnly Razor Page použitím Admin zásad. Ověřeným uživatelům, kteří nesplňují požadavky zásad, se zobrazí zpráva o odepření přístupu.

      Tip

      Případně můžete místo toho upravit AdminsOnly.cshtml.cs. V takovém případě byste přidali [Authorize(Policy = "Admin")] jako atribut třídy AdminsOnlyModel . Výhodou AuthorizePage výše uvedeného přístupu je, že zabezpečená stránka Razor Page nevyžaduje žádné úpravy. Aspekt autorizace se místo toho spravuje v Program.cs.

  2. V souboru Pages/Shared/_Layout.cshtml zabudujte následující změny:

    <ul class="navbar-nav flex-grow-1">
        <li class="nav-item">
            <a class="nav-link text-dark" asp-area="" asp-page="/Index">Home</a>
        </li>
        <li class="nav-item">
            <a class="nav-link text-dark" asp-area="" asp-page="/Pizza">Pizza List</a>
        </li>
        <li class="nav-item">
            <a class="nav-link text-dark" asp-area="" asp-page="/Privacy">Privacy</a>
        </li>
        @if (Context.User.HasClaim("IsAdmin", bool.TrueString))
        {
        <li class="nav-item">
            <a class="nav-link text-dark" asp-area="" asp-page="/AdminsOnly">Admins</a>
        </li>
        }
    </ul>
    

    Předchozí změna podmíněně skryje odkaz správce v záhlaví, pokud uživatel není správcem.

IsAdmin Přidání deklarace identity uživateli

Aby bylo možné určit, kteří uživatelé by měli získat IsAdmin=True deklaraci identity, bude vaše aplikace spoléhat na potvrzenou e-mailovou adresu k identifikaci správce.

  1. V appsettings.json přidejte zvýrazněnou vlastnost:

    {
      "AdminEmail" : "admin@contosopizza.com",
      "Logging": {
    

    Toto je potvrzená e-mailová adresa, která získá přiřazenou deklaraci identity.

  2. V oblastech, identitě, stránkách, účtu nebo ConfirmEmail.cshtml.cs proveďte následující změny:

    1. Začleňte následující zvýrazněný kód:

      public class ConfirmEmailModel : PageModel
      {
          private readonly UserManager<RazorPagesPizzaUser> _userManager;
          private readonly IConfiguration Configuration;
      
          public ConfirmEmailModel(UserManager<RazorPagesPizzaUser> userManager,
                                      IConfiguration configuration)
          {
              _userManager = userManager;
              Configuration = configuration;
          }
      
      

      Předchozí změna upraví konstruktor tak, aby přijímal z IConfiguration kontejneru IoC. Obsahuje IConfiguration hodnoty z appsettings.json a je přiřazena k vlastnosti jen pro čtení s názvem Configuration.

    2. Do metody OnGetAsync začleňte zvýrazněné změny:

      public async Task<IActionResult> OnGetAsync(string userId, string code)
      {
          if (userId == null || code == null)
          {
              return RedirectToPage("/Index");
          }
      
          var user = await _userManager.FindByIdAsync(userId);
          if (user == null)
          {
              return NotFound($"Unable to load user with ID '{userId}'.");
          }
      
          code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code));
          var result = await _userManager.ConfirmEmailAsync(user, code);
          StatusMessage = result.Succeeded ? "Thank you for confirming your email." : "Error confirming your email.";
      
          var adminEmail = Configuration["AdminEmail"] ?? string.Empty;
          if(result.Succeeded)
          {
              var isAdmin = string.Compare(user.Email, adminEmail, true) == 0 ? true : false;
              await _userManager.AddClaimAsync(user, 
                  new Claim("IsAdmin", isAdmin.ToString()));
          }
      
          return Page();
      }
      

      V předchozím kódu:

      • Řetězec AdminEmail se načte z Configuration vlastnosti a přiřadí se k adminEmail.
      • Operátor nulového sjednocení ?? se používá k zajištění adminEmail , že je nastavený, string.Empty pokud v appsettings.json neexistuje žádná odpovídající hodnota.
      • Pokud se e-mail uživatele úspěšně potvrdí:
        • Adresa uživatele je porovnána s adminEmail. string.Compare() se používá pro porovnání nerozlišující malá a velká písmena.
        • Metoda AddClaimAsync třídy UserManager je vyvolána za účelem uložení deklarace identity IsAdmin do tabulky AspNetUserClaims.
    3. Na začátek souboru přidejte následující kód. Řeší odkazy na Claim třídy v OnGetAsync metodě:

      using System.Security.Claims;
      

Otestování deklarace identity správce

Pojďme provést jeden poslední test, abychom ověřili nové funkce správce.

  1. Ujistěte se, že jste uložili všechny změny.

  2. Spusťte aplikaci pomocí dotnet runpříkazu .

  3. Přejděte do aplikace a přihlaste se pomocí existujícího uživatele, pokud ještě nejste přihlášení. V záhlaví vyberte Seznam pizzy. Všimněte si, že uživatel nezobrazuje prvky uživatelského rozhraní pro odstranění nebo vytvoření pizzy.

  4. V záhlaví není žádný odkaz pro správce . Na panelu Adresa prohlížeče přejděte přímo na stránku AdminsOnly . Nahraďte /Pizza v adrese URL textem /AdminsOnly.

    Tomuto uživateli je zakázán přechod na tuto stránku. Zobrazí se zpráva o odepření přístupu.

  5. Vyberte možnost odhlášení.

  6. Zaregistrujte nového uživatele s adresou admin@contosopizza.com.

  7. Stejně jako předtím potvrďte e-mailovou adresu nového uživatele a přihlaste se.

  8. Jakmile se přihlásíte pomocí nového správce, vyberte v záhlaví odkaz Seznam pizzy.

    Správce může vytvářet a odstraňovat pizzy.

  9. V záhlaví vyberte odkaz Správci.

    Zobrazí se stránka AdminsOnly .

Prohlídka tabulky AspNetUserClaims

Pomocí rozšíření SQL Serveru ve VS Code spusťte následující dotaz:

SELECT u.Email, c.ClaimType, c.ClaimValue
FROM dbo.AspNetUserClaims AS c
    INNER JOIN dbo.AspNetUsers AS u
    ON c.UserId = u.Id

Zobrazí se karta s výsledky podobnými následujícímu:

E-mail ClaimType ClaimValue
admin@contosopizza.com IsAdmin True

Deklarace identity IsAdmin je uložená v tabulce AspNetUserClaims jako dvojice klíč-hodnota. Záznam AspNetUserClaims je přidružený k záznamu uživatele v tabulce AspNetUsers.

Shrnutí

V této lekci jste aplikaci upravili tak, aby ukládaly deklarace identity a použili zásady pro podmíněný přístup.