Autoryzacja oparta na zasadach w programie ASP.NET Core

Poniżej okładek , autoryzacja oparta na rolach i autoryzacja oparta na oświadczeniach używają wymagania, procedury obsługi wymagań i wstępnie skonfigurowanych zasad. Te bloki konstrukcyjne obsługują wyrażenie ocen autoryzacji w kodzie. Wynikiem jest bogatsza, wielokrotnego użytku struktura autoryzacji z możliwością testowania.

Zasady autoryzacji składają się z co najmniej jednego wymagania. Zarejestruj ją w ramach konfiguracji usługi autoryzacji w pliku aplikacji Program.cs :

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AtLeast21", policy =>
        policy.Requirements.Add(new MinimumAgeRequirement(21)));
});

W poprzednim przykładzie zostanie utworzona zasada "AtLeast21". Ma jedno wymaganie — minimalny wiek, który jest dostarczany jako parametr do wymagania.

IAuthorizationService

Podstawowa usługa określająca, czy autoryzacja zakończyła się pomyślnie, to IAuthorizationService:

/// <summary>
/// Checks policy based permissions for a user
/// </summary>
public interface IAuthorizationService
{
    /// <summary>
    /// Checks if a user meets a specific set of requirements for the specified resource
    /// </summary>
    /// <param name="user">The user to evaluate the requirements against.</param>
    /// <param name="resource">
    /// An optional resource the policy should be checked with.
    /// If a resource is not required for policy evaluation you may pass null as the value
    /// </param>
    /// <param name="requirements">The requirements to evaluate.</param>
    /// <returns>
    /// A flag indicating whether authorization has succeeded.
    /// This value is <value>true</value> when the user fulfills the policy; 
    /// otherwise <value>false</value>.
    /// </returns>
    /// <remarks>
    /// Resource is an optional parameter and may be null. Please ensure that you check 
    /// it is not null before acting upon it.
    /// </remarks>
    Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, 
                                     IEnumerable<IAuthorizationRequirement> requirements);

    /// <summary>
    /// Checks if a user meets a specific authorization policy
    /// </summary>
    /// <param name="user">The user to check the policy against.</param>
    /// <param name="resource">
    /// An optional resource the policy should be checked with.
    /// If a resource is not required for policy evaluation you may pass null as the value
    /// </param>
    /// <param name="policyName">The name of the policy to check against a specific 
    /// context.</param>
    /// <returns>
    /// A flag indicating whether authorization has succeeded.
    /// Returns a flag indicating whether the user, and optional resource has fulfilled 
    /// the policy.    
    /// <value>true</value> when the policy has been fulfilled; 
    /// otherwise <value>false</value>.
    /// </returns>
    /// <remarks>
    /// Resource is an optional parameter and may be null. Please ensure that you check
    /// it is not null before acting upon it.
    /// </remarks>
    Task<AuthorizationResult> AuthorizeAsync(
                                ClaimsPrincipal user, object resource, string policyName);
}

Powyższy kod wyróżnia dwie metody IAuthorizationService.

IAuthorizationRequirement jest usługą znacznikową bez metod i mechanizmem śledzenia, czy autoryzacja zakończyła się pomyślnie.

Każdy IAuthorizationHandler z nich jest odpowiedzialny za sprawdzenie, czy spełnione są wymagania:

/// <summary>
/// Classes implementing this interface are able to make a decision if authorization
/// is allowed.
/// </summary>
public interface IAuthorizationHandler
{
    /// <summary>
    /// Makes a decision if authorization is allowed.
    /// </summary>
    /// <param name="context">The authorization information.</param>
    Task HandleAsync(AuthorizationHandlerContext context);
}

Klasa AuthorizationHandlerContext jest używana przez program obsługi do oznaczania, czy zostały spełnione wymagania:

 context.Succeed(requirement)

Poniższy kod przedstawia uproszczoną (i oznaczona adnotacjami z komentarzami) domyślną implementację usługi autoryzacji:

public async Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, 
             object resource, IEnumerable<IAuthorizationRequirement> requirements)
{
    // Create a tracking context from the authorization inputs.
    var authContext = _contextFactory.CreateContext(requirements, user, resource);

    // By default this returns an IEnumerable<IAuthorizationHandlers> from DI.
    var handlers = await _handlers.GetHandlersAsync(authContext);

    // Invoke all handlers.
    foreach (var handler in handlers)
    {
        await handler.HandleAsync(authContext);
    }

    // Check the context, by default success is when all requirements have been met.
    return _evaluator.Evaluate(authContext);
}

Poniższy kod przedstawia typową konfigurację usługi autoryzacji:

// Add all of your handlers to DI.
builder.Services.AddSingleton<IAuthorizationHandler, MyHandler1>();
// MyHandler2, ...

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

// Configure your policies
builder.Services.AddAuthorization(options =>
      options.AddPolicy("Something",
      policy => policy.RequireClaim("Permission", "CanViewPage", "CanViewAnything")));

Użyj wartości IAuthorizationService, [Authorize(Policy = "Something")]lub RequireAuthorization("Something") w celu autoryzacji.

Stosowanie zasad do kontrolerów MVC

W przypadku aplikacji korzystających ze Razor stron zobacz sekcję Zastosuj zasady do Razor stron .

Zastosuj zasady do kontrolerów przy użyciu atrybutu [Authorize] o nazwie zasad:

[Authorize(Policy = "AtLeast21")]
public class AtLeast21Controller : Controller
{
    public IActionResult Index() => View();
}

Jeśli na poziomie kontrolera i akcji zastosowano wiele zasad, wszystkie zasady muszą zostać przekazane przed udzieleniem dostępu:

[Authorize(Policy = "AtLeast21")]
public class AtLeast21Controller : Controller
{
    public IActionResult Index() => View();
}

Stosowanie zasad do Razor stron

Zastosuj zasady do Razor stron przy użyciu atrybutu [Authorize] z nazwą zasad. Przykład:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace AuthorizationPoliciesSample.Pages;

[Authorize(Policy = "AtLeast21")]
public class AtLeast21Model : PageModel { }

Nie można stosować zasad na Razor poziomie procedury obsługi strony. Należy je zastosować do strony.

Zasady można również stosować do Razor stron przy użyciu konwencji autoryzacji.

Stosowanie zasad do punktów końcowych

Zastosuj zasady do punktów końcowych przy użyciu RequireAuthorization nazwy zasad. Przykład:

app.MapGet("/helloworld", () => "Hello World!")
    .RequireAuthorization("AtLeast21");

Wymagania

Wymaganie autoryzacji to kolekcja parametrów danych, których zasady mogą użyć do oceny bieżącego podmiotu zabezpieczeń użytkownika. W naszych zasadach "AtLeast21" wymaganie jest pojedynczym parametrem — minimalnym wiekiem. Wymaganie implementuje IAuthorizationRequirementinterfejs , który jest pustym interfejsem znacznika. Sparametryzowane wymaganie minimalnego wieku można zaimplementować w następujący sposób:

using Microsoft.AspNetCore.Authorization;

namespace AuthorizationPoliciesSample.Policies.Requirements;

public class MinimumAgeRequirement : IAuthorizationRequirement
{
    public MinimumAgeRequirement(int minimumAge) =>
        MinimumAge = minimumAge;

    public int MinimumAge { get; }
}

Jeśli zasady autoryzacji zawierają wiele wymagań dotyczących autoryzacji, wszystkie wymagania muszą zostać przekazane, aby ocena zasad powiodła się. Innymi słowy, wiele wymagań dotyczących autoryzacji dodanych do jednej zasady autoryzacji jest traktowanych na zasadzie AND .

Uwaga

Wymaganie nie musi mieć danych ani właściwości.

Programy obsługi autoryzacji

Procedura obsługi autoryzacji jest odpowiedzialna za ocenę właściwości wymagania. Procedura obsługi autoryzacji ocenia wymagania podane AuthorizationHandlerContext w celu określenia, czy dostęp jest dozwolony.

Wymaganie może mieć wiele procedur obsługi. Procedura obsługi może dziedziczyć AuthorizationHandler<TRequirement>element , gdzie TRequirement jest wymagane do obsługi. Alternatywnie program obsługi może zaimplementować IAuthorizationHandler bezpośrednio w celu obsługi więcej niż jednego typu wymagania.

Używanie programu obsługi dla jednego wymagania

W poniższym przykładzie przedstawiono relację jeden do jednego, w której program obsługi minimalnego wieku obsługuje jedno wymaganie:

using System.Security.Claims;
using AuthorizationPoliciesSample.Policies.Requirements;
using Microsoft.AspNetCore.Authorization;

namespace AuthorizationPoliciesSample.Policies.Handlers;

public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context, MinimumAgeRequirement requirement)
    {
        var dateOfBirthClaim = context.User.FindFirst(
            c => c.Type == ClaimTypes.DateOfBirth && c.Issuer == "http://contoso.com");

        if (dateOfBirthClaim is null)
        {
            return Task.CompletedTask;
        }

        var dateOfBirth = Convert.ToDateTime(dateOfBirthClaim.Value);
        int calculatedAge = DateTime.Today.Year - dateOfBirth.Year;
        if (dateOfBirth > DateTime.Today.AddYears(-calculatedAge))
        {
            calculatedAge--;
        }

        if (calculatedAge >= requirement.MinimumAge)
        {
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}

Powyższy kod określa, czy bieżący podmiot zabezpieczeń użytkownika ma datę urodzenia, która została wydana przez znanego i zaufanego wystawcę. Autoryzacja nie może wystąpić, gdy brakuje oświadczenia, w tym przypadku zostanie zwrócone ukończone zadanie. Gdy oświadczenie jest obecne, jest obliczany wiek użytkownika. Jeśli użytkownik spełnia minimalny wiek zdefiniowany przez wymaganie, autoryzacja zostanie uznana za pomyślną. Gdy autoryzacja zakończy się pomyślnie, context.Succeed zostanie wywołana z spełnionym wymaganiem jako jego jedynym parametrem.

Używanie programu obsługi dla wielu wymagań

W poniższym przykładzie przedstawiono relację jeden do wielu, w której program obsługi uprawnień może obsłużyć trzy różne typy wymagań:

using System.Security.Claims;
using AuthorizationPoliciesSample.Policies.Requirements;
using Microsoft.AspNetCore.Authorization;

namespace AuthorizationPoliciesSample.Policies.Handlers;

public class PermissionHandler : IAuthorizationHandler
{
    public Task HandleAsync(AuthorizationHandlerContext context)
    {
        var pendingRequirements = context.PendingRequirements.ToList();

        foreach (var requirement in pendingRequirements)
        {
            if (requirement is ReadPermission)
            {
                if (IsOwner(context.User, context.Resource)
                    || IsSponsor(context.User, context.Resource))
                {
                    context.Succeed(requirement);
                }
            }
            else if (requirement is EditPermission || requirement is DeletePermission)
            {
                if (IsOwner(context.User, context.Resource))
                {
                    context.Succeed(requirement);
                }
            }
        }

        return Task.CompletedTask;
    }

    private static bool IsOwner(ClaimsPrincipal user, object? resource)
    {
        // Code omitted for brevity
        return true;
    }

    private static bool IsSponsor(ClaimsPrincipal user, object? resource)
    {
        // Code omitted for brevity
        return true;
    }
}

Powyższy kod przechodzi PendingRequirements— właściwość zawierająca wymagania, które nie zostały oznaczone jako pomyślne. ReadPermission Aby uzyskać dostęp do żądanego zasobu, użytkownik musi być właścicielem lub sponsorem. Aby uzyskać dostęp EditPermissionDeletePermission do żądanego zasobu, musi być właścicielem lub.

Rejestracja programu obsługi

Zarejestruj programy obsługi w kolekcji usług podczas konfiguracji. Przykład:

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

Powyższy kod jest rejestrowany MinimumAgeHandler jako pojedynczy kod. Programy obsługi można zarejestrować przy użyciu dowolnego z wbudowanych okresów istnienia usługi.

Można powiązać zarówno wymaganie, jak i procedurę obsługi z pojedynczą klasą implementaną zarówno IAuthorizationRequirement , jak i IAuthorizationHandler. Ten zestaw tworzy ścisłe sprzężenie między programem obsługi a wymaganiem i jest zalecane tylko w przypadku prostych wymagań i procedur obsługi. Utworzenie klasy, która implementuje oba interfejsy, eliminuje konieczność zarejestrowania programu obsługi w usłudze DI ze względu na wbudowaną procedurę PassThroughAuthorizationHandler , która umożliwia samodzielne obsługę wymagań.

Zobacz klasę AssertionRequirement, aby zapoznać się z dobrym przykładem, w którym AssertionRequirement element jest wymaganiem i procedurą obsługi w całkowicie samodzielnej klasie.

Co powinno zwrócić program obsługi?

Zwróć uwagę, że Handle metoda w przykładzie procedury obsługi nie zwraca żadnej wartości. W jaki sposób jest wskazywany stan powodzenia lub niepowodzenia?

  • Procedura obsługi wskazuje powodzenie przez wywołanie context.Succeed(IAuthorizationRequirement requirement)metody , przekazując wymaganie, które zostało pomyślnie zweryfikowane.

  • Program obsługi nie musi ogólnie obsługiwać błędów, ponieważ inne procedury obsługi dla tego samego wymagania mogą się powieść.

  • Aby zagwarantować niepowodzenie, nawet jeśli inne procedury obsługi wymagań powiedzą się, wywołaj metodę context.Fail.

Jeśli wywołania context.Succeed programu obsługi lub context.Fail, wszystkie inne programy obsługi są nadal wywoływane. Pozwala to na generowanie skutków ubocznych, takich jak rejestrowanie, które odbywa się nawet wtedy, gdy inna procedura obsługi pomyślnie zweryfikowała lub nie powiodła się. Gdy jest ustawiona InvokeHandlersAfterFailure wartość false, właściwość zwariuje wykonywanie procedur obsługi, gdy context.Fail jest wywoływana. InvokeHandlersAfterFailure wartość domyślna to true, w którym przypadku wywoływane są wszystkie programy obsługi.

Uwaga

Procedury obsługi autoryzacji są wywoływane nawet w przypadku niepowodzenia uwierzytelniania. Programy obsługi mogą również być wykonywane w dowolnej kolejności, więc nie zależą od nich wywoływanych w żadnej określonej kolejności.

Dlaczego chcę, aby wiele procedur obsługi było wymaganych?

W przypadkach, w których ocena ma być oparta na or , zaimplementuj wiele procedur obsługi dla jednego wymagania. Na przykład firma Microsoft ma drzwi otwarte tylko za pomocą kart kluczowych. Jeśli opuścisz kartę klucza w domu, recepcjonista drukuje tymczasową naklejkę i otwiera drzwi dla Ciebie. W tym scenariuszu będziesz mieć jedno wymaganie, BuildingEntry, ale wiele procedur obsługi, z których każdy sprawdza jedno wymaganie.

BuildingEntryRequirement.cs

using Microsoft.AspNetCore.Authorization;

namespace AuthorizationPoliciesSample.Policies.Requirements;

public class BuildingEntryRequirement : IAuthorizationRequirement { }

BadgeEntryHandler.cs

using AuthorizationPoliciesSample.Policies.Requirements;
using Microsoft.AspNetCore.Authorization;

namespace AuthorizationPoliciesSample.Policies.Handlers;

public class BadgeEntryHandler : AuthorizationHandler<BuildingEntryRequirement>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context, BuildingEntryRequirement requirement)
    {
        if (context.User.HasClaim(
            c => c.Type == "BadgeId" && c.Issuer == "https://microsoftsecurity"))
        {
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}

TemporaryStickerHandler.cs

using AuthorizationPoliciesSample.Policies.Requirements;
using Microsoft.AspNetCore.Authorization;

namespace AuthorizationPoliciesSample.Policies.Handlers;

public class TemporaryStickerHandler : AuthorizationHandler<BuildingEntryRequirement>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context, BuildingEntryRequirement requirement)
    {
        if (context.User.HasClaim(
            c => c.Type == "TemporaryBadgeId" && c.Issuer == "https://microsoftsecurity"))
        {
            // Code to check expiration date omitted for brevity.
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}

Upewnij się, że oba programy obsługi są zarejestrowane. Jeśli którakolwiek z procedur obsługi zakończy się powodzeniem, gdy zasady ocenią BuildingEntryRequirementwartość , ocena zasad zakończy się powodzeniem.

Używanie func do spełnienia zasad

Mogą wystąpić sytuacje, w których spełnienie zasad jest proste do wyrażenia w kodzie. Podczas konfigurowania zasad za pomocą konstruktora RequireAssertion zasad można podać Func<AuthorizationHandlerContext, bool> element .

Na przykład poprzedni BadgeEntryHandler kod może zostać przepisany w następujący sposób:

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("BadgeEntry", policy =>
        policy.RequireAssertion(context => context.User.HasClaim(c =>
            (c.Type == "BadgeId" || c.Type == "TemporaryBadgeId")
            && c.Issuer == "https://microsoftsecurity")));
});

Uzyskiwanie dostępu do kontekstu żądania MVC w programach obsługi

Metoda HandleRequirementAsync ma dwa parametry: an AuthorizationHandlerContext i TRequirement obsługiwane. Struktury, takie jak MVC lub SignalR są bezpłatne, aby dodać dowolny obiekt do Resource właściwości w AuthorizationHandlerContext celu przekazania dodatkowych informacji.

W przypadku korzystania z routingu punktów końcowych autoryzacja jest zwykle obsługiwana przez oprogramowanie pośredniczące autoryzacji. W tym przypadku Resource właściwość jest wystąpieniem HttpContextklasy . Kontekst może służyć do uzyskiwania dostępu do bieżącego punktu końcowego, który może służyć do sondowania bazowego zasobu, do którego jest routing. Przykład:

if (context.Resource is HttpContext httpContext)
{
    var endpoint = httpContext.GetEndpoint();
    var actionDescriptor = endpoint.Metadata.GetMetadata<ControllerActionDescriptor>();
    ...
}

W przypadku tradycyjnego routingu lub gdy autoryzacja odbywa się w ramach filtru autoryzacji MVC, wartość Resource jest wystąpieniem AuthorizationFilterContext . Ta właściwość zapewnia dostęp do HttpContextelementów , RouteDatai wszystkich innych elementów udostępnianych przez MVC i Razor Pages.

Użycie Resource właściwości jest specyficzne dla platformy. Używanie informacji we Resource właściwości ogranicza zasady autoryzacji do określonych struktur. Rzutowanie Resource właściwości przy użyciu słowa kluczowego is , a następnie potwierdzenie, że rzutowanie powiodło się, aby upewnić się, że kod nie ulega awarii InvalidCastException podczas uruchamiania w innych strukturach:

// Requires the following import:
//     using Microsoft.AspNetCore.Mvc.Filters;
if (context.Resource is AuthorizationFilterContext mvcContext)
{
    // Examine MVC-specific things like routing data.
}

Globalnie wymaga uwierzytelnienia wszystkich użytkowników

Aby uzyskać informacje na temat globalnego wymagania uwierzytelnienia wszystkich użytkowników, zobacz Wymaganie uwierzytelnionych użytkowników.

Przykład autoryzacji z usługą zewnętrzną

Przykładowy kod w witrynie AspNetCore.Docs.Samples pokazuje, jak zaimplementować dodatkowe wymagania dotyczące autoryzacji za pomocą zewnętrznej usługi autoryzacji. Przykładowy Contoso.API projekt jest zabezpieczony za pomocą usługi Azure AD. Dodatkowa kontrola autoryzacji z Contoso.Security.API projektu zwraca ładunek opisujący, czy aplikacja kliencka Contoso.API może wywołać GetWeather interfejs API.

Konfigurowanie przykładu

  1. Utwórz rejestrację aplikacji w dzierżawie usługi Azure Active Directory (Azure AD):
  • Przypisz ją do aplikacji AppRole.
  • W obszarze Uprawnienia interfejsu API dodaj aplikację AppRole jako uprawnienie i przyznaj Administracja zgodę. Pamiętaj, że w tej konfiguracji ta rejestracja aplikacji reprezentuje zarówno interfejs API, jak i klienta wywołującego interfejs API. Jeśli chcesz, możesz utworzyć dwie rejestracje aplikacji. Jeśli używasz tej konfiguracji, pamiętaj, aby wykonać tylko uprawnienia interfejsu API, dodaj aplikację AppRole jako krok uprawnień tylko dla klienta. Tylko rejestracja aplikacji klienckiej wymaga wygenerowania wpisu tajnego klienta.
  1. Contoso.API Skonfiguruj projekt przy użyciu następujących ustawień:
{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "<Tenant name from AAD properties>.onmicrosoft.com">,
    "TenantId": "<Tenant Id from AAD properties>",
    "ClientId": "<Client Id from App Registration representing the API>"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}
  1. Skonfiguruj Contoso.Security.API przy użyciu następujących ustawień:
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "AllowedClients": [
    "<Use the appropriate Client Id representing the Client calling the API>"
  ]
}
  1. Zaimportuj plik ContosoAPI.postman_collection.json do narzędzia Postman i skonfiguruj środowisko przy użyciu następujących elementów:

    • ClientId: identyfikator klienta z rejestracji aplikacji reprezentujący klienta wywołującego interfejs API.
    • clientSecret: Wpis tajny klienta z rejestracji aplikacji reprezentujący klienta wywołującego interfejs API.
    • TenantId: Identyfikator dzierżawy z właściwości usługi AAD
  2. Uruchom rozwiązanie i użyj narzędzia Postman, aby wywołać interfejs API. Punkty przerwania można dodawać do Contoso.Security.API.SecurityPolicyController elementu i obserwować, że identyfikator klienta jest przekazywany w celu potwierdzenia, czy jest dozwolony do pobierania pogody.

Dodatkowe zasoby

Poniżej okładek, autoryzacja oparta na rolach i autoryzacja oparta na oświadczeniach używają wymagania, procedury obsługi wymagań i wstępnie skonfigurowanych zasad. Te bloki konstrukcyjne obsługują wyrażenie ocen autoryzacji w kodzie. Wynikiem jest bogatsza, wielokrotnego użytku struktura autoryzacji z możliwością testowania.

Zasady autoryzacji składają się z co najmniej jednego wymagania. Jest ona zarejestrowana w ramach konfiguracji usługi autoryzacji w metodzie Startup.ConfigureServices :

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();
    services.AddRazorPages();
    services.AddAuthorization(options =>
    {
        options.AddPolicy("AtLeast21", policy =>
            policy.Requirements.Add(new MinimumAgeRequirement(21)));
    });
}

W poprzednim przykładzie zostanie utworzona zasada "AtLeast21". Ma jedno wymaganie — minimalny wiek, który jest dostarczany jako parametr do wymagania.

IAuthorizationService

Podstawowa usługa określająca, czy autoryzacja zakończyła się pomyślnie, to IAuthorizationService:

/// <summary>
/// Checks policy based permissions for a user
/// </summary>
public interface IAuthorizationService
{
    /// <summary>
    /// Checks if a user meets a specific set of requirements for the specified resource
    /// </summary>
    /// <param name="user">The user to evaluate the requirements against.</param>
    /// <param name="resource">
    /// An optional resource the policy should be checked with.
    /// If a resource is not required for policy evaluation you may pass null as the value
    /// </param>
    /// <param name="requirements">The requirements to evaluate.</param>
    /// <returns>
    /// A flag indicating whether authorization has succeeded.
    /// This value is <value>true</value> when the user fulfills the policy; 
    /// otherwise <value>false</value>.
    /// </returns>
    /// <remarks>
    /// Resource is an optional parameter and may be null. Please ensure that you check 
    /// it is not null before acting upon it.
    /// </remarks>
    Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, 
                                     IEnumerable<IAuthorizationRequirement> requirements);

    /// <summary>
    /// Checks if a user meets a specific authorization policy
    /// </summary>
    /// <param name="user">The user to check the policy against.</param>
    /// <param name="resource">
    /// An optional resource the policy should be checked with.
    /// If a resource is not required for policy evaluation you may pass null as the value
    /// </param>
    /// <param name="policyName">The name of the policy to check against a specific 
    /// context.</param>
    /// <returns>
    /// A flag indicating whether authorization has succeeded.
    /// Returns a flag indicating whether the user, and optional resource has fulfilled 
    /// the policy.    
    /// <value>true</value> when the policy has been fulfilled; 
    /// otherwise <value>false</value>.
    /// </returns>
    /// <remarks>
    /// Resource is an optional parameter and may be null. Please ensure that you check
    /// it is not null before acting upon it.
    /// </remarks>
    Task<AuthorizationResult> AuthorizeAsync(
                                ClaimsPrincipal user, object resource, string policyName);
}

Powyższy kod wyróżnia dwie metody IAuthorizationService.

IAuthorizationRequirement jest usługą znacznikową bez metod i mechanizmem śledzenia, czy autoryzacja zakończyła się pomyślnie.

Każdy IAuthorizationHandler z nich jest odpowiedzialny za sprawdzenie, czy spełnione są wymagania:

/// <summary>
/// Classes implementing this interface are able to make a decision if authorization
/// is allowed.
/// </summary>
public interface IAuthorizationHandler
{
    /// <summary>
    /// Makes a decision if authorization is allowed.
    /// </summary>
    /// <param name="context">The authorization information.</param>
    Task HandleAsync(AuthorizationHandlerContext context);
}

Klasa AuthorizationHandlerContext jest używana przez program obsługi do oznaczania, czy zostały spełnione wymagania:

 context.Succeed(requirement)

Poniższy kod przedstawia uproszczoną (i oznaczona adnotacjami z komentarzami) domyślną implementację usługi autoryzacji:

public async Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, 
             object resource, IEnumerable<IAuthorizationRequirement> requirements)
{
    // Create a tracking context from the authorization inputs.
    var authContext = _contextFactory.CreateContext(requirements, user, resource);

    // By default this returns an IEnumerable<IAuthorizationHandlers> from DI.
    var handlers = await _handlers.GetHandlersAsync(authContext);

    // Invoke all handlers.
    foreach (var handler in handlers)
    {
        await handler.HandleAsync(authContext);
    }

    // Check the context, by default success is when all requirements have been met.
    return _evaluator.Evaluate(authContext);
}

Poniższy kod przedstawia typowy ConfigureServiceskod:

public void ConfigureServices(IServiceCollection services)
{
    // Add all of your handlers to DI.
    services.AddSingleton<IAuthorizationHandler, MyHandler1>();
    // MyHandler2, ...

    services.AddSingleton<IAuthorizationHandler, MyHandlerN>();

    // Configure your policies
    services.AddAuthorization(options =>
          options.AddPolicy("Something",
          policy => policy.RequireClaim("Permission", "CanViewPage", "CanViewAnything")));


    services.AddControllersWithViews();
    services.AddRazorPages();
}

Użyj polecenia IAuthorizationService lub [Authorize(Policy = "Something")] w celu autoryzacji.

Stosowanie zasad do kontrolera MVC

Jeśli używasz Razor stron, zobacz Stosowanie zasad do Razor stron w tym dokumencie.

Zasady są stosowane do kontrolerów przy użyciu atrybutu [Authorize] z nazwą zasad. Przykład:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

[Authorize(Policy = "AtLeast21")]
public class AlcoholPurchaseController : Controller
{
    public IActionResult Index() => View();
}

Stosowanie zasad do Razor stron

Zasady są stosowane do Razor stron przy użyciu atrybutu [Authorize] z nazwą zasad. Przykład:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;

[Authorize(Policy = "AtLeast21")]
public class AlcoholPurchaseModel : PageModel
{
}

Nie można stosować zasad na Razor poziomie procedury obsługi strony. Należy je zastosować do strony.

Zasady można stosować do Razor stron przy użyciu konwencji autoryzacji.

Wymagania

Wymaganie autoryzacji to kolekcja parametrów danych, których zasady mogą użyć do oceny bieżącego podmiotu zabezpieczeń użytkownika. W naszych zasadach "AtLeast21" wymaganie jest pojedynczym parametrem — minimalnym wiekiem. Wymaganie implementuje IAuthorizationRequirementinterfejs , który jest pustym interfejsem znacznika. Sparametryzowane wymaganie minimalnego wieku można zaimplementować w następujący sposób:

using Microsoft.AspNetCore.Authorization;

public class MinimumAgeRequirement : IAuthorizationRequirement
{
    public int MinimumAge { get; }

    public MinimumAgeRequirement(int minimumAge)
    {
        MinimumAge = minimumAge;
    }
}

Jeśli zasady autoryzacji zawierają wiele wymagań dotyczących autoryzacji, wszystkie wymagania muszą zostać przekazane, aby ocena zasad powiodła się. Innymi słowy, wiele wymagań dotyczących autoryzacji dodanych do jednej zasady autoryzacji jest traktowanych na zasadzie AND .

Uwaga

Wymaganie nie musi mieć danych ani właściwości.

Programy obsługi autoryzacji

Procedura obsługi autoryzacji jest odpowiedzialna za ocenę właściwości wymagania. Procedura obsługi autoryzacji ocenia wymagania podane AuthorizationHandlerContext w celu określenia, czy dostęp jest dozwolony.

Wymaganie może mieć wiele procedur obsługi. Procedura obsługi może dziedziczyć AuthorizationHandler<TRequirement>element , gdzie TRequirement jest wymagane do obsługi. Alternatywnie program obsługi może zaimplementować IAuthorizationHandler obsługę więcej niż jednego typu wymagania.

Używanie programu obsługi dla jednego wymagania

W poniższym przykładzie pokazano relację jeden do jednego, w której program obsługi minimalnego wieku korzysta z jednego wymagania:

using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using PoliciesAuthApp1.Services.Requirements;

public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
                                                   MinimumAgeRequirement requirement)
    {
        if (!context.User.HasClaim(c => c.Type == ClaimTypes.DateOfBirth &&
                                        c.Issuer == "http://contoso.com"))
        {
            //TODO: Use the following if targeting a version of
            //.NET Framework older than 4.6:
            //      return Task.FromResult(0);
            return Task.CompletedTask;
        }

        var dateOfBirth = Convert.ToDateTime(
            context.User.FindFirst(c => c.Type == ClaimTypes.DateOfBirth && 
                                        c.Issuer == "http://contoso.com").Value);

        int calculatedAge = DateTime.Today.Year - dateOfBirth.Year;
        if (dateOfBirth > DateTime.Today.AddYears(-calculatedAge))
        {
            calculatedAge--;
        }

        if (calculatedAge >= requirement.MinimumAge)
        {
            context.Succeed(requirement);
        }

        //TODO: Use the following if targeting a version of
        //.NET Framework older than 4.6:
        //      return Task.FromResult(0);
        return Task.CompletedTask;
    }
}

Powyższy kod określa, czy bieżący podmiot zabezpieczeń użytkownika ma datę urodzenia, która została wydana przez znanego i zaufanego wystawcę. Autoryzacja nie może wystąpić, gdy brakuje oświadczenia, w tym przypadku zostanie zwrócone ukończone zadanie. Gdy oświadczenie jest obecne, jest obliczany wiek użytkownika. Jeśli użytkownik spełnia minimalny wiek zdefiniowany przez wymaganie, autoryzacja zostanie uznana za pomyślną. Gdy autoryzacja zakończy się pomyślnie, context.Succeed zostanie wywołana z spełnionym wymaganiem jako jego jedynym parametrem.

Używanie programu obsługi dla wielu wymagań

W poniższym przykładzie przedstawiono relację jeden do wielu, w której program obsługi uprawnień może obsłużyć trzy różne typy wymagań:

using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using PoliciesAuthApp1.Services.Requirements;

public class PermissionHandler : IAuthorizationHandler
{
    public Task HandleAsync(AuthorizationHandlerContext context)
    {
        var pendingRequirements = context.PendingRequirements.ToList();

        foreach (var requirement in pendingRequirements)
        {
            if (requirement is ReadPermission)
            {
                if (IsOwner(context.User, context.Resource) ||
                    IsSponsor(context.User, context.Resource))
                {
                    context.Succeed(requirement);
                }
            }
            else if (requirement is EditPermission ||
                     requirement is DeletePermission)
            {
                if (IsOwner(context.User, context.Resource))
                {
                    context.Succeed(requirement);
                }
            }
        }

        //TODO: Use the following if targeting a version of
        //.NET Framework older than 4.6:
        //      return Task.FromResult(0);
        return Task.CompletedTask;
    }

    private bool IsOwner(ClaimsPrincipal user, object resource)
    {
        // Code omitted for brevity

        return true;
    }

    private bool IsSponsor(ClaimsPrincipal user, object resource)
    {
        // Code omitted for brevity

        return true;
    }
}

Powyższy kod przechodzi PendingRequirements— właściwość zawierająca wymagania, które nie zostały oznaczone jako pomyślne. ReadPermission Aby uzyskać dostęp do żądanego zasobu, użytkownik musi być właścicielem lub sponsorem. Aby uzyskać EditPermissionDeletePermission dostęp do żądanego zasobu, użytkownik musi być właścicielem lub.

Rejestracja programu obsługi

Programy obsługi są rejestrowane w kolekcji usług podczas konfiguracji. Przykład:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();
    services.AddRazorPages();
    services.AddAuthorization(options =>
    {
        options.AddPolicy("AtLeast21", policy =>
            policy.Requirements.Add(new MinimumAgeRequirement(21)));
    });

    services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();
}

Powyższy kod jest rejestrowany MinimumAgeHandler jako pojedynczy kod przez wywołanie services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();metody . Programy obsługi można zarejestrować przy użyciu dowolnego z wbudowanych okresów istnienia usługi.

Można powiązać zarówno wymaganie, jak i procedurę obsługi w jednej klasie implementowania zarówno , jak IAuthorizationRequirement i IAuthorizationHandler. Ten zestaw tworzy ścisłe sprzężenie między programem obsługi a wymaganiem i jest zalecane tylko w przypadku prostych wymagań i procedur obsługi. Utworzenie klasy, która implementuje oba interfejsy, eliminuje konieczność zarejestrowania programu obsługi w usłudze DI ze względu na wbudowaną procedurę PassThroughAuthorizationHandler , która umożliwia samodzielne obsługę wymagań.

Zobacz klasę AssertionRequirement, aby zapoznać się z dobrym przykładem, w którym AssertionRequirement element jest wymaganiem i procedurą obsługi w całkowicie samodzielnej klasie.

Co powinno zwrócić program obsługi?

Zwróć uwagę, że Handle metoda w przykładzie procedury obsługi nie zwraca żadnej wartości. W jaki sposób jest wskazywany stan powodzenia lub niepowodzenia?

  • Procedura obsługi wskazuje powodzenie przez wywołanie context.Succeed(IAuthorizationRequirement requirement)metody , przekazując wymaganie, które zostało pomyślnie zweryfikowane.

  • Program obsługi nie musi ogólnie obsługiwać błędów, ponieważ inne procedury obsługi dla tego samego wymagania mogą się powieść.

  • Aby zagwarantować niepowodzenie, nawet jeśli inne procedury obsługi wymagań powiedzą się, wywołaj metodę context.Fail.

Jeśli wywołania context.Succeed programu obsługi lub context.Fail, wszystkie inne programy obsługi są nadal wywoływane. Pozwala to na generowanie skutków ubocznych, takich jak rejestrowanie, które odbywa się nawet wtedy, gdy inna procedura obsługi pomyślnie zweryfikowała lub nie powiodła się. Gdy jest ustawiona InvokeHandlersAfterFailure wartość false, właściwość zwariuje wykonywanie procedur obsługi, gdy context.Fail jest wywoływana. InvokeHandlersAfterFailure wartość domyślna to true, w którym przypadku wywoływane są wszystkie programy obsługi.

Uwaga

Procedury obsługi autoryzacji są wywoływane nawet w przypadku niepowodzenia uwierzytelniania.

Dlaczego chcę, aby wiele procedur obsługi było wymaganych?

W przypadkach, w których ocena ma być oparta na or , zaimplementuj wiele procedur obsługi dla jednego wymagania. Na przykład firma Microsoft ma drzwi otwarte tylko za pomocą kart kluczowych. Jeśli opuścisz kartę klucza w domu, recepcjonista drukuje tymczasową naklejkę i otwiera drzwi dla Ciebie. W tym scenariuszu będziesz mieć jedno wymaganie, BuildingEntry, ale wiele procedur obsługi, z których każdy sprawdza jedno wymaganie.

BuildingEntryRequirement.cs

using Microsoft.AspNetCore.Authorization;

public class BuildingEntryRequirement : IAuthorizationRequirement
{
}

BadgeEntryHandler.cs

using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using PoliciesAuthApp1.Services.Requirements;

public class BadgeEntryHandler : AuthorizationHandler<BuildingEntryRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
                                                   BuildingEntryRequirement requirement)
    {
        if (context.User.HasClaim(c => c.Type == "BadgeId" &&
                                       c.Issuer == "http://microsoftsecurity"))
        {
            context.Succeed(requirement);
        }

        //TODO: Use the following if targeting a version of
        //.NET Framework older than 4.6:
        //      return Task.FromResult(0);
        return Task.CompletedTask;
    }
}

TemporaryStickerHandler.cs

using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using PoliciesAuthApp1.Services.Requirements;

public class TemporaryStickerHandler : AuthorizationHandler<BuildingEntryRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, 
                                                   BuildingEntryRequirement requirement)
    {
        if (context.User.HasClaim(c => c.Type == "TemporaryBadgeId" &&
                                       c.Issuer == "https://microsoftsecurity"))
        {
            // We'd also check the expiration date on the sticker.
            context.Succeed(requirement);
        }

        //TODO: Use the following if targeting a version of
        //.NET Framework older than 4.6:
        //      return Task.FromResult(0);
        return Task.CompletedTask;
    }
}

Upewnij się, że oba programy obsługi są zarejestrowane. Jeśli którakolwiek z procedur obsługi zakończy się powodzeniem, gdy zasady ocenią BuildingEntryRequirementwartość , ocena zasad zakończy się powodzeniem.

Używanie func do spełnienia zasad

Mogą wystąpić sytuacje, w których spełnienie zasad jest proste do wyrażenia w kodzie. Można podać element Func<AuthorizationHandlerContext, bool> podczas konfigurowania zasad za pomocą konstruktora RequireAssertion zasad.

Na przykład poprzedni BadgeEntryHandler kod może zostać przepisany w następujący sposób:

services.AddAuthorization(options =>
{
     options.AddPolicy("BadgeEntry", policy =>
        policy.RequireAssertion(context =>
            context.User.HasClaim(c =>
                (c.Type == "BadgeId" ||
                 c.Type == "TemporaryBadgeId") &&
                 c.Issuer == "https://microsoftsecurity")));
});

Uzyskiwanie dostępu do kontekstu żądania MVC w programach obsługi

Metoda HandleRequirementAsync zaimplementowana w procedurze obsługi autoryzacji ma dwa parametry: an AuthorizationHandlerContext i obsługiwaną TRequirement przez Ciebie. Struktury, takie jak MVC lub SignalR są bezpłatne, aby dodać dowolny obiekt do Resource właściwości w AuthorizationHandlerContext celu przekazania dodatkowych informacji.

W przypadku korzystania z routingu punktów końcowych autoryzacja jest zwykle obsługiwana przez oprogramowanie pośredniczące autoryzacji. W tym przypadku Resource właściwość jest wystąpieniem HttpContextklasy . Kontekst może służyć do uzyskiwania dostępu do bieżącego punktu końcowego, który może służyć do sondowania bazowego zasobu, do którego jest routing. Przykład:

if (context.Resource is HttpContext httpContext)
{
    var endpoint = httpContext.GetEndpoint();
    var actionDescriptor = endpoint.Metadata.GetMetadata<ControllerActionDescriptor>();
    ...
}

W przypadku tradycyjnego routingu lub gdy autoryzacja odbywa się w ramach filtru autoryzacji MVC, wartość Resource jest wystąpieniem AuthorizationFilterContext . Ta właściwość zapewnia dostęp do HttpContextelementów , RouteDatai wszystkich innych elementów udostępnianych przez MVC i Razor Pages.

Użycie Resource właściwości jest specyficzne dla platformy. Używanie informacji we Resource właściwości ogranicza zasady autoryzacji do określonych struktur. Rzutowanie Resource właściwości przy użyciu słowa kluczowego is , a następnie potwierdzenie, że rzutowanie powiodło się, aby upewnić się, że kod nie ulega awarii InvalidCastException podczas uruchamiania w innych strukturach:

// Requires the following import:
//     using Microsoft.AspNetCore.Mvc.Filters;
if (context.Resource is AuthorizationFilterContext mvcContext)
{
    // Examine MVC-specific things like routing data.
}

Globalnie wymaga uwierzytelnienia wszystkich użytkowników

Aby uzyskać informacje na temat globalnego wymagania uwierzytelnienia wszystkich użytkowników, zobacz Wymaganie uwierzytelnionych użytkowników.