Autorisatie op basis van resources in ASP.NET Core

In dit artikel wordt beschreven hoe u gebruikers machtigt voor toegang tot app-resources.

In een app wordt een resource meestal vertegenwoordigd door een C#-klasse die gegevens bevat die zijn opgeslagen in een verzameling, zoals een byte[] matrix. De klasse bevat meestal aanvullende metagegevens met betrekking tot de resource, zoals een unieke resource-id, datums, auteurs, brongegevens en een beschrijvende naam voor weergave in een gebruikersinterface. De verzameling met resourcegegevens wordt meestal geladen vanuit fysieke bestandsinhoud, een cloudopslagobject, een in-memory object of gegevens uit een database.

Autorisatie op basis van resources vereist speciale aandacht in ASP.NET Core apps. Kenmerkevaluatie vindt plaats vóór gegevensbinding en voordat een methode wordt uitgevoerd die een resource laadt. Declaratieve autorisatie met een [Authorize] kenmerk volstaat niet voor autorisatie op basis van resources. In plaats daarvan moet de app een aangepaste autorisatiemethode aanroepen, een benadering die imperatieve autorisatie wordt genoemd.

In dit artikel worden voorbeelden van Razor-onderdelen gebruikt en is gericht op Blazor autorisatiescenario's voor ASP.NET Core 3.1 of hoger. Voor Razor Pages en richtlijnen voor MVC, die van toepassing zijn op alle versies van ASP.NET Core, raadpleegt u de volgende bronnen:

Voorbeelden in dit artikel maken gebruik van primaire constructors, beschikbaar in C# 12 (.NET 8) of hoger. Zie Primaire constructors declareren voor klassen en structs (C#-documentatie) en Primaire constructors (C#-handleiding) voor meer informatie.

Voorbeeld-app

Het Blazor Web App voorbeeld voor dit artikel is de BlazorWebAppAuthorization voorbeeld-app (dotnet/AspNetCore.Docs.Samples GitHub-opslagplaats) (downloaden). De voorbeeld-app maakt gebruik van seeded accounts met vooraf geconfigureerde documentobjecten om de voorbeelden in dit artikel te demonstreren. Zie het README-bestand (README.md) van het voorbeeld voor meer informatie.

Caution

Deze voorbeeld-app maakt gebruik van een in-memory database voor het opslaan van gebruikersgegevens, die niet geschikt is voor productiescenario's. De voorbeeld-app is alleen bedoeld voor demonstratiedoeleinden en mag niet worden gebruikt als uitgangspunt voor productie-apps.

Imperatieve autorisatie gebruiken

Autorisatie wordt geïmplementeerd als een IAuthorizationService, die is geregistreerd in de serviceverzameling bij het opstarten van de app op het ASP.NET Core framework. De service wordt beschikbaar gesteld voor Razor onderdelen en andere klassen via afhankelijkheidsinjectie:

@using Microsoft.AspNetCore.Authorization
@inject IAuthorizationService AuthorizationService

IAuthorizationService heeft twee AuthorizeAsync methode-overbelastingen. Een van de overloadvarianten neemt een resourcenaam en beleidsnaam als parameter:

Task<AuthorizationResult> AuthorizeAsync(
    ClaimsPrincipal user, 
    object resource, 
    string policyName);

De andere overbelasting accepteert een resource en verzameling vereisten (IAuthorizationRequirement) om het volgende te evalueren:

Task<AuthorizationResult> AuthorizeAsync(
    ClaimsPrincipal user, 
    object resource,
    IEnumerable<IAuthorizationRequirement> requirements);

In het volgende voorbeeld, dat volledig wordt uitgelegd in de sectie Een handler op basis van resources maken , wordt de beveiligde resource in een aangepast Document object geladen. Er wordt een AuthorizeAsync overload aangeroepen om te bepalen of de huidige gebruiker toegang krijgt tot het document op basis van het machtigingsbeleid 'SameAuthorPolicy'. Als authorizationResult.Succeededtrue is, is de gebruiker geautoriseerd voor het document omdat hij of zij het document heeft opgesteld (Document.Author komt overeen met de Name van de gebruiker):

protected override async Task OnParametersSetAsync()
{
    var user = (await AuthStateProvider.GetAuthenticationStateAsync()).User;

    if (user.Identity is not null && user.Identity.IsAuthenticated)
    {
        var document = DocumentRepository.Find(DocumentId);

        ...

        var authorizationResult = await AuthorizationService
            .AuthorizeAsync(user, document, "SameAuthorPolicy");

        ...
    }
}

Een handler op basis van resources maken

Het maken van een autorisatiehandler op basis van resources is vergelijkbaar met het maken van een handler voor gewone vereisten. Maak een aangepaste vereisteklasse en implementeer een vereistehandlerklasse. Zie Autorisatie op basis van beleid: Vereisten voor meer informatie over het maken van een vereisteklasse.

De volgende demonstratieklasse Document wordt gebruikt:

namespace BlazorWebAppAuthorization.Models;

public class Document
{
    public string? Author { get; set; }

    public byte[]? Content { get; set; }

    public Guid ID { get; set; }

    public string? Title { get; set; }
}

De handlerklasse geeft de vereiste en het resourcetype op. In het volgende voorbeeld ziet u een handler die gebruikmaakt van een SameAuthorRequirement vereiste en een Document resource.

Services/DocumentAuthorizationHandler.cs:

using Microsoft.AspNetCore.Authorization;
using BlazorWebAppAuthorization.Models;

namespace BlazorWebAppAuthorization.Services;

public class DocumentAuthorizationHandler :
    AuthorizationHandler<SameAuthorRequirement, Document>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context, 
        SameAuthorRequirement requirement, 
        Document resource)
    {
        if (context.User.Identity?.Name == resource.Author)
        {
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}

public class SameAuthorRequirement : IAuthorizationRequirement { }

Registreer de vereiste en verwerker in Program.cs:

builder.Services.AddAuthorizationBuilder()
    .AddPolicy("SameAuthorPolicy", policy =>
        policy.Requirements.Add(new SameAuthorRequirement()));

builder.Services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationHandler>();

Registreer de vereiste en verwerker in Startup.ConfigureServices:

services.AddAuthorization(options =>
{
    options.AddPolicy("SameAuthorPolicy", policy =>
        policy.Requirements.Add(new SameAuthorRequirement()));
});

services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationHandler>();

Zie Beleid gebaseerde autorisatie in ASP.NET Core voor meer informatie over het maken van autorisatiebeleid.

Het volgende AccessDocument onderdeel roept een AuthorizeAsync overbelasting aan om te bepalen of de huidige gebruiker een document mag weergeven op basis van het autorisatiebeleid 'SameAuthorPolicy. Als authorizationResult.Succeededtrue is, is de gebruiker geautoriseerd voor het document omdat hij of zij het document heeft opgesteld (Document.Author komt overeen met de Name van de gebruiker).

Pages/AccessDocument.razor:

@page "/access-document/{documentId}"
@using Microsoft.AspNetCore.Authorization
@using BlazorWebAppAuthorization.Data
@inject AuthenticationStateProvider AuthStateProvider
@inject IAuthorizationService AuthorizationService
@inject IDocumentRepository DocumentRepository

<h1>Access Document</h1>

<AuthorizeView>
    <Authorized>
        <p>Hello, @context.User.Identity?.Name!</p>
        <p>@message</p>
    </Authorized>
    <NotAuthorized>
        <p>You're not authorized to access this page.</p>
    </NotAuthorized>
</AuthorizeView>

@code {
    private string? message;

    [Parameter]
    public string? DocumentId { get; set; }

    protected override async Task OnParametersSetAsync()
    {
        var user = (await AuthStateProvider.GetAuthenticationStateAsync()).User;

        if (user.Identity is not null && user.Identity.IsAuthenticated)
        {
            var document = DocumentRepository.Find(DocumentId);

            if (document == null)
            {
                message = "Document not found.";
                return;
            }

            var authorizationResult = await AuthorizationService
                .AuthorizeAsync(user, document, "SameAuthorPolicy");

            message = authorizationResult.Succeeded
                ? $"You are authorized for document {DocumentId}."
                : $"You are NOT authorized for document {DocumentId}.";
        }
    }
}

In de voorbeeld-app heeft elke gebruiker van de app geautoriseerde toegang tot het vooraf geladen document waarvan hij de auteur is.

Operationele vereisten

Als u beslissingen wilt nemen op basis van de resultaten van CRUD-bewerkingen (Maken, Lezen, Bijwerken, Verwijderen), gebruikt u de OperationAuthorizationRequirement helperklasse. Met de helperklasse kunt u één handler schrijven in plaats van een afzonderlijke klasse voor elk bewerkingstype. Met de volgende Operations klasse worden alle vier de typen CRUD-bewerkingen vastgelegd:

using Microsoft.AspNetCore.Authorization.Infrastructure;

public static class Operations
{
    public static readonly OperationAuthorizationRequirement Create =
        new() { Name = nameof(Create) };
    public static readonly OperationAuthorizationRequirement Delete =
        new() { Name = nameof(Delete) };
    public static readonly OperationAuthorizationRequirement Read =
        new() { Name = nameof(Read) };
    public static readonly OperationAuthorizationRequirement Update =
        new() { Name = nameof(Update) };
}

De volgende DocumentAuthorizationCrudHandler autorisatiehandler valideert de bewerking met behulp van de resource, in sommige gevallen de identiteit (rol) van de gebruiker en de eigenschap Name van de vereiste:

  • Alle gebruikers kunnen documenten lezen.
  • Alleen gebruikers in de Admin rol kunnen documenten maken en bijwerken.
  • Alleen gebruikers in de SuperUser rol kunnen documenten verwijderen.

Services/DocumentAuthorizationCrudHandler.cs:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization.Infrastructure;
using BlazorWebAppAuthorization.Models;

namespace BlazorWebAppAuthorization.Services;

public class DocumentAuthorizationCrudHandler :
    AuthorizationHandler<OperationAuthorizationRequirement, Document>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context,
        OperationAuthorizationRequirement requirement, 
        Document resource)
    {
        if (requirement.Name == Operations.Create.Name &&
            context.User.IsInRole("Admin"))
        {
            context.Succeed(requirement);
        }

        if (requirement.Name == Operations.Delete.Name &&
            context.User.IsInRole("SuperUser"))
        {
            context.Succeed(requirement);
        }

        if (requirement.Name == Operations.Read.Name)
        {
            context.Succeed(requirement);
        }

        if (requirement.Name == Operations.Update.Name &&
            context.User.IsInRole("Admin"))
        {
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}

Waar services zijn geregistreerd in de app:

builder.Services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationCrudHandler>();

Roep de overload van AuthorizeAsync aan met de bewerking om het autorisatieresultaat te retourneren.

Voor autorisatie voor het maken van een document:

var authorizationResult = await AuthorizationService
    .AuthorizeAsync(user, document, Operations.Create);

Voor autorisatie voor het lezen van een document:

var authorizationResult = await AuthorizationService
    .AuthorizeAsync(user, document, Operations.Read);

Autorisatie voor het verwijderen van een document:

var authorizationResult = await AuthorizationService
    .AuthorizeAsync(user, document, Operations.Delete);

Voor autorisatie voor het bijwerken van een document:

var authorizationResult = await AuthorizationService
    .AuthorizeAsync(user, document, Operations.Update);

Op de pagina van de voorbeeld-appAccessDocumentCrud:

  • Leela (leela@contoso.com), als een Admin en SuperUser, kan volledige CRUD-bewerkingen uitvoeren op bronnen.
  • Harry (harry@contoso.com), als enige Admin, kan resources maken, lezen en bijwerken.
  • Sarah (sarah@contoso.com), als enige SuperUser, kan resources verwijderen en lezen.