Ö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 får endast administratörer skapa och ta bort pizzor. Låt oss låsa den.
I Pages/Pizza.cshtml.cs tillämpar du följande ändringar:
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
. 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 metodernaOnPost
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 elementen för att skapa/ta bort användargränssnitt för icke-administratörer i nästa steg. Det hindrar inte en angripare med ett verktyg som HttpRepl eller Postman 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 formulär för ny pizza
<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
.
Gör följande ändringar i Program.cs:
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
enligt följande:builder.Services.AddRazorPages(options => options.Conventions.AuthorizePage("/AdminsOnly", "Admin"));
Metodanropet
AuthorizePage
skyddar väg för /AdminsOnly Razor-sidan genom attAdmin
tillämpa principen. För autentiserade användare som inte uppfyller principkraven visas ett meddelande om nekad åtkomst.Tips
Du kan också ha ändrat AdminsOnly.cshtml.cs i stället. I så fall skulle du lägga till
[Authorize(Policy = "Admin")]
som ett attribut iAdminsOnlyModel
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 villkorsstyrt länken Admin i rubriken om användaren inte är administratör.
Lägga 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 tilldelas anspråket.
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 för att ta emot en
IConfiguration
från IoC-containern.IConfiguration
innehåller 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 sker följande:
- Strängen
AdminEmail
läss frånConfiguration
egenskapen och tilldelas tilladminEmail
. - Operatorn
??
null-coalescing används för att säkerställaadminEmail
att anges tillstring.Empty
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 de nya administratörsfunktionerna.
Kontrollera att du har sparat alla ändringar.
Kör appen med
dotnet run
.Gå 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. I webbläsarens adressfält navigerar du direkt till sidan AdminsOnly . 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-post | ClaimType | ClaimValue |
---|---|---|
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.