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.Fail
anropas 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å false
kortsluter 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 Resource
på AuthorizationHandlerContext
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
, RouteData
och 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.Fail
anropas 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å false
kortsluter 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 Resource
på AuthorizationHandlerContext
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
, RouteData
och 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.
ASP.NET Core