Cvičení – použití deklarací identity s autorizací na základě zásad
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.
V části Stránky/Pizza.cshtml.cs použijte následující změny:
[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.
Přeložte odkaz
Authorize
přidáním následujícího řádku dousing
direktiv v horní části souboru:using Microsoft.AspNetCore.Authorization;
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 hodnotouTrue
. Výsledek tohoto vyhodnocení je přístupný přes vlastnost jen pro čtení s názvemIsAdmin
.Přidejte
if (!IsAdmin) return Forbid();
na začátek obouOnPost
metod aOnPostDelete
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.
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.
V Program.cs proveďte následující změny:
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 identityIsAdmin
měl nastavenou naTrue
.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ímAdmin
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řídyAdminsOnlyModel
. VýhodouAuthorizePage
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.
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.
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.
V oblastech, identitě, stránkách, účtu nebo ConfirmEmail.cshtml.cs proveďte následující změny:
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. ObsahujeIConfiguration
hodnoty z appsettings.json a je přiřazena k vlastnosti jen pro čtení s názvemConfiguration
.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 zConfiguration
vlastnosti a přiřadí se kadminEmail
. - 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řídyUserManager
je vyvolána za účelem uložení deklarace identityIsAdmin
do tabulkyAspNetUserClaims
.
- Adresa uživatele je porovnána s
- Řetězec
Na začátek souboru přidejte následující kód. Řeší odkazy na
Claim
třídy vOnGetAsync
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.
Ujistěte se, že jste uložili všechny změny.
Spusťte aplikaci pomocí
dotnet run
příkazu .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.
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.
Vyberte možnost odhlášení.
Zaregistrujte nového uživatele s adresou
admin@contosopizza.com
.Stejně jako předtím potvrďte e-mailovou adresu nového uživatele a přihlaste se.
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.
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:
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.