Ćwiczenie — korzystanie z oświadczeń z autoryzacją opartą na zasadach

Ukończone

W poprzedniej lekcji przedstawiono różnicę między uwierzytelnianiem a autoryzacją. Przedstawiono również sposób użycia oświadczeń przez zasady do autoryzacji. W tej lekcji użyjesz tożsamości do przechowywania oświadczeń i stosowania zasad dostępu warunkowego.

Zabezpieczanie listy pizzy

Otrzymano nowe wymaganie, że strona Lista pizzy powinna być widoczna tylko dla uwierzytelnionych użytkowników. Ponadto tylko administratorzy mogą tworzyć i usuwać pizze. Zablokujmy go.

  1. W obszarze Strony/Pizza.cshtml.cs zastosuj następujące zmiany:

    1. [Authorize] Dodaj atrybut do PizzaModel klasy.

      [Authorize]
      public class PizzaModel : PageModel
      

      Atrybut opisuje wymagania dotyczące autoryzacji użytkownika dla strony. W takim przypadku nie ma żadnych wymagań wykraczających poza uwierzytelnienie użytkownika. Użytkownicy anonimowi nie mogą wyświetlać strony i są przekierowywani do strony logowania.

    2. Rozwiąż odwołanie, Authorize dodając następujący wiersz do using dyrektyw w górnej części pliku:

      using Microsoft.AspNetCore.Authorization;
      
    3. Dodaj następującą właściwość do klasy PizzaModel:

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

      Poprzedni kod określa, czy uwierzytelniony użytkownik ma oświadczenie IsAdmin z wartością równą True. Kod pobiera informacje o uwierzytelnianego użytkownika z HttpContext klasy nadrzędnej PageModel . Wynik tej oceny jest dostępny za pośrednictwem właściwości tylko do odczytu o nazwie IsAdmin.

    4. Dodaj if (!IsAdmin) return Forbid(); do początku metod OnPost i OnPostDelete :

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

      W następnym kroku ukryjesz elementy interfejsu użytkownika tworzenia/usuwania dla użytkowników niebędących administratorami. Nie uniemożliwia to przeciwnikowi bezpośredniego uzyskiwania dostępu do tych punktów końcowych za pomocą narzędzia takiego jak HttpRepl lub curl. Dodanie tego sprawdzania gwarantuje, że w przypadku próby zostanie zwrócony kod stanu HTTP 403.

  2. W pliku Pages/Pizza.cshtml dodaj kontrole, aby ukryć elementy interfejsu użytkownika administratora przed administratorami:

    Ukryj nowy formularz 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>
    }
    

    Ukryj przycisk Usuń pizzę

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

    Powyższe zmiany powodują, że elementy interfejsu użytkownika powinny być dostępne tylko dla administratorów do renderowania tylko wtedy, gdy uwierzytelniony użytkownik jest administratorem.

Stosowanie zasad autoryzacji

Jest jeszcze jedna rzecz, którą należy zablokować. Istnieje strona, która powinna być dostępna tylko dla administratorów, wygodnie o nazwie Pages/AdminsOnly.cshtml. Utwórzmy zasady, aby sprawdzić IsAdmin=True oświadczenie.

  1. W Program.cs wprowadź następujące zmiany:

    1. Uwzględnij następujący wyróżniony kod:

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

      Poprzedni kod definiuje zasady autoryzacji o nazwie Admin. Zasady wymagają, aby użytkownik był uwierzytelniony i miał oświadczenie IsAdmin ustawione na True.

    2. Zmodyfikuj wywołanie w AddRazorPages następujący sposób:

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

      Wywołanie AuthorizePage metody zabezpiecza trasę strony /AdminsOnly Razor przez zastosowanie Admin zasad. Użytkownicy uwierzytelnieni, którzy nie spełniają wymagań zasad, mają wyświetlany komunikat Access denied (Odmowa dostępu).

      Napiwek

      Alternatywnie można było zmodyfikować AdminsOnly.cshtml.cs. W takim przypadku należy dodać [Authorize(Policy = "Admin")] jako atrybut klasy AdminsOnlyModel . Zaletą przedstawionego AuthorizePage powyżej podejścia jest to, że chroniona strona Razor nie wymaga żadnych modyfikacji. Aspekt autoryzacji jest zamiast tego zarządzany w Program.cs.

  2. W pliku Pages/Shared/_Layout.cshtml uwzględnij następujące zmiany:

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

    Poprzednia zmiana warunkowo ukrywa link administratora w nagłówku, jeśli użytkownik nie jest administratorem. Używa Context właściwości RazorPage klasy, aby uzyskać dostęp do HttpContext informacji o uwierzytelniającym użytkowniku.

IsAdmin Dodawanie oświadczenia do użytkownika

Aby określić, którzy użytkownicy powinni uzyskać IsAdmin=True oświadczenie, aplikacja będzie polegać na potwierdzonym adresie e-mail w celu zidentyfikowania administratora.

  1. W appsettings.json dodaj wyróżnioną właściwość:

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

    Jest to potwierdzony adres e-mail, który pobiera przypisane oświadczenie.

  2. W obszarze Obszary/Tożsamość/Strony/Konto/ConfirmEmail.cshtml.cs wprowadź następujące zmiany:

    1. Uwzględnij następujący wyróżniony kod:

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

      Poprzednia zmiana modyfikuje konstruktor, aby odbierał element IConfiguration z kontenera IoC. Zawiera IConfiguration wartości z appsettings.json i jest przypisywany do właściwości tylko do odczytu o nazwie Configuration.

    2. Zastosuj wyróżnione zmiany do metody OnGetAsync:

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

      Powyższy kod:

      • Ciąg AdminEmail jest odczytywany Configuration z właściwości i przypisany do adminEmailelementu .
      • Operator ?? łączenia wartości null jest używany do zapewnieniaadminEmail, że jest ustawiony na string.Empty wartość , jeśli w appsettings.json nie ma odpowiedniej wartości.
      • Jeśli wiadomość e-mail użytkownika została pomyślnie potwierdzona:
        • Adres użytkownika jest porównywany z adresem adminEmail. string.Compare() służy do porównywania bez uwzględniania wielkości liter.
        • Metoda AddClaimAsync klasy UserManager jest wywoływana w celu zapisania oświadczenia IsAdmin w tabeli AspNetUserClaims.
    3. Dodaj następujący kod na górze pliku. Claim Rozpoznaje odwołania do klas w metodzie OnGetAsync :

      using System.Security.Claims;
      

Testowanie oświadczenia administratora

Wykonajmy ostatni test, aby zweryfikować nową funkcję administratora.

  1. Upewnij się, że wszystkie zmiany zostały zapisane.

  2. Uruchom aplikację za pomocą polecenia dotnet run.

  3. Przejdź do aplikacji i zaloguj się przy użyciu istniejącego użytkownika, jeśli jeszcze się nie zalogowałeś. Wybierz pozycję Pizza List (Lista pizzy) z nagłówka . Zwróć uwagę, że użytkownik nie przedstawia elementów interfejsu użytkownika do usunięcia ani utworzenia pizzy.

  4. W nagłówku nie ma linku Administratorzy . Na pasku adresu przeglądarki przejdź bezpośrednio do strony AdministratorzyOnly . Zastąp /Pizza ciąg w adresie URL ciągiem /AdminsOnly.

    Przechodzenie użytkownika do tej strony jest zabronione. Zostanie wyświetlony komunikat Access denied (Odmowa dostępu).

  5. Wybierz pozycję Logout (Wyloguj).

  6. Zarejestruj nowego użytkownika przy użyciu adresu admin@contosopizza.com.

  7. Tak jak wcześniej potwierdź adres e-mail nowego użytkownika i zaloguj się.

  8. Po zalogowaniu się przy użyciu nowego użytkownika administracyjnego wybierz link Lista pizzy w nagłówku.

    Użytkownik administracyjny może tworzyć i usuwać pizze.

  9. Wybierz link Administratorzy w nagłówku.

    Zostanie wyświetlona strona AdministratorzyOnly .

Sprawdzanie tabeli AspNetUserClaims

Za pomocą rozszerzenia programu SQL Server w programie VS Code uruchom następujące zapytanie:

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

Zostanie wyświetlona karta z wynikami podobnymi do następujących:

Email Typ oświadczenia ClaimValue
admin@contosopizza.com IsAdmin Prawda

Oświadczenie IsAdmin jest przechowywane jako para klucz-wartość w tabeli AspNetUserClaims. Rekord AspNetUserClaims jest skojarzony z rekordem użytkownika w tabeli AspNetUsers.

Podsumowanie

W tej lekcji zmodyfikowano aplikację w celu przechowywania oświadczeń i stosowania zasad dostępu warunkowego.