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

Dokončeno

V této lekci vytvoříte nového uživatele s oprávněními správce. Její součástí je ukázka vytvoření a uložení deklarací identity uživatelů. Zároveň jsou definované zásady autorizace, aby bylo možné určit, jestli má ověřený uživatel zvýšená oprávnění v uživatelském rozhraní.

Zabezpečení katalogu produktů

Stránka katalogu produktů by měla být viditelná jen ověřeným uživatelům. Produkty ale mohou upravovat, vytvářet a odstraňovat jen správci.

  1. V souboru Pages/Products/Index.cshtml.cs proveďte následující změny:

    1. Nahraďte komentář // Add [Authorize] attribute následujícím atributem:

      [Authorize]
      

      Předchozí atribut popisuje požadavky na ověření uživatele pro tuto stránku. V tomto případě neexistují kromě ověření uživatele žádné požadavky. Anonymní uživatelé nemohou tuto stránku zobrazit a jsou přesměrováni na přihlašovací stránku.

    2. Zrušte komentář u řádku //using Microsoft.AspNetCore.Authorization; na začátku souboru.

      Předchozí změna přeloží atribut [Authorize] v předchozím kroku.

    3. Nahraďte komentář // Add IsAdmin property následující vlastností:

      public bool IsAdmin =>
          HttpContext.User.HasClaim("IsAdmin", bool.TrueString);
      

      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. Nahraďte komentář // Add IsAdmin check v metodě OnDelete následujícím kódem:

      if (!IsAdmin)
      {
          return Forbid();
      }
      

      Když se ověřený zaměstnanec pokusí odstranit produkt přes uživatelské rozhraní nebo ruční odeslání žádosti HTTP DELETE této stránce, vrátí se stavový kód HTTP 403.

  2. V souboru Pages/Products/Index.cshtml aktualizujte odkazy pro úpravu, odstranění a přidání produktu následujícím zvýrazněným kódem:

    Odkazy pro úpravu a odstranění:

    <td>
        @if (Model.IsAdmin)
        {
        <a asp-page="Edit" asp-route-id="@product.Id">Edit</a> <span>|</span>
        <a href="#" onclick="deleteProduct('@product.Id', antiForgeryToken())">Delete</a>
        }
    </td>
    

    Odkaz pro přidání produktu:

    @if (Model.IsAdmin)
    {
    <a asp-page="./Create">Add Product</a>
    }
    

    Předchozí změny způsobí, že se tyto odkazy vykreslí jen v případě, že je ověřeným uživatelem správce.

Registrace a uplatnění zásad autorizace

Stránky pro vytvoření produktu a úpravu produktu by měly být přístupné jen správcům. Pro zapouzdření autorizačních kritérií pro tyto stránky se vytvoří zásady Admin.

  1. V metodě ConfigureServices v souboru Startup.cs proveďte následující změny:

    1. Nahraďte komentář // Add call to AddAuthorization následujícím kódem:

      services.AddAuthorization(options =>
          options.AddPolicy("Admin", policy =>
              policy.RequireAuthenticatedUser()
                  .RequireClaim("IsAdmin", bool.TrueString)));
      

      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. Začleňte následující zvýrazněný kód:

      services.AddAntiforgery(options => options.HeaderName = "X-CSRF-TOKEN");
      services.AddRazorPages(options =>
          options.Conventions.AuthorizePage("/Products/Edit", "Admin"));
      services.AddControllers();
          
      

      Volání metody AuthorizePage zabezpečí směrování stránky Razor /Products/Edit uplatněním zásad Admin. Výhodou tohoto přístupu je, že tato zabezpečovaná stránka Razor nevyžaduje žádné úpravy. Aspekt autorizace se místo toho spravuje v souboru Startup.cs. Anonymní uživatelé budou přesměrováni na přihlašovací stránku. Ověřeným uživatelům, kteří nesplňují požadavky zásad, se zobrazí zpráva o odepření přístupu.

  2. V souboru Pages/Products/Create.cshtml.cs proveďte následující změny:

    1. Nahraďte komentář // Add [Authorize(Policy = "Admin")] attribute následujícím atributem:

      [Authorize(Policy = "Admin")]
      

      Předchozí kód představuje alternativu k volání metody AuthorizePage v souboru Startup.cs. Atribut [Authorize] vynucuje, aby požadavky zásad Admin byly splněny. Anonymní uživatelé budou přesměrováni na přihlašovací stránku. Ověřeným uživatelům, kteří nesplňují požadavky zásad, se zobrazí zpráva o odepření přístupu.

    2. Zrušte komentář u řádku //using Microsoft.AspNetCore.Authorization; na začátku souboru.

      Předchozí změna přeloží atribut [Authorize(Policy = "Admin")] v předchozím kroku.

Úprava registrační stránky

Následujícím postupem upravte registrační stránku tak, abyste správcům umožnili registraci.

  1. V souboru Areas/Identity/Pages/Account/Register.cshtml.cs proveďte následující změny:

    1. Do vnořené třídy InputModel přidejte následující vlastnost:

      public class InputModel
      {
          [DataType(DataType.Password)]
          [Display(Name = "Admin enrollment key")]
          public ulong? AdminEnrollmentKey { get; set; }
      
          [Required]
          [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 1)]
          [Display(Name = "First name")]
          public string FirstName { get; set; }
      
          [Required]
          [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 1)]
          [Display(Name = "Last name")]
          public string LastName { get; set; }
      
          [Required]
          [EmailAddress]
          [Display(Name = "Email")]
          public string Email { get; set; }
      
          [Required]
          [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
          [DataType(DataType.Password)]
          [Display(Name = "Password")]
          public string Password { get; set; }
      
          [DataType(DataType.Password)]
          [Display(Name = "Confirm password")]
          [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
          public string ConfirmPassword { get; set; }
      }
      
    2. Do metody OnPostAsync začleňte zvýrazněné změny:

      public async Task<IActionResult> OnPostAsync(
          [FromServices] AdminRegistrationTokenService tokenService,
          string returnUrl = null)
      {
          returnUrl = returnUrl ?? Url.Content("~/");
          ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
          if (ModelState.IsValid)
          {
              var user = new ContosoPetsUser
              {
                  FirstName = Input.FirstName,
                  LastName = Input.LastName,
                  UserName = Input.Email,
                  Email = Input.Email,
              };
              var result = await _userManager.CreateAsync(user, Input.Password);
              if (result.Succeeded)
              {
                  _logger.LogInformation("User created a new account with password.");
      
                  await _userManager.AddClaimAsync(user, 
                      new Claim("IsAdmin", 
                          (Input.AdminEnrollmentKey == tokenService.CreationKey).ToString()));
      
                  var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
                  code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
                  var callbackUrl = Url.Page(
                      "/Account/ConfirmEmail",
                      pageHandler: null,
                      values: new { area = "Identity", userId = user.Id, code = code },
                      protocol: Request.Scheme);
      
                  await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
                      $"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
      
                  if (_userManager.Options.SignIn.RequireConfirmedAccount)
                  {
                      return RedirectToPage("RegisterConfirmation", new { email = Input.Email });
                  }
                  else
                  {
                      await _signInManager.SignInAsync(user, isPersistent: false);
                      return LocalRedirect(returnUrl);
                  }
              }
              foreach (var error in result.Errors)
              {
                  ModelState.AddModelError(string.Empty, error.Description);
              }
          }
      
          // If we got this far, something failed, redisplay form
          return Page();
      }
      

      V předchozím kódu:

      • Atribut [FromServices] poskytuje instanci AdminRegistrationTokenService z kontejneru IOC.
      • 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. Tento kód překládá odkazy na třídu AdminRegistrationTokenService a Claim v metodě OnPostAsync:

      using ContosoPets.Ui.Services;
      using System.Security.Claims;
      
  2. Do souboru Areas/Identity/Pages/Account/Register.cshtml přidejte následující kód:

    <div class="form-group">
        <label asp-for="Input.ConfirmPassword"></label>
        <input asp-for="Input.ConfirmPassword" class="form-control" />
        <span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="Input.AdminEnrollmentKey"></label>
        <input asp-for="Input.AdminEnrollmentKey" class="form-control" />
        <span asp-validation-for="Input.AdminEnrollmentKey" class="text-danger"></span>
    </div>
    <button type="submit" class="btn btn-primary">Register</button>
    

Otestování deklarace identity správce

  1. Spuštěním následujícího příkazu sestavte aplikaci:

    dotnet build --no-restore
    

    Parametr --no-restore je použitý, protože od posledního sestavení nebyly přidány žádné balíčky NuGet. Proces sestavení vynechá obnovení balíčků NuGet a úspěšně se dokončí bez upozornění. Pokud se sestavení nezdaří, zkontrolujte výstupní informace o odstraňování potíží.

  2. Spuštěním následujícího příkazu nasaďte aplikaci do Azure App Service:

    az webapp up
    
  3. Přejděte do aplikace a přihlaste se jako existující uživatel, pokud ještě nejste přihlášeni. V záhlaví vyberte produkty. Všimněte si, že tomuto uživateli se nezobrazují odkazy pro úpravy, odstranění nebo vytvoření produktů.

  4. V adresním řádku prohlížeče přejděte přímo na stránku pro vytvoření produktu. Adresu URL této stránky lze získat spuštěním následujícího příkazu:

    echo "$webAppUrl/Products/Create"
    

    Tomuto uživateli je zakázán přechod na tuto stránku. Zobrazí se zpráva o odepření přístupu. Tento uživatel bude mít podobně zakázaný přechod například sem: /Products/Edit/1.

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

  6. Pomocí následujícího příkazu si opatřete token pro samoobslužnou registraci správce:

    echo $(wget -q -O - $webAppUrl/admintoken)
    

    Upozornění

    Mechanismus samoobslužné registrace správce je uvedený jen pro ilustrativní účely. Koncový bod /api/Admin pro získání tokenu by měl být před použitím v produkčním prostředí zabezpečen.

  7. Ve webové aplikaci zaregistrujte nového uživatele. Token z předchozího kroku by měl být zadán do textového pole pro registrační klíč správce.

  8. Jakmile se přihlásíte jako nový správce, klikněte v záhlaví na odkaz na produkty.

    Správce může prohlížet, upravovat a vytvářet produkty.

Prohlídka tabulky AspNetUserClaims

Spusťte následující příkaz:

db -c 'SELECT u."Email", c."ClaimType", c."ClaimValue" FROM "AspNetUserClaims" AS c INNER JOIN "AspNetUsers" AS u ON c."UserId" = u."Id"'

Zobrazí se varianta následujícího výstupu:

        Email         | ClaimType | ClaimValue
----------------------+-----------+------------
 scott@contoso.com    | IsAdmin   | True
(1 row)
db -Q "SELECT u.Email, c.ClaimType, c.ClaimValue FROM dbo.AspNetUserClaims AS c INNER JOIN dbo.AspNetUsers AS u ON c.UserId = u.Id" -Y25 -y10

Zobrazí se varianta následujícího výstupu:

Email                     ClaimType  ClaimValue
------------------------- ---------- ----------
scott@contoso.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.