Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Une stratégie d’autorisation ASP.NET Core est un ensemble d’une ou plusieurs exigences d’autorisation que l’infrastructure évalue pour déterminer si un utilisateur est autorisé à accéder à une ressource. Une stratégie peut être inscrite avec un nom et appliquée aux ressources qui en ont besoin.
Cet article explique :
- Comment exiger des utilisateurs authentifiés par défaut.
- Comment créer des exigences.
- Comment inscrire et appliquer des stratégies.
- Comment les stratégies nommées, par défaut et de secours sont sélectionnées.
- Gestionnaires d’autorisation pour l’évaluation des exigences uniques et multiples.
- Comment plusieurs exigences dans une stratégie unique sont évaluées.
Une politique nommée est appliquée au moyen de [Authorize(Policy = "...")] (Razor composants, pages et contrôleurs) ou de RequireAuthorization(...) (points de terminaison), et l’infrastructure utilise des gestionnaires pour évaluer les exigences associées à une politique.
IAuthorizationPolicyProvider(Les fournisseurs de stratégies d’autorisation personnalisées dans ASP.NET Core documentation) génèrent dynamiquement des stratégies au lieu de les inscrire au démarrage de l’application.
L’autorisation basée sur les rôles et l’autorisation basée sur les revendications utilisent une exigence, un gestionnaire de conditions requises et une stratégie d’autorisation préconfigurée. Ces blocs de construction prennent en charge l’expression des évaluations d’autorisation dans le code.
Cet article utilise des exemples de composants Razor et se concentre sur les scénarios d’autorisation Blazor pour ASP.NET Core 3.1 ou version ultérieure. Pour Razor obtenir des conseils sur les pages et MVC qui s’appliquent à toutes les versions de ASP.NET Core, consultez les ressources suivantes après avoir lu cet article :
- Autorisation basée sur des stratégies dans ASP.NET Core Razor Pages
- Autorisation basée sur une stratégie dans ASP.NET Core MVC
Certains exemples de cet article (ASP.NET Core 8.0 ou version ultérieure) utilisent des constructeurs principaux, disponibles en C# 12 (.NET 8) ou version ultérieure. Pour plus d’informations, consultez Déclarer des constructeurs principaux pour les classes et les structs (didacticiel de documentation C#) et les constructeurs principaux (Guide C#).
Exiger une authentification utilisateur globale
Pour une application côté serveur où la plupart ou tous les points de terminaison nécessitent une authentification, définissez une stratégie de secours qui nécessite un utilisateur authentifié. Cette approche sécurisée par défaut protège les points de terminaison nouvellement ajoutés qui ne spécifient pas de métadonnées d’autorisation.
La stratégie de secours, et non la stratégie par défaut, s’applique aux points de terminaison qui ne spécifient pas de métadonnées d’autorisation. La stratégie par défaut s’applique lorsqu’un point de terminaison le sélectionne avec [Authorize] ou RequireAuthorization() sans nom de stratégie. Pour connaître les règles de sélection de stratégie complètes, consultez la section Stratégies par défaut et de secours .
var requireAuthPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
builder.Services.AddAuthorizationBuilder()
.SetFallbackPolicy(requireAuthPolicy);
builder.Services.AddAuthorization(options =>
{
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});
Dans Startup.ConfigureServices :
services.AddAuthorization(options =>
{
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});
Appliquez [AllowAnonymous] ou appelez AllowAnonymous() pour les points de terminaison délibérément publics.
La stratégie de repli s’applique aux requêtes traitées par le middleware d’autorisation. Par exemple:
- Une requête qui ne correspond à aucun endpoint utilise la stratégie de repli si l’intergiciel d’autorisation s’exécute pour cette requête.
- Les fichiers statiques servis par l’intergiciel de fichiers statiques avant l’intergiciel d’autorisation ne sont pas protégés par la stratégie de repli.
- Les points de terminaison publics peuvent dépendre des ressources statiques qui doivent également autoriser l’accès anonyme.
Pour plus d’informations, consultez Fichiers statiques dans ASP.NET Core et modèles d’autorisation des applications côté Blazor serveur. Les applications Blazor WebAssembly ne prennent pas en charge une stratégie d’autorisation de secours côté serveur. Pour connaître Blazor WebAssembly les modèles d’autorisation, consultez Secure ASP.NET Core Blazor WebAssembly.
Stratégies par défaut et de secours
Le middleware d’autorisation regroupe les métadonnées d’autorisation associées à un point de terminaison en une stratégie. Le tableau suivant décrit comment les métadonnées déterminent la stratégie utilisée :
| Métadonnées d’autorisation | Stratégie ou comportement |
|---|---|
| None |
AuthorizationOptions.FallbackPolicy est utilisé, s’il est configuré. Par défaut, la stratégie de secours est null, donc l’autorisation n’est pas requise. |
[Authorize] ou RequireAuthorization() sans nom de stratégie |
AuthorizationOptions.DefaultPolicy est utilisé, à moins que le point de terminaison n’ait également une instance AuthorizationPolicy explicite. Par défaut, la stratégie par défaut nécessite un utilisateur authentifié. |
[Authorize(Policy = "{POLICY NAME}")] ou RequireAuthorization("{POLICY NAME}") |
La stratégie nommée est utilisée. |
[Authorize(Roles = "{ROLES}")] |
Une stratégie est générée avec les rôles spécifiés. La stratégie par défaut n’est pas ajoutée pour cette déclaration d’autorisation. |
RequireAuthorization(policy) avec une AuthorizationPolicy instance |
La stratégie explicite est utilisée. Si des métadonnées de stratégie explicites sont présentes, les données d’autorisation seules et les données d’autorisation limitées au schéma d’authentification n’ajoutent pas la stratégie par défaut. |
[Authorize(AuthenticationSchemes = "{SCHEME}")] sans nom de stratégie ni rôles |
Le schéma d’authentification spécifié est utilisé. La stratégie par défaut est également utilisée, sauf si le point de terminaison a une instance explicite AuthorizationPolicy . |
Plusieurs attributs [Authorize] ou appels RequireAuthorization(...) sélectionnant la stratégie |
Les stratégies nommées sélectionnées, les rôles, les schémas d’authentification et les stratégies explicites sont combinées. Les données d’autorisation simples ajoutent la stratégie par défaut uniquement lorsqu’aucune instance explicite AuthorizationPolicy n’est présente. Toutes les exigences de la stratégie combinée doivent réussir. La stratégie de secours n’est pas utilisée. |
[AllowAnonymous] ou AllowAnonymous() |
L’intergiciel d’autorisation n’applique pas de défaillance d’autorisation pour le point de terminaison. L’authentification peut toujours s’exécuter et remplir HttpContext.User. |
La stratégie de repli n’est pas combinée à une stratégie nommée ou une stratégie par défaut. Pour les déclarations affichées dans le tableau précédent, elle est sélectionnée uniquement lorsqu’aucune stratégie d’autorisation n’est produite à partir des métadonnées d’autorisation du point de terminaison. Par exemple, [Authorize] utilise la stratégie par défaut au lieu de la stratégie de secours et [Authorize(Policy = "{POLICY NAME}")] utilise la stratégie nommée au lieu de la stratégie de secours.
Les exigences fournies via IAuthorizationRequirementData sous forme de métadonnées de point de terminaison sont ajoutées après la sélection de la stratégie. Si aucune autre métadonnées d’autorisation ne produit de stratégie, ces exigences sont combinées à la stratégie de secours lorsqu’une stratégie de secours est configurée.
Dans ASP.NET Core 8.0 à 10.0, les attributs qui implémentent IAuthorizationRequirementData ne sont appliqués qu’aux points de terminaison d’API minimales et aux points de terminaison routés. Pour obtenir la prise en charge des modèles de version et d’hébergement, consultez Les stratégies d’autorisation personnalisées avec « IAuthorizationRequirementData ».
Exigences et enregistrement de stratégie
Une stratégie d’autorisation se compose d’une ou plusieurs exigences, utilisées pour évaluer l’autorisation du principal utilisateur actuel. Une exigence implémente IAuthorizationRequirement, qui est une interface de marqueur vide.
Lorsqu’une exigence ne contient pas de données ni possède des propriétés (paramètres), elle agit comme un marqueur vide pour déclencher un gestionnaire d’autorisation associé (IAuthorizationHandler) pour le traitement de l’autorisation (décrite en détail plus loin dans cet article). Étant donné que le gestionnaire s’appuie entièrement sur le contexte HTTP, les revendications utilisateur ou les données back-end pour prendre une décision sur l’utilisateur répondant à l’exigence, la classe d’exigence elle-même ne nécessite pas de données ou de paramètres internes. L’exigence indique uniquement au framework quelle règle évaluer.
Par exemple, considérez l’exigence d’âge minimale suivante (MinimumAgeRequirement), qui est implémentée simplement en tant que classe de marqueur :
public class MinimumAgeRequirement : IAuthorizationRequirement { }
L’exigence précédente est utilisée pour créer une stratégie qui confirme que l’utilisateur a plus d’un âge spécifique que le gestionnaire vérifie. Un AuthorizationHandler<MinimumAgeRequirement> inspecte le AuthorizationHandlerContext.User. Si l’utilisateur a une revendication de date de naissance qui indique qu’il a plus d’un certain âge, l’exigence réussit. L’objet d’exigence ne nécessite aucune propriété (paramètres) dans ce cas. L’exemple suivant illustre l’implémentation complète d’une exigence d’âge minimum qui a un paramètre pour définir l’âge minimal.
Tenez compte de l’exigence suivante MinimumAgeRequirement , qui décrit un paramètre unique, un âge minimal, pour évaluer l’autorisation de l’utilisateur :
using Microsoft.AspNetCore.Authorization;
namespace BlazorWebAppAuthorization.Policies.Requirements;
public class MinimumAgeRequirement(int minimumAge) : IAuthorizationRequirement
{
public int MinimumAge { get; } = minimumAge;
}
using Microsoft.AspNetCore.Authorization;
public class MinimumAgeRequirement : IAuthorizationRequirement
{
public MinimumAgeRequirement(int minimumAge) =>
MinimumAge = minimumAge;
public int MinimumAge { get; }
}
using Microsoft.AspNetCore.Authorization;
public class MinimumAgeRequirement : IAuthorizationRequirement
{
public int MinimumAge { get; }
public MinimumAgeRequirement(int minimumAge)
{
MinimumAge = minimumAge;
}
}
Une stratégie est enregistrée dans la configuration du service d’autorisation, dans le fichier Program de l’application, en appelant AuthorizationBuilder.AddPolicy. L’exemple suivant crée une AtLeast21 stratégie avec une exigence unique d’un âge minimal et définit l’âge minimal sur 21 ans.
builder.Services.AddAuthorizationBuilder()
.AddPolicy("AtLeast21", policy =>
policy.Requirements.Add(new MinimumAgeRequirement(21)));
Une stratégie est enregistrée dans la configuration du service d’autorisation, dans le fichier Program de l’application, en appelant AuthorizationBuilder.AddPolicy. L’exemple suivant crée une AtLeast21 stratégie avec une seule exigence d’âge minimal et définit l’âge minimal sur 21 ans :
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AtLeast21", policy =>
policy.Requirements.Add(new MinimumAgeRequirement(21)));
});
Une stratégie est enregistrée dans la configuration du service d’autorisation dans Startup.ConfigureServices (Startup.cs), en appelant AuthorizationBuilder.AddPolicy. L’exemple suivant crée une AtLeast21 stratégie avec une seule exigence d’âge minimal et définit l’âge minimal sur 21 ans :
services.AddAuthorization(options =>
{
options.AddPolicy("AtLeast21", policy =>
policy.Requirements.Add(new MinimumAgeRequirement(21)));
});
Si une stratégie d’autorisation contient plusieurs exigences d’autorisation, toutes les exigences doivent être passées pour que l’évaluation de la stratégie réussisse. En d’autres termes, plusieurs exigences d’autorisation ajoutées à une stratégie d’autorisation unique sont traitées sur une base AND.
Appliquer des stratégies aux Razor composants
Appliquez des stratégies aux composants Razor à l’aide de l’attribut [Authorize] avec le nom de la stratégie :
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize(Policy = "CustomerServiceMember")]
Si plusieurs stratégies sont appliquées, toutes les stratégies doivent passer avant l’octroi de l’accès :
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize(Policy = "CustomerServiceMember")]
@attribute [Authorize(Policy = "HumanResourcesMember")]
Appliquer des stratégies aux points de terminaison
Appliquez des stratégies aux points de terminaison à l'aide de RequireAuthorization avec le nom de la stratégie. Par exemple:
app.MapGet("/helloworld", () => "Hello World!")
.RequireAuthorization("AtLeast21");
Appliquer des stratégies dans les applications MVC et Razor Pages
Pour obtenir des conseils sur l’application de stratégies dans Razor les applications Pages et MVC, consultez les ressources suivantes :
- Autorisation basée sur des stratégies dans ASP.NET Core Razor Pages
- Autorisation basée sur une stratégie dans ASP.NET Core MVC
Interface de service d’autorisation (IAuthorizationService)
IAuthorizationService est principalement chargé de déterminer si l’autorisation est accordée lorsqu’une surcharge de IAuthorizationService.AuthorizeAsync est appelée :
-
AuthorizeAsync(ClaimsPrincipal user, object resource, IEnumerable<IAuthorizationRequirement> requirements): vérifie si un utilisateur répond à un ensemble spécifique d’exigences d’autorisation pour une ressource spécifiée. -
AuthorizeAsync(ClaimsPrincipal user, object resource, string policyName): vérifie si un utilisateur répond à une stratégie d’autorisation spécifique pour une ressource spécifiée.
Si une ressource n’est pas requise pour évaluer la stratégie, null est transmis comme ressource.
Les méthodes précédentes renvoient un AuthorizationResult encapsulé dans un Task.
Chacun d’eux IAuthorizationHandler est chargé de vérifier si les exigences sont remplies via IAuthorizationHandler.HandleAsync. La AuthorizationHandlerContext classe contient les informations d’autorisation utilisées par l’implémentation IAuthorizationHandler . IAuthorizationRequirement est une interface de marqueur sans méthodes qui sert de mécanisme pour le suivi de la réussite de l’autorisation. Quand AuthorizationHandlerContext.Succeed est appelée avec le IAuthorizationRequirement, la stratégie est respectée :
context.Succeed(requirement);
Gestionnaires d’autorisation
Un gestionnaire d’autorisation est responsable de l’évaluation des propriétés d’une exigence. Le gestionnaire d’autorisation évalue les exigences par rapport à un AuthorizationHandlerContext fourni pour déterminer si access est autorisé.
Une exigence peut avoir plusieurs gestionnaires. Un gestionnaire peut hériter de AuthorizationHandler<TRequirement>, où TRequirement est l’exigence à traiter. Un gestionnaire peut également implémenter IAuthorizationHandler directement pour gérer plusieurs types d’exigences.
Utiliser un gestionnaire pour une exigence
L’exemple suivant montre une relation un-à-un dans laquelle un gestionnaire d’âge minimum gère une seule exigence :
using System.Security.Claims;
using Microsoft.AspNetCore.Authorization;
using BlazorWebAppAuthorization.Policies.Requirements;
namespace BlazorWebAppAuthorization.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);
if (dateOfBirthClaim is null)
{
return Task.CompletedTask;
}
var dateOfBirth = Convert.ToDateTime(dateOfBirthClaim.Value);
var calculatedAge = DateTime.Today.Year - dateOfBirth.Year;
if (dateOfBirth > DateTime.Today.AddYears(-calculatedAge))
{
calculatedAge--;
}
if (calculatedAge >= requirement.MinimumAge)
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
using System.Security.Claims;
using Microsoft.AspNetCore.Authorization;
public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context, MinimumAgeRequirement requirement)
{
var dateOfBirthClaim =
context.User.FindFirst(c => c.Type == ClaimTypes.DateOfBirth);
if (dateOfBirthClaim is null)
{
return Task.CompletedTask;
}
var dateOfBirth = Convert.ToDateTime(dateOfBirthClaim.Value);
var calculatedAge = DateTime.Today.Year - dateOfBirth.Year;
if (dateOfBirth > DateTime.Today.AddYears(-calculatedAge))
{
calculatedAge--;
}
if (calculatedAge >= requirement.MinimumAge)
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
MinimumAgeRequirement requirement)
{
if (!context.User.HasClaim(c => c.Type == ClaimTypes.DateOfBirth))
{
// 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).Value);
var calculatedAge = DateTime.Today.Year - dateOfBirth.Year;
if (dateOfBirth > DateTime.Today.AddYears(-calculatedAge))
{
calculatedAge--;
}
if (calculatedAge >= requirement.MinimumAge)
{
context.Succeed(requirement);
}
// Use the following if targeting a version of
// .NET Framework older than 4.6:
// return Task.FromResult(0);
return Task.CompletedTask;
}
}
Le code précédent détermine si le principal de l’utilisateur actuel possède une revendication relative à la date de naissance. L'autorisation ne peut pas être accordée lorsque la réclamation est manquante, auquel cas une tâche achevée est retournée. Lorsqu’une revendication est présente, l’âge de l’utilisateur est calculé. Si l’utilisateur répond à l’âge minimal défini par l’exigence, l’autorisation est considérée comme réussie. Lorsque l’autorisation réussit, context.Succeed est appelé avec l’exigence satisfaite comme seul paramètre.
Utiliser un gestionnaire pour plusieurs exigences
L’exemple suivant montre une relation un-à-plusieurs dans laquelle un gestionnaire d’autorisations peut gérer trois types d’exigences différents :
using System.Security.Claims;
using Microsoft.AspNetCore.Authorization;
using BlazorWebAppAuthorization.Policies.Requirements;
namespace BlazorWebAppAuthorization.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;
}
}
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
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);
}
}
}
// 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;
}
}
Le code précédent parcourt PendingRequirements— une propriété contenant des exigences non marquées comme réussies. Pour une condition ReadPermission, l’utilisateur doit être soit un propriétaire, soit un sponsor pour accéder à la ressource demandée. Pour une exigence EditPermission ou DeletePermission, ils doivent être propriétaires pour accéder à la ressource demandée.
Enregistrement des gestionnaires
Inscrivez des gestionnaires dans la collection de services pendant la configuration. L’exemple suivant inscrit un gestionnaire d’âge minimal (MinimumAgeHandler) en tant que service singleton, mais un gestionnaire peut être inscrit avec n’importe quelle durée de vie de service intégrée :
builder.Services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();
services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();
Il est possible de regrouper à la fois une exigence et un gestionnaire dans une classe unique implémentant à la fois IAuthorizationRequirement et IAuthorizationHandler. Ce regroupement crée un couplage étroit entre le gestionnaire et la spécification et est recommandé uniquement pour les exigences et les gestionnaires simples. La création d’une classe qui implémente les deux interfaces supprime la nécessité d’inscrire le gestionnaire dans le conteneur de service en raison de la configuration intégrée PassThroughAuthorizationHandler qui permet aux exigences de gérer elles-mêmes.
Consultez l’implémentation de la classe ASP.NET Core AssertionRequirement pour voir un exemple où AssertionRequirement est à la fois une condition et le gestionnaire dans une classe entièrement autonome. L’API du framework AssertionRequirement vous permet de valider l’accès à l’aide d’expressions lambda en ligne, plutôt que de devoir écrire des classes distinctes d’exigence et de gestionnaire répétitives.
Note
Les liens de documentation vers la source de référence .NET chargent généralement la branche par défaut du référentiel, qui représente le développement actuel pour la prochaine version de .NET. Pour sélectionner une balise pour une version spécifique, utilisez la liste déroulante Échanger les branches ou les balises.
Que doit retourner un gestionnaire ?
La Handle méthode de l’exemple de gestionnaire ne retourne aucune valeur. Comment un état de réussite ou d’échec est-il indiqué ?
Un gestionnaire indique la réussite en appelant
context.Succeed, en passant l’exigence validée avec succès (IAuthorizationRequirement).Un gestionnaire n’est pas nécessaire pour gérer les défaillances en général, car d’autres gestionnaires pour la même exigence peuvent réussir.
Pour garantir l'échec, même si d'autres gestionnaires d'exigences réussissent, appelez
context.Fail
Si un gestionnaire appelle context.Succeed ou context.Fail, tous les autres gestionnaires sont toujours appelés. Cela permet aux exigences de produire des effets secondaires, tels que la journalisation, qui se produit même si un autre gestionnaire valide ou échoue correctement sur une exigence. Lorsqu’elle est définie sur false, la InvokeHandlersAfterFailure propriété court-circuite l’exécution des gestionnaires quand context.Fail est appelé.
InvokeHandlersAfterFailure la valeur par défaut est true, auquel cas tous les gestionnaires sont appelés.
Note
Les gestionnaires d’autorisation sont appelés même si l’authentification échoue. En outre, les gestionnaires peuvent s’exécuter dans n’importe quel ordre, donc ne dépendez pas de l’ordre d’appel des gestionnaires.
Pourquoi voudrais-je plusieurs gestionnaires pour une exigence ?
Dans les cas où vous souhaitez que l’évaluation soit sur une base OR, implémentez plusieurs gestionnaires pour une seule exigence. Par exemple, supposons que Contoso Corporation dispose de portes qui s’ouvrent uniquement avec des cartes de clés. Si vous laissez votre carte de clé à la maison, le réceptionniste imprime un autocollant temporaire et ouvre la porte pour vous. Dans ce scénario, l’application a une exigence unique, mais plusieurs gestionnaires, chacun examinant une seule exigence.
Dans les exemples d’implémentations suivants :
-
BuildingEntryRequirementest l’exigence d’accès au bâtiment. -
BadgeEntryHandler(l’individu a un badge) etTemporaryStickerHandler(l’individu a un autocollant temporaire) sont des gestionnaires distincts, chacun examinant une seule exigence.
BuildingEntryRequirement.cs :
using Microsoft.AspNetCore.Authorization;
namespace BlazorWebAppAuthorization.Policies.Requirements;
public class BuildingEntryRequirement : IAuthorizationRequirement { }
BadgeEntryHandler.cs :
using Microsoft.AspNetCore.Authorization;
using BlazorWebAppAuthorization.Policies.Requirements;
namespace BlazorWebAppAuthorization.Policies.Handlers;
public class BadgeEntryHandler : AuthorizationHandler<BuildingEntryRequirement>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context, BuildingEntryRequirement requirement)
{
if (context.User.HasClaim(c => c.Type == "BadgeId"))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
TemporaryStickerHandler.cs :
using Microsoft.AspNetCore.Authorization;
using BlazorWebAppAuthorization.Policies.Requirements;
namespace BlazorWebAppAuthorization.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://contososecurity"))
{
// Code to check expiration date omitted for brevity.
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
BuildingEntryRequirement.cs :
using Microsoft.AspNetCore.Authorization;
public class BuildingEntryRequirement : IAuthorizationRequirement
{
}
BadgeEntryHandler.cs :
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
public class BadgeEntryHandler : AuthorizationHandler<BuildingEntryRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
BuildingEntryRequirement requirement)
{
if (context.User.HasClaim(c =>
c.Type == "BadgeId" &&
c.Issuer == "https://contososecurity"))
{
context.Succeed(requirement);
}
// 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.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
public class TemporaryStickerHandler : AuthorizationHandler<BuildingEntryRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
BuildingEntryRequirement requirement)
{
if (context.User.HasClaim(c =>
c.Type == "TemporaryBadgeId" &&
c.Issuer == "https://contososecurity"))
{
// We'd also check the expiration date on the sticker.
context.Succeed(requirement);
}
// Use the following if targeting a version of
// .NET Framework older than 4.6:
// return Task.FromResult(0);
return Task.CompletedTask;
}
}
Vérifiez que les deux gestionnaires sont inscrits. Si l’un des gestionnaires réussit lorsqu’une stratégie évalue le BuildingEntryRequirement, l’évaluation de la stratégie réussit.
Utiliser un(e) Func pour satisfaire à une stratégie
Il existe des situations où la réalisation d’une stratégie est simple à exprimer dans le code avec un Func<AuthorizationHandlerContext, bool> délégué lors de la configuration d’une stratégie avec le RequireAssertion générateur de stratégies. Par exemple, le précédent BadgeEntryHandler peut être réécrit comme suit :
options.AddPolicy("AtLeast21", policy =>
policy.Requirements.Add(new MinimumAgeRequirement(21)));
(c.Type == "BadgeId" || c.Type == "TemporaryBadgeId")
&& c.Issuer == "https://contososecurity")));
});
// <snippet_minimumAgeHandlerRegistration>
services.AddAuthorization(options =>
{
options.AddPolicy("BadgeEntry", policy =>
policy.RequireAssertion(context =>
context.User.HasClaim(c =>
(c.Type == "BadgeId" ||
c.Type == "TemporaryBadgeId") &&
c.Issuer == "https://microsoftsecurity")));
});
Autorisation via un exemple de service externe
L’exemple « Autorisation via un service externe » (dotnet/AspNetCore.Docs.Samples dépôt GitHub) montre comment mettre en œuvre des exigences d’autorisation supplémentaires à l’aide d’un service d’autorisation externe. Le projet de la Contoso.API solution est sécurisé avec Microsoft Entra ID. Une vérification d’autorisation supplémentaire de la Contoso.Security.API project retourne une charge utile décrivant si l’application cliente Contoso.API peut appeler l’API GetWeather.
Configurer l'exemple
La démonstration suivante s’appuie sur l’utilisation de NSwag (Swagger/OpenAPI) ou cURL dans un interpréteur de commandes.
Dans le projet Contoso.Security.API, définissez l’espace réservé AllowedClients ({CLIENT ID}) sur une valeur GUID de test quelconque (par exemple, 00001111-aaaa-2222-bbbb-3333cccc4444):
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"AllowedClients": [
"{CLIENT ID (FOR THE CLIENT CALLING CONTOSO.API)}"
]
}
Dans un interpréteur de commandes ouvert dans le projet Contoso.API, utilisez dotnet user-jwts pour générer un jeton d’accès avec une revendication appid pour l’ID de l’application cliente, créé à l’étape précédente (par exemple, 00001111-aaaa-2222-bbbb-3333cccc4444).
dotnet user-jwts create --claim appid={GUID}
Exemple :
dotnet user-jwts create --claim appid=00001111-aaaa-2222-bbbb-3333cccc4444
La sortie produit un jeton après «Token: » dans l’interpréteur de commandes :
New JWT saved with ID '{JWT ID}'.
Name: {USER}
Custom Claims: [appid=00001111-aaaa-2222-bbbb-3333cccc4444]
Token: {TOKEN}
Mettez de côté la valeur du token (à l’emplacement du paramètre {TOKEN} dans la sortie précédente) pour l’utiliser plus tard.
Vous pouvez décoder le jeton dans un décodeur en ligne JWT , par exemple jwt.ms pour afficher son contenu, révélant qu’il contient une appid revendication avec l’ID de l’application cliente :
{
"alg": "HS256",
"typ": "JWT"
}.{
"unique_name": "{USER}",
"sub": "{USER}",
"jti": "14ed7729",
"appid": "{CLIENT ID}",
"aud": [
"https://localhost:7250",
"http://localhost:7251"
],
"nbf": 1780660887,
"exp": 1788609687,
"iat": 1780660888,
"iss": "dotnet-user-jwts"
}.[Signature]
Réexécutez la commande avec une valeur d’ID client (appid) incorrecte :
dotnet user-jwts create --claim appid=aaaabbbb-0000-cccc-1111-dddd2222eeee
Mettez la valeur du deuxième jeton de côté.
Démarrez les deux projets Contoso.API et Contoso.Security.API dans Visual Studio ou à l’aide de la commande dotnet watch dans un interpréteur de commandes :
dotnet watch
Dans l’interface utilisateur Swagger du Contoso.API projet (https://localhost:7250/swagger/index.html), sélectionnez le bouton Autoriser .
Dans la fenêtre Autorisations disponibles : Bearer, entrez le jeton d’accès. Sélectionnez le bouton Autoriser. Fermez la fenêtre Autorisations disponibles .
Sous Par défaut, sélectionnez le bouton Get pour le point de terminaison /WeatherForecast. Sélectionnez le bouton Essayer. Sélectionnez le bouton Exécuter.
La sortie dans Réponses>Réponse du serveur>Corps de la réponse affiche le JSON des prévisions météorologiques renvoyé par le projet Contoso.API.
Effectuez les mêmes étapes avec le jeton d’accès généré avec un ID d’application client non valide. La réponse est 403 - Interdit.