Ćwiczenie — korzystanie z oświadczeń z autoryzacją opartą na zasadach
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.
W obszarze Strony/Pizza.cshtml.cs zastosuj następujące zmiany:
[Authorize]
Dodaj atrybut doPizzaModel
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.
Rozwiąż odwołanie,
Authorize
dodając następujący wiersz dousing
dyrektyw w górnej części pliku:using Microsoft.AspNetCore.Authorization;
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 zHttpContext
klasy nadrzędnejPageModel
. Wynik tej oceny jest dostępny za pośrednictwem właściwości tylko do odczytu o nazwieIsAdmin
.Dodaj
if (!IsAdmin) return Forbid();
do początku metodOnPost
iOnPostDelete
: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.
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.
W Program.cs wprowadź następujące zmiany:
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świadczenieIsAdmin
ustawione naTrue
.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 zastosowanieAdmin
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 klasyAdminsOnlyModel
. Zaletą przedstawionegoAuthorizePage
powyżej podejścia jest to, że chroniona strona Razor nie wymaga żadnych modyfikacji. Aspekt autoryzacji jest zamiast tego zarządzany w Program.cs.
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ściRazorPage
klasy, aby uzyskać dostęp doHttpContext
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.
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.
W obszarze Obszary/Tożsamość/Strony/Konto/ConfirmEmail.cshtml.cs wprowadź następujące zmiany:
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. ZawieraIConfiguration
wartości z appsettings.json i jest przypisywany do właściwości tylko do odczytu o nazwieConfiguration
.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 odczytywanyConfiguration
z właściwości i przypisany doadminEmail
elementu . - Operator
??
łączenia wartości null jest używany do zapewnieniaadminEmail
, że jest ustawiony nastring.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
klasyUserManager
jest wywoływana w celu zapisania oświadczeniaIsAdmin
w tabeliAspNetUserClaims
.
- Adres użytkownika jest porównywany z adresem
- Ciąg
Dodaj następujący kod na górze pliku.
Claim
Rozpoznaje odwołania do klas w metodzieOnGetAsync
:using System.Security.Claims;
Testowanie oświadczenia administratora
Wykonajmy ostatni test, aby zweryfikować nową funkcję administratora.
Upewnij się, że wszystkie zmiany zostały zapisane.
Uruchom aplikację za pomocą polecenia
dotnet run
.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.
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).
Wybierz pozycję Logout (Wyloguj).
Zarejestruj nowego użytkownika przy użyciu adresu
admin@contosopizza.com
.Tak jak wcześniej potwierdź adres e-mail nowego użytkownika i zaloguj się.
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.
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:
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.