Übung: Verwenden von Ansprüchen mit richtlinienbasierter Autorisierung
In vorherigen Lerneinheit haben Sie den Unterschied zwischen Authentifizierung und Autorisierung kennengelernt. Sie haben auch gelernt, wie Ansprüche von Richtlinien zur Autorisierung verwendet werden. In dieser Lerneinheit verwenden Sie Identity, um Ansprüche zu speichern und Richtlinien für bedingten Zugriff anzuwenden.
Sichern der Pizzaliste
Sie haben eine neue Anforderung erhalten, dass die Seite mit der Pizzaliste nur für authentifizierte Benutzer sichtbar sein soll. Darüber hinaus sind nur Administratoren berechtigt, Pizzen zu bearbeiten, zu erstellen und zu löschen. Lassen Sie uns diese Funktionen sperren.
Nehmen Sie in Pages/Pizza.cshtml.cs die folgenden Änderungen vor:
Fügen Sie ein
[Authorize]
-Attribut zurPizzaModel
-Klasse hinzu.[Authorize] public class PizzaModel : PageModel
Das Attribut beschreibt die Benutzerautorisierungsanforderungen für die Seite. In diesem Fall gibt es keine Anforderungen, die über den authentifizierten Benutzer hinausgehen. Anonyme Benutzer können die Seite nicht anzeigen und werden zur Anmeldeseite weitergeleitet.
Lösen Sie den Verweis auf
Authorize
auf, indem Sie am Anfang der Datei die folgende Zeile zu denusing
-Direktiven hinzufügen:using Microsoft.AspNetCore.Authorization;
Fügen Sie der
PizzaModel
-Klasse die folgende Eigenschaft hinzu:[Authorize] public class PizzaModel : PageModel { public bool IsAdmin => HttpContext.User.HasClaim("IsAdmin", bool.TrueString); public List<Pizza> pizzas = new();
Mit dem obigen Code wird ermittelt, ob der authentifizierte Benutzer über einen
IsAdmin
-Anspruch mit dem WertTrue
verfügt. Der Code erhält Informationen über den authentifizierten Benutzer vonHttpContext
in der übergeordneten KlassePageModel
. Auf das Ergebnis dieser Auswertung wird mithilfe einer schreibgeschützten Eigenschaft namensIsAdmin
zugegriffen.Fügen Sie
if (!IsAdmin) return Forbid();
am Anfang der beiden MethodenOnPost
undOnPostDelete
hinzu: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"); }
Sie werden die Elemente der Benutzeroberfläche zum Erstellen/Löschen für Nicht-Administratoren im nächsten Schritt ausblenden. Dadurch wird nicht verhindert, dass ein Angreifer mit einem Tool wie HttpRepl oder curl direkt auf diese Endpunkte zugreift. Durch das Hinzufügen dieser Überprüfung wird sichergestellt, dass bei einem entsprechenden Versuch ein HTTP 403-Statuscode zurückgegeben wird.
Fügen Sie in Pages/Pizza.cshtml Prüfungen hinzu, um Elemente der Benutzeroberfläche, die für Administratoren vorgesehen sind, für Nicht-Administratoren auszublenden:
Ausblenden des Formulars Neue 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> }
Ausblenden der Schaltfläche Pizza löschen
<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>
Die obigen Änderungen führen dazu, dass Elemente der Benutzeroberfläche, auf die nur Administratoren zugreifen können sollen, nur dann gerendert werden, wenn der authentifizierte Benutzer ein Administrator ist.
Anwenden einer Autorisierungsrichtlinie
Es gibt noch etwas, was Sie sperren sollten. Es gibt eine Seite Pages/AdminsOnly.cshtml, auf die nur Administratoren zugreifen können sollten. Lassen Sie uns eine Richtlinie erstellen, um den IsAdmin=True
-Anspruch zu überprüfen.
Nehmen Sie in Program.cs die folgenden Änderungen vor:
Fügen Sie den folgenden hervorgehobenen Code ein:
// 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();
Im vorangehenden Code wird eine Autorisierungsrichtlinie namens
Admin
definiert. Die Richtlinie erfordert, dass der Benutzer authentifiziert ist und über einen aufTrue
festgelegtenIsAdmin
-Anspruch verfügt.Ändern Sie den Aufruf von
AddRazorPages
wie folgt:builder.Services.AddRazorPages(options => options.Conventions.AuthorizePage("/AdminsOnly", "Admin"));
Der Aufruf der
AuthorizePage
-Methode schützt die Razor Pages-Route /AdminsOnly, indem dieAdmin
-Richtlinie angewendet wird. Authentifizierte Benutzer, die die Anforderungen der Richtlinie nicht erfüllen, erhalten die Nachricht Access denied (Zugriff verweigert).Tipp
Alternativ könnten Sie stattdessen AdminsOnly.cshtml.cs ändern. In diesem Fall würden Sie
[Authorize(Policy = "Admin")]
als Attribut für dieAdminsOnlyModel
-Klasse hinzufügen. Ein Vorteil des oben gezeigtenAuthorizePage
-Ansatzes besteht darin, dass für die geschützte Razor-Seite keine Anpassungen erforderlich sind. Die Autorisierung wird stattdessen in Program.cs verwaltet.
Implementieren Sie in Pages/Shared/_Layout.cshtml die folgenden Änderungen:
<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>
Die obige Änderung blendet den Admin-Link in der Kopfzeile aus, wenn der Benutzer kein Administrator ist. Er verwendet die Eigenschaft
Context
der KlasseRazorPage
, um auf denHttpContext
über den authentifizierten Benutzer zuzugreifen.
Hinzufügen des IsAdmin
-Anspruchs für einen Benutzer
Um zu bestimmen, welche Benutzer den IsAdmin=True
-Anspruch erhalten sollen, verwendet die App eine bestätigte E-Mail-Adresse, um den Administrator zu identifizieren.
Fügen Sie in appsettings.json die hervorgehobene Eigenschaft hinzu:
{ "AdminEmail" : "admin@contosopizza.com", "Logging": {
Dies ist die bestätigte E-Mail-Adresse, der der Anspruch zugewiesen wird.
Nehmen Sie in Areas/Identity/Pages/Account/ConfirmEmail.cshtml.cs die folgenden Änderungen vor:
Fügen Sie den folgenden hervorgehobenen Code ein:
public class ConfirmEmailModel : PageModel { private readonly UserManager<RazorPagesPizzaUser> _userManager; private readonly IConfiguration Configuration; public ConfirmEmailModel(UserManager<RazorPagesPizzaUser> userManager, IConfiguration configuration) { _userManager = userManager; Configuration = configuration; }
Die obige Änderung ändert den Konstruktor für den Empfang einer
IConfiguration
vom IoC-Container.IConfiguration
enthält Werte aus appsettings.json und wird einer schreibgeschützten Eigenschaft namensConfiguration
zugewiesen.Wenden Sie die hervorgehobenen Änderungen zur
OnGetAsync
-Methode hinzu: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(); }
Im obigen Code:
- Die
AdminEmail
-Zeichenfolge wird aus derConfiguration
-Eigenschaft gelesen undadminEmail
zugewiesen. - Der NULL-Sammeloperator
??
wird verwendet, um sicherzustellen, dassadminEmail
aufstring.Empty
festgelegt wird, wenn kein entsprechender Wert in appsettings.json vorhanden ist. - Wenn die E-Mail des Benutzers erfolgreich bestätigt wird:
- Die Adresse des Benutzers wird verglichen mit
adminEmail
.string.Compare()
wird für den Vergleich ohne Berücksichtigung der Groß- und Kleinschreibung verwendet. - Wird die
AddClaimAsync
-Methode derUserManager
-Klasse aufgerufen, um einenIsAdmin
-Anspruch in derAspNetUserClaims
-Tabelle zu speichern.
- Die Adresse des Benutzers wird verglichen mit
- Die
Fügen Sie den folgenden Code ganz oben in der Datei ein. Damit werden die Verweise auf die
Claim
-Klasse in derOnGetAsync
-Methode aufgelöst:using System.Security.Claims;
Testen des Administratoranspruchs
Lassen Sie uns einen letzten Test ausführen, um die neue Administratorfunktionalität zu überprüfen.
Vergewissern Sie sich, dass Sie alle Änderungen gespeichert haben.
Führen Sie die App mit
dotnet run
aus.Navigieren Sie zu Ihrer App, und melden Sie sich bei einem vorhandenen Benutzer an, wenn Sie noch nicht angemeldet sind. Wählen Sie Pizza List in der Kopfzeile aus. Beachten Sie, dass für den Benutzer keine Benutzeroberflächenelemente zum Löschen oder Erstellen von Pizzen angezeigt werden.
Es gibt keinen Admins-Link in der Kopfzeile. Navigieren Sie in der Adressleiste des Browsers direkt zur Seite AdminsOnly. Ersetzen Sie
/Pizza
in der URL durch/AdminsOnly
.Der Benutzer kann die Seite nicht aufrufen. Die Meldung Access denied (Zugriff verweigert) wird angezeigt.
Klicken Sie auf Abmelden.
Registrieren Sie einen neuen Benutzer mit der Adresse
admin@contosopizza.com
.Bestätigen Sie wie zuvor die E-Mail-Adresse des neuen Benutzers, und melden Sie sich an.
Klicken Sie auf den Link Pizza List in der Kopfzeile, sobald Sie sich als neuer Administrator angemeldet haben.
Der Administrator kann Pizzas erstellen und löschen.
Wählen Sie den Admins-Link in der Kopfzeile aus.
Die AdminsOnly-Seite wird angezeigt.
Untersuchen der AspNetUserClaims-Tabelle
Führen Sie unter Verwendung der SQL Server-Erweiterung in VS Code die folgende Abfrage aus:
SELECT u.Email, c.ClaimType, c.ClaimValue
FROM dbo.AspNetUserClaims AS c
INNER JOIN dbo.AspNetUsers AS u
ON c.UserId = u.Id
Eine Registerkarte mit ähnlichen Ergebnissen wie folgt wird angezeigt:
ClaimType | ClaimValue | |
---|---|---|
admin@contosopizza.com | IsAdmin | True |
Der IsAdmin
-Anspruch wird als Schlüssel-Wert-Paar in der AspNetUserClaims
-Tabelle gespeichert. Der Eintrag AspNetUserClaims
wird dem Benutzereintrag in der AspNetUsers
-Tabelle zugeordnet.
Zusammenfassung
In dieser Lerneinheit haben Sie die App geändert, um Ansprüche zu speichern und Richtlinien für bedingten Zugriff anzuwenden.