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í. Také jste se dozvěděli, jak zásady používají deklarace identity k 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

Dostali jste nový požadavek, aby stránka Seznam pizzerie byla viditelná jenom ověřeným uživatelům. Kromě toho můžou pizzu vytvářet a odstraňovat jenom správci. Pojďme ho zamknout.

  1. V pages/Pizza.cshtml.cs použijte následující změny:

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

      [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 jsou přesměrováni na přihlašovací stránku.

    2. Vyřešte odkaz na Authorize přidáním následujícího řádku do using direktiv na začátek 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. Na začátek metody aOnPostOnPostDelete přidejteif (!IsAdmin) return Forbid();:

      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 s nástrojem, jako je HttpRepl nebo Postman, v přímém přístupu k těmto koncovým bodům. Přidání této kontroly zajistí, že se při tomto pokusu vrátí stavový kód HTTP 403.

  2. V Pages/Pizza.cshtml přidejte kontroly, které skryjí prvky uživatelského rozhraní správce před nesprávci:

    Skrýt formulář Nová pizza

    <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ůsobí, že prvky uživatelského rozhraní, které by měly být přístupné pouze správcům, se vykreslují jenom v případě, že je ověřený uživatel správcem.

Použití zásad autorizace

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

  1. V souboru 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 metody následujícím způsobem:

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

      Volání AuthorizePage metody zabezpečí 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ě byste místo toho mohli upravit Soubor AdminsOnly.cshtml.cs. V takovém případě byste přidali [Authorize(Policy = "Admin")] jako atribut do AdminsOnlyModel třídy . 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 souboru Program.cs.

  2. Do souboru Pages/Shared/_Layout.cshtml zapracujte 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áva v záhlaví, pokud uživatel není správcem.

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

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

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

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

    Jedná se o potvrzenou e-mailovou adresu, která získá přiřazenou deklaraci identity.

  2. V části Areas/Identity/Pages/Account/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 IConfiguration z kontejneru IoC . Obsahuje IConfiguration hodnoty z souboru appsettings.json a je přiřazený 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.
      • Pokud v souboru appsettings.json neexistuje odpovídající hodnota, použije se operátor ?? nulového sjednocení, který zajistí, že adminEmail je nastavený na string.Empty hodnotu .
      • 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í bez rozlišování malých a velkých písmen.
        • 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. Přeloží odkazy na Claim třídy v OnGetAsync metodě :

      using System.Security.Claims;
      

Otestování deklarace identity správce

Pojďme provést 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. Pokud ještě nejste přihlášení, přejděte do aplikace a přihlaste se pod stávajícím uživatelem. V záhlaví vyberte Seznam pizzy . Všimněte si, že uživateli se nezobrazují prvky uživatelského rozhraní pro odstranění nebo vytvoření pizzy.

  4. V záhlaví není žádný odkaz Správci . 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 jako nový 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 Server v editoru 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 Typ deklarace identity Deklarace identity
admin@contosopizza.com IsAdmin Ano

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.

Souhrn

V této lekci jste aplikaci upravili tak, aby ukládala deklarace identity a použila zásady podmíněného přístupu.