Övning – Använd anspråk med principbaserad auktorisering
I föregående lektion lärde du dig skillnaden mellan autentisering och auktorisering. Du har också lärt dig hur anspråk används av principer för auktorisering. I den här lektionen använder du Identity för att lagra anspråk och tillämpa principer för villkorlig åtkomst.
Säkra pizzalistan
Du har fått ett nytt krav på att sidan Pizzalista endast ska vara synlig för autentiserade användare. Dessutom kan endast administratörer skapa och ta bort pizzor. Vi låser den.
Använd följande ändringar i Sidor/Pizza.cshtml.cs:
Lägg till ett
[Authorize]
attribut iPizzaModel
klassen.[Authorize] public class PizzaModel : PageModel
Attributet beskriver användarens auktoriseringskrav för sidan. I det här fallet finns det inga krav utöver användaren som autentiseras. Anonyma användare får inte visa sidan och omdirigeras till inloggningssidan.
Lös referensen till genom att lägga till
Authorize
följande rad i direktivenusing
överst i filen:using Microsoft.AspNetCore.Authorization;
Lägg till följande egenskap i klassen
PizzaModel
:[Authorize] public class PizzaModel : PageModel { public bool IsAdmin => HttpContext.User.HasClaim("IsAdmin", bool.TrueString); public List<Pizza> pizzas = new();
Koden ovan fastställer om den autentiserade användaren har ett
IsAdmin
-anspråk med värdetTrue
. Koden hämtar information om den autentiserade användaren frånHttpContext
den överordnadePageModel
klassen. Resultatet av den här utvärderingen nås via en skrivskyddad egenskap med namnetIsAdmin
.Lägg till
if (!IsAdmin) return Forbid();
i början av bådeOnPost
metoderna ochOnPostDelete
: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"); }
Du kommer att dölja användargränssnittselementen för skapande/borttagning för icke-administratörer i nästa steg. Det hindrar inte en angripare med ett verktyg som HttpRepl eller curl från att komma åt dessa slutpunkter direkt. Om du lägger till den här kontrollen ser du till att om detta görs returneras en HTTP 403-statuskod.
I Pages/Pizza.cshtml lägger du till kontroller för att dölja administratörsgränssnittselement från icke-administratörer:
Dölj nytt pizzaformulär
<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> }
Dölj knappen Ta bort pizza
<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>
Föregående ändringar gör att gränssnittselement som endast ska vara tillgängliga för administratörer återges när den autentiserade användaren är administratör.
Tillämpa en auktoriseringsprincip
Det är en sak till du borde låsa. Det finns en sida som endast ska vara tillgänglig för administratörer med namnet Pages/AdminsOnly.cshtml. Nu ska vi skapa en princip för att kontrollera anspråket IsAdmin=True
.
I Program.cs gör du följande ändringar:
Inkludera följande markerade 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();
Koden ovan definierar en auktoriseringsprincip med namnet
Admin
. Principen kräver att användaren autentiseras och har ettIsAdmin
-anspråk inställt påTrue
.Ändra anropet till
AddRazorPages
på följande sätt:builder.Services.AddRazorPages(options => options.Conventions.AuthorizePage("/AdminsOnly", "Admin"));
Metodanropet
AuthorizePage
skyddar /AdminsOnly Razor Page-vägen genom attAdmin
tillämpa principen. För autentiserade användare som inte uppfyller principkraven visas ett meddelande om nekad åtkomst.Dricks
Du kan också ha ändrat AdminsOnly.cshtml.cs i stället. I så fall lägger du till
[Authorize(Policy = "Admin")]
som ett attribut förAdminsOnlyModel
klassen. En fördel med metodenAuthorizePage
ovan är att Razor-sidan som skyddas inte kräver några ändringar. Auktoriseringsaspekten hanteras i stället i Program.cs.
I Pages/Shared/_Layout.cshtml lägger du till följande ändringar:
<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>
Föregående ändring döljer villkorligt administratörslänken i rubriken om användaren inte är administratör. Den använder
Context
-egenskapen förRazorPage
klassen för att komma åt denHttpContext
innehållande informationen om den autentiserade användaren.
Lägg till anspråket IsAdmin
till en användare
För att avgöra vilka användare som ska få anspråket IsAdmin=True
kommer din app att förlita sig på en bekräftad e-postadress för att identifiera administratören.
Lägg till den markerade egenskapen i appsettings.json:
{ "AdminEmail" : "admin@contosopizza.com", "Logging": {
Det här är den bekräftade e-postadressen som får anspråket tilldelat.
Gör följande ändringar i Områden/Identitet/Sidor/Konto/ConfirmEmail.cshtml.cs:
Inkludera följande markerade 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; }
Föregående ändring ändrar konstruktorn till att ta emot en
IConfiguration
från IoC-containern. InnehållerIConfiguration
värden från appsettings.json och tilldelas till en skrivskyddad egenskap med namnetConfiguration
.Tillämpa de markerade ändringarna i metoden
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(); }
I koden ovan:
- Strängen
AdminEmail
läse frånConfiguration
egenskapen och tilldelas tilladminEmail
. - Operatorn
??
null-coalescing används för att säkerställaadminEmail
attstring.Empty
är inställd på om det inte finns något motsvarande värde i appsettings.json. - Om användarens e-post har bekräftats:
- Användarens adress jämförs med
adminEmail
.string.Compare()
används för skiftlägesokänslig jämförelse. -
UserManager
-klassensAddClaimAsync
-metod anropas för att spara ettIsAdmin
-anspråk i tabellenAspNetUserClaims
.
- Användarens adress jämförs med
- Strängen
Lägg till följande kod högst upp i filen. Den löser
Claim
klassreferenserna iOnGetAsync
metoden:using System.Security.Claims;
Testa administratörsanspråk
Nu ska vi göra ett sista test för att verifiera den nya administratörsfunktionen.
Kontrollera att du har sparat alla dina ändringar.
Kör appen med
dotnet run
.Navigera till din app och logga in med en befintlig användare om du inte redan är inloggad. Välj Pizzalista i rubriken. Observera att användaren inte visas gränssnittselement för att ta bort eller skapa pizzor.
Det finns ingen administratörslänk i rubriken. Gå direkt till sidan AdminsOnly i webbläsarens adressfält. Ersätt
/Pizza
i URL:en med/AdminsOnly
.Användaren tillåts inte att navigera till sidan. Ett meddelande om nekad åtkomst visas.
Välj Logout (Logga ut).
Registrera en ny användare med adressen
admin@contosopizza.com
.Som tidigare bekräftar du den nya användarens e-postadress och loggar in.
När du har loggat in med den nya administrativa användaren väljer du länken Pizzalista i rubriken.
Den administrativa användaren kan skapa och ta bort pizzor.
Välj länken Administratörer i rubriken.
Sidan AdminsOnly visas.
Granska tabellen AspNetUserClaims
Kör följande fråga med hjälp av SQL Server-tillägget i VS Code:
SELECT u.Email, c.ClaimType, c.ClaimValue
FROM dbo.AspNetUserClaims AS c
INNER JOIN dbo.AspNetUsers AS u
ON c.UserId = u.Id
En flik med resultat som liknar följande visas:
E-postmeddelande | Kravtyp | Värdet för anspråk |
---|---|---|
admin@contosopizza.com | IsAdmin | Sant |
Anspråket IsAdmin
lagras som ett nyckel/värde-par i tabellen AspNetUserClaims
. Posten AspNetUserClaims
associeras med användarposten i tabellen AspNetUsers
.
Sammanfattning
I den här lektionen har du ändrat appen för att lagra anspråk och tillämpa principer för villkorlig åtkomst.