Dela via


Principbaserad auktorisering i ASP.NET Core

Under huven använder rollbaserad auktorisering och anspråksbaserad auktorisering ett krav, en kravhanterare och en förkonfigurerad strategi. Dessa byggstenar stöder uttrycket för auktoriseringsutvärderingar i kod. Resultatet är en rikare, återanvändbar, testbar auktoriseringsstruktur.

En auktoriseringsprincip består av ett eller flera krav. Registrera den som en del av konfigurationen av auktoriseringstjänsten i appens Program.cs-fil:

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

I föregående exempel skapas en "AtLeast21"-princip. Den har ett enda krav – en minimiålder, som anges som en parameter till kravet.

IAuthorizationService

Den primära tjänsten som avgör om auktoriseringen lyckas är 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);
}

Föregående kod visar de två metoderna i IAuthorizationService.

IAuthorizationRequirement är en markörtjänst utan några metoder och mekanismen för att övervaka om auktoriseringen lyckas.

Varje IAuthorizationHandler ansvarar för att kontrollera om kraven uppfylls:

/// <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);
}

Klassen AuthorizationHandlerContext är vad hanteraren använder för att markera om kraven har uppfyllts:

 context.Succeed(requirement)

Följande kod visar den förenklade (och kommenterade med kommentarer) standardimplementeringen av auktoriseringstjänsten:

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<IAuthorizationHandler> 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);
}

Följande kod visar en typisk auktoriseringstjänstkonfiguration:

// 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")));

Använd IAuthorizationService, [Authorize(Policy = "Something")]eller RequireAuthorization("Something") för auktorisering.

Tillämpa principer på MVC-styrenheter

För appar som använder Razor Pages, se avsnittet Tillämpa policyer på Razor Sidor.

Tillämpa principer på kontrollanter med hjälp av attributet [Authorize] med principnamnet:

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

Om flera principer tillämpas på kontrollant- och åtgärdsnivå måste alla principer passera innan åtkomst beviljas:

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

Tillämpa principer på Razor sidor

Tillämpa principer på Razor pages med hjälp av attributet [Authorize] med principnamnet. Till exempel:

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

namespace AuthorizationPoliciesSample.Pages;

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

Riktlinjer kan inte tillämpas på Razor sidhanterarnivå, de måste tillämpas på sidan.

Policyer kan också tillämpas på Razor Sidor med hjälp av en auktoriseringskonvention.

Tillämpa principer på slutpunkter

Tillämpa principer på slutpunkter med hjälp av RequireAuthorization med principnamnet. Till exempel:

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

Krav

Ett auktoriseringskrav är en samling dataparametrar som en policy kan använda för att utvärdera den aktuella användarens instans. I vår "AtLeast21"-princip är kravet en enda parameter – minimiåldern. Ett krav implementerar IAuthorizationRequirement, som är ett tomt markörgränssnitt. Ett parametriserat minimikrav för minimiålder kan implementeras på följande sätt:

using Microsoft.AspNetCore.Authorization;

namespace AuthorizationPoliciesSample.Policies.Requirements;

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

    public int MinimumAge { get; }
}

Om en auktoriseringsprincip innehåller flera auktoriseringskrav måste alla krav uppfyllas för att principutvärderingen ska lyckas. Med andra ord behandlas flera auktoriseringskrav som läggs till en enda auktoriseringsprincip på en OCH basis.

Notera

Ett krav behöver inte ha data eller egenskaper.

Auktoriseringshanterare

En auktoriseringshanterare ansvarar för utvärderingen av ett kravs egenskaper. Auktoriseringshanteraren utvärderar kraven mot en angivna AuthorizationHandlerContext för att avgöra om åtkomst tillåts.

Ett krav kan ha flera hanterare. En hanterare kan ärva AuthorizationHandler<TRequirement>, där TRequirement är kravet som ska hanteras. En hanterare kan också implementera IAuthorizationHandler direkt för att hantera mer än en typ av krav.

Använd en hanterare för ett krav

I följande exempel visas en en-till-en-relation där en minimiåldershanterare hanterar ett enda krav:

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;
    }
}

Föregående kod avgör om det aktuella användarens huvudnamn har ett födelsedatum som har utfärdats av en känd och betrodd utfärdare. Auktorisering kan inte utföras när anspråket saknas, i så fall returneras en slutförd uppgift. När ett anspråk finns beräknas användarens ålder. Om användaren uppfyller den lägsta ålder som definieras av kravet anses auktoriseringen vara lyckad. När auktoriseringen lyckas anropas context.Succeed med det uppfyllda kravet som enda parameter.

Använda en hanterare för flera krav

I följande exempel visas en en-till-många-relation där en behörighetshanterare kan hantera tre olika typer av krav:

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;
    }
}

Föregående kod passerar PendingRequirements– en egenskap som innehåller krav som inte har markerats som lyckade. För ett ReadPermission krav måste användaren antingen vara ägare eller sponsor för att få åtkomst till den begärda resursen. För ett EditPermission- eller DeletePermission krav måste de vara ägare för att få åtkomst till den begärda resursen.

Handlerregistrering

Registrera hanterare i tjänstesamlingen under konfigurationen. Till exempel:

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

Föregående kod registrerar MinimumAgeHandler som en singleton. Hanterare kan registreras med vilken som helst av de inbyggda tjänstlivslängderna.

Det är möjligt att paketera både ett krav och en hanterare i en enda klass som implementerar både IAuthorizationRequirement och IAuthorizationHandler. Den här kombinationskopplingen skapar en nära koppling mellan hanteraren och kraven och rekommenderas endast för enkla krav och hanterare. Att skapa en klass som implementerar båda gränssnitten tar bort behovet av att registrera hanteraren i DI på grund av den inbyggda PassThroughAuthorizationHandler som gör att kraven kan hantera sig själva.

Se klassen AssertionRequirement för ett bra exempel där AssertionRequirement är både ett krav och hanteraren i en helt fristående klass.

Vad ska en hanterare returnera?

Observera att metoden Handle i -hanterarexemplet inte returnerar något värde. Hur anges statusen för antingen lyckad eller misslyckad?

  • En hanterare anger framgång genom att anropa context.Succeed(IAuthorizationRequirement requirement)och överföra det krav som framgångsrikt har verifierats.

  • En hanterare behöver inte hantera fel i allmänhet, eftersom andra hanterare för samma krav kan lyckas.

  • Om du vill garantera misslyckande, även om andra hanterare av krav lyckas, anropar du context.Fail.

Om en hanterare anropar context.Succeed eller context.Failanropas fortfarande alla andra hanterare. Detta gör att kraven kan ge biverkningar, till exempel loggning, som sker även om en annan hanterare har verifierat eller misslyckats med ett krav. När värdet är inställt på falsekortsluter egenskapen InvokeHandlersAfterFailure körningen av hanterare när context.Fail anropas. InvokeHandlersAfterFailure återgår till true, i vilket fall alla hanterare anropas.

Not

Auktoriseringshanterare anropas även om autentiseringen misslyckas. Hanterare kan också köras i valfri ordning, så ska inte vara beroende av att de anropas i någon viss ordning.

Varför skulle jag vilja ha flera hanterare för ett krav?

Om du vill att utvärderingen ska vara ELLER bör du implementera flera hanterare för ett enda krav. Microsoft har till exempel dörrar som bara öppnas med nyckelkort. Om du lämnar ditt nyckelkort hemma skriver receptionisten ut ett tillfälligt klistermärke och öppnar dörren åt dig. I det här scenariot skulle du ha ett enda krav, BuildingEntry, men flera hanterare, var och en undersöker ett enda krav.

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;
    }
}

Kontrollera att båda hanterarna är registrerade. Om någon av hanterarna lyckas när en policy utvärderar BuildingEntryRequirement, lyckas policyutvärderingen.

Använd en funktion för att uppfylla en policy

Det kan finnas situationer där det är enkelt att uppfylla en princip i koden. Du kan ange en Func<AuthorizationHandlerContext, bool> när du konfigurerar en princip med principverktyget RequireAssertion.

Till exempel kan föregående BadgeEntryHandler skrivas om på följande sätt:

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")));
});

Åtkomst till MVC-begärandekontext i hanterare

Metoden HandleRequirementAsync har två parametrar: en AuthorizationHandlerContext och TRequirement som hanteras. Ramverk som MVC eller SignalR kan lägga till objekt i egenskapen ResourceAuthorizationHandlerContext för att skicka extra information.

När du använder slutpunktsroutning hanteras auktorisering vanligtvis av auktoriseringsmellanprogrammet. I det här fallet är egenskapen Resource ett exemplar av HttpContext. Kontexten kan användas för att komma åt den aktuella slutpunkten, som kan användas för att avsöka den underliggande resurs som du dirigerar till. Till exempel:

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

Med traditionell routning, eller när auktorisering sker som en del av MVC:s auktoriseringsfilter, är värdet för Resource en AuthorizationFilterContext instans. Den här egenskapen ger åtkomst till HttpContext, RouteDataoch allt annat som tillhandahålls av MVC och Razor Pages.

Användningen av egenskapen Resource är ramverksspecifik. Om du använder information i egenskapen Resource begränsas dina auktoriseringsprinciper till vissa ramverk. Casta egenskapen Resource med nyckelordet is och bekräfta sedan att casten har lyckats för att se till att koden inte kraschar med en InvalidCastException när den körs på andra ramverk:

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

Globalt kräver alla användare att autentiseras

Information om hur du globalt kräver att alla användare autentiseras finns i Kräv autentiserade användare.

Auktorisering med externt tjänstexempel

Exempelkoden på AspNetCore.Docs.Samples visar hur du implementerar ytterligare auktoriseringskrav med en extern auktoriseringstjänst. Exempelprojektet Contoso.API skyddas med Azure AD-. En ytterligare auktoriseringskontroll från Contoso.Security.API-projektet returnerar en nyttolast som beskriver om Contoso.API-klientappen kan anropa GetWeather-API:et.

Konfigurera exemplet

  • Skapa en programregistrering i din Microsoft Entra ID-klientorganisation:

  • Tilldela den en AppRole.

  • Under API-behörigheter lägger du till AppRole som en behörighet och beviljar administratörsmedgivande. Observera att den här appregistreringen i den här konfigurationen representerar både API:et och klienten som anropar API:et. Om du vill kan du skapa två appregistreringar. Om du använder den här konfigurationen ska du bara utföra API-behörigheterna och lägga till AppRole som ett behörighetssteg för endast klienten. Endast klientappregistreringen kräver att en klienthemlighet genereras.

  • Konfigurera Contoso.API projektet med följande inställningar:

{
  "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": "*"
}
  • Konfigurera Contoso.Security.API med följande inställningar:
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "AllowedClients": [
    "<Use the appropriate Client Id representing the Client calling the API>"
  ]
}
  • Öppna filen ContosoAPI.collection.json och konfigurera en miljö med följande:

    • ClientId: Klient-ID från appregistrering som representerar klienten som anropar API:et.
    • clientSecret: Klienthemlighet från appregistrering som representerar klienten som anropar API:et.
    • TenantId: Hyresgästs-ID från AAD-egenskaper
  • Extrahera kommandona från filen ContosoAPI.collection.json och använd dem för att konstruera cURL-kommandon för att testa appen.

  • Kör lösningen och använd cURL- för att anropa API:et. Du kan lägga till brytpunkter i Contoso.Security.API.SecurityPolicyController och observera att klient-ID:t skickas, vilket används för att kontrollera om det är tillåtet att hämta väder.

Ytterligare resurser

Bakom kulisserna använder rollbaserad auktorisering och anspråksbaserad auktorisering ett krav, en kravhanterare och en förkonfigurerad policy. Dessa byggstenar stöder uttrycket för auktoriseringsutvärderingar i kod. Resultatet är en rikare, återanvändbar, testbar auktoriseringsstruktur.

En auktoriseringsprincip består av ett eller flera krav. Den registreras som en del av konfigurationen av auktoriseringstjänsten i metoden Startup.ConfigureServices:

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

I föregående exempel skapas en "AtLeast21"-princip. Den har ett enda krav – en minimiålder, som anges som en parameter till kravet.

IAuthorizationService

Den primära tjänsten som avgör om auktoriseringen lyckas är 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);
}

Föregående kod visar de två metoderna i IAuthorizationService.

IAuthorizationRequirement är en markörtjänst utan metoder och mekanismen för att spåra om auktoriseringen lyckas.

Varje IAuthorizationHandler ansvarar för att kontrollera om kraven uppfylls:

/// <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);
}

Klassen AuthorizationHandlerContext är vad hanteraren använder för att markera om kraven har uppfyllts:

 context.Succeed(requirement)

Följande kod visar den förenklade (och kommenterade med kommentarer) standardimplementeringen av auktoriseringstjänsten:

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);
}

Följande kod visar en typisk ConfigureServices:

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();
}

Använd IAuthorizationService eller [Authorize(Policy = "Something")] för auktorisering.

Tillämpa principer på MVC-styrenhet

Om du använder Razor sidorna kan du läsa Tillämpa policyer på Razor sidorna i det här dokumentet.

Principer tillämpas på kontrollanter med hjälp av attributet [Authorize] med principnamnet. Till exempel:

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

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

Tillämpa policyer på Razor sidor

Principer tillämpas på Razor Pages med hjälp av attributet [Authorize] med principnamnet. Till exempel:

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

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

Principer kan inte tillämpas på Razor sidhanterarnivå, de måste tillämpas på sidan.

Principer kan tillämpas på Razor sidor med hjälp av en auktoriseringskonvention.

Krav

Ett auktoriseringskrav är en samling dataparametrar som en policy kan använda för att utvärdera den aktuella användarens säkerhetsidentitet. I vår "AtLeast21"-princip är kravet en enda parameter – minimiåldern. Ett krav implementerar IAuthorizationRequirement, som är ett tomt markörgränssnitt. Ett parametriserat minimikrav för minimiålder kan implementeras på följande sätt:

using Microsoft.AspNetCore.Authorization;

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

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

Om en auktoriseringsprincip innehåller flera auktoriseringskrav måste alla krav uppfyllas för att principutvärderingen ska lyckas. Med andra ord behandlas flera auktoriseringskrav som läggs till i en enda auktoriseringsprincip på ett OCH-sätt.

Observera

Ett krav behöver inte ha data eller egenskaper.

Auktoriseringshanterare

En auktoriseringshanterare ansvarar för utvärderingen av ett kravs egenskaper. Auktoriseringshanteraren utvärderar kraven mot en angivna AuthorizationHandlerContext för att avgöra om åtkomst tillåts.

Ett krav kan ha flera hanterare. En hanterare kan ärva AuthorizationHandler<TRequirement>, där TRequirement är kravet som ska hanteras. En hanterare kan också implementera IAuthorizationHandler för att hantera fler än en typ av krav.

Använda en hanterare för ett krav

I följande exempel visas en en-till-en-relation där en minimiåldershanterare använder ett enda krav:

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;
    }
}

Föregående kod avgör om det aktuella användarens huvudnamn har ett födelsedatum som har utfärdats av en känd och betrodd utfärdare. Auktorisering kan inte ske när anspråket saknas, då returneras ett fullbordat uppdrag. När ett anspråk finns beräknas användarens ålder. Om användaren uppfyller den lägsta ålder som definieras av kravet anses auktoriseringen vara lyckad. När auktoriseringen lyckas anropas context.Succeed med det uppfyllda kravet som enda parameter.

Använda en hanterare för flera krav

I följande exempel visas en en-till-många-relation där en behörighetshanterare kan hantera tre olika typer av krav:

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;
    }
}

Föregående kod passerar PendingRequirements– en egenskap som innehåller krav som inte har markerats som lyckade. För ett ReadPermission krav måste användaren antingen vara ägare eller sponsor för att få åtkomst till den begärda resursen. För ett EditPermission eller DeletePermission krav måste användaren vara ägare för att få åtkomst till den begärda resursen.

Handlerregistrering

Hanterare registreras i tjänstesamlingen under konfigurationsprocessen. Till exempel:

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>();
}

Föregående kod registrerar MinimumAgeHandler som en singleton genom att anropa services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();. Hanterare kan registreras med valfri av de inbyggda tjänstelivstiderna.

Det är möjligt att kombinera både ett krav och en hanterare i en enda klass som implementerar både IAuthorizationRequirement och IAuthorizationHandler. Den här kombinationskopplingen skapar en nära koppling mellan hanteraren och kraven och rekommenderas endast för enkla krav och hanterare. Att skapa en klass som implementerar båda gränssnitten tar bort behovet av att registrera hanteraren i DI på grund av den inbyggda PassThroughAuthorizationHandler som gör att kraven kan hantera sig själva.

Se klassen AssertionRequirement för ett bra exempel där AssertionRequirement fungerar både som ett krav och en hanterare i en helt självständig klass.

Vad ska en hanterare returnera?

Observera att metoden Handle i -hanterarexemplet inte returnerar något värde. Hur anges statusen för antingen lyckad eller misslyckad?

  • En hanterare anger framgång genom att anropa context.Succeed(IAuthorizationRequirement requirement), och överföra det krav som har verifierats framgångsrikt.

  • En hanterare behöver inte hantera fel i allmänhet, eftersom andra hanterare för samma krav kan lyckas.

  • För att garantera misslyckande, även om andra kravhanterare lyckas, anropa context.Fail.

Om en hanterare anropar context.Succeed eller context.Failanropas fortfarande alla andra hanterare. Detta gör att kraven kan ge biverkningar, till exempel loggning, som sker även om en annan hanterare har verifierat eller misslyckats med ett krav. När värdet är inställt på falsekortsluter egenskapen InvokeHandlersAfterFailure körningen av hanterare när context.Fail anropas. InvokeHandlersAfterFailure har standardvärdet true, i vilket fall alla hanterare anropas.

Notera

Auktoriseringshanterare anropas även om autentiseringen misslyckas.

Varför skulle jag vilja ha flera hanterare för ett krav?

Om du vill att utvärderingen ska vara ELLER bör du implementera flera hanterare för ett enda krav. Microsoft har till exempel dörrar som bara öppnas med nyckelkort. Om du lämnar ditt nyckelkort hemma skriver receptionisten ut ett tillfälligt klistermärke och öppnar dörren åt dig. I det här scenariot skulle du ha ett enda krav, BuildingEntry, men flera hanterare, var och en undersöker ett enda krav.

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;
    }
}

Kontrollera att båda hanterarna är registrerade. Om någon av hanterarna lyckas när en policy utvärderar BuildingEntryRequirement, lyckas utvärderingen av policyn.

Använd en funktion för att uppfylla en policy

Det kan finnas situationer där det är enkelt att uppfylla en princip i koden. Du kan ange en Func<AuthorizationHandlerContext, bool> när du konfigurerar din policy med principverktyget RequireAssertion.

Till exempel kan föregående BadgeEntryHandler skrivas om på följande sätt:

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

Åtkomst till MVC-begärandekontext i hanterare

Den HandleRequirementAsync metod som du implementerar i en auktoriseringshanterare har två parametrar: en AuthorizationHandlerContext och den TRequirement du hanterar. Ramverk som MVC eller SignalR kan lägga till objekt i egenskapen ResourceAuthorizationHandlerContext för att skicka extra information.

När du använder slutpunktsroutning hanteras auktorisering vanligtvis av auktoriseringsmellanprogrammet. I det här fallet är egenskapen Resource en instans av HttpContext. Kontexten kan användas för att komma åt den aktuella slutpunkten, som kan användas för att avsöka den underliggande resurs som du dirigerar till. Till exempel:

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

Med traditionell routning, eller när auktorisering sker som en del av MVC:s auktoriseringsfilter, är värdet för Resource en AuthorizationFilterContext instans. Den här egenskapen ger åtkomst till HttpContext, RouteDataoch allt annat som tillhandahålls av MVC och Razor Pages.

Användningen av egenskapen Resource är ramverksspecifik. Om du använder information i egenskapen Resource begränsas dina auktoriseringsprinciper till vissa ramverk. Casta egenskapen Resource med nyckelordet is och bekräfta sedan att casten har lyckats för att se till att koden inte kraschar med en InvalidCastException när den körs på andra ramverk:

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

Globalt kräver alla användare att autentiseras

Information om hur du globalt kräver att alla användare autentiseras finns i Kräv autentiserade användare.