Autorisation basée sur les ressources dans ASP.NET Core

L’approche d’autorisation dépend de la ressource. Par exemple, seul l’auteur d’un document est autorisé à mettre à jour le document. Par conséquent, le document doit être récupéré à partir du magasin de données avant que l’évaluation de l’autorisation puisse avoir lieu.

L’évaluation des attributs se produit avant la liaison de données et avant l’exécution du gestionnaire de pages ou de l’action qui charge le document. Pour ces raisons, l’autorisation déclarative avec un attribut [Authorize] ne suffit pas. Au lieu de cela, vous pouvez appeler une méthode d’autorisation personnalisée, un style connu sous le nom d’autorisation impérative.

Affichez ou téléchargez un exemple de code (procédure de téléchargement).

Créer une application ASP.NET Core avec des données utilisateur protégées par l’autorisation contient un exemple d’application qui utilise l’autorisation basée sur les ressources.

Utiliser l’autorisation impérative

L’autorisation est implémentée en tant que service IAuthorizationService et est inscrite dans la collection de services au démarrage de l’application. Le service est mis à disposition via l’injection de dépendances aux gestionnaires de pages ou aux actions.

public class DocumentController : Controller
{
    private readonly IAuthorizationService _authorizationService;
    private readonly IDocumentRepository _documentRepository;

    public DocumentController(IAuthorizationService authorizationService,
                              IDocumentRepository documentRepository)
    {
        _authorizationService = authorizationService;
        _documentRepository = documentRepository;
    }

IAuthorizationService a deux AuthorizeAsync surcharges de méthode : l’une acceptant la ressource et le nom de la stratégie et l’autre acceptant la ressource et une liste d’exigences à évaluer.

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

Dans l’exemple suivant, la ressource à sécuriser est chargée dans un objet personnalisé Document. Une surcharge AuthorizeAsync est appelée pour déterminer si l’utilisateur actuel est autorisé à modifier le document fourni. Une stratégie d’autorisation « EditPolicy » personnalisée est prise en compte dans la décision. Pour plus d’informations sur la création de stratégies, consultez Autorisation personnalisée basée sur une stratégie.

Notes

Les exemples de code suivants supposent que l’authentification a exécuté et défini la propriété User.

public async Task<IActionResult> OnGetAsync(Guid documentId)
{
    Document = _documentRepository.Find(documentId);

    if (Document == null)
    {
        return new NotFoundResult();
    }

    var authorizationResult = await _authorizationService
            .AuthorizeAsync(User, Document, "EditPolicy");

    if (authorizationResult.Succeeded)
    {
        return Page();
    }
    else if (User.Identity.IsAuthenticated)
    {
        return new ForbidResult();
    }
    else
    {
        return new ChallengeResult();
    }
}

Écrire un gestionnaire basé sur les ressources

L’écriture d’un gestionnaire pour l’autorisation basée sur les ressources n’est pas très différente de l’écriture d’un gestionnaire d’exigences simples. Créez une classe d’exigences personnalisée et implémentez une classe de gestionnaire d’exigences. Pour plus d’informations sur la création d’une classe de conditions requises, consultez Configuration requise.

La classe de gestionnaire spécifie à la fois l’exigence et le type de ressource. Par exemple, un gestionnaire utilisant une ressourceSameAuthorRequirement et une ressource Document suit :

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

        return Task.CompletedTask;
    }
}

public class SameAuthorRequirement : IAuthorizationRequirement { }

Dans l’exemple précédent, imaginez que SameAuthorRequirement est un cas spécial d’une classe plus générique SpecificAuthorRequirement. La classe SpecificAuthorRequirement (non affichée) contient une propriété Name représentant le nom de l’auteur. La propriété Name peut être définie sur l’utilisateur actuel.

Inscrivez la configuration requise et le gestionnaire dans Program.cs :

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("EditPolicy", policy =>
        policy.Requirements.Add(new SameAuthorRequirement()));
});

builder.Services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationHandler>();
builder.Services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationCrudHandler>();
builder.Services.AddScoped<IDocumentRepository, DocumentRepository>();

Exigences opérationnelles

Si vous prenez des décisions en fonction des résultats des opérations CRUD (Create, Read, Update, Delete), utilisez la classe d’assistance OperationAuthorizationRequirement. Cette classe vous permet d’écrire un seul gestionnaire au lieu d’une classe individuelle pour chaque type d’opération. Pour l’utiliser, fournissez quelques noms d’opération :

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

Le gestionnaire est implémenté comme suit, à l’aide d’une exigence OperationAuthorizationRequirement et d’une ressourceDocument :

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

        return Task.CompletedTask;
    }
}

Le gestionnaire précédent valide l’opération à l’aide de la ressource, de l’identité de l’utilisateur et de la propriété requise Name.

Demande et interdiction avec un gestionnaire de ressources opérationnelles

Cette section montre comment les résultats des actions de contestation et d’interdiction sont traités et comment la contestation et l’interdiction diffèrent.

Pour appeler un gestionnaire de ressources opérationnelles, spécifiez l’opération lors de l’appel AuthorizeAsync dans votre gestionnaire de page ou action. L’exemple suivant détermine si l’utilisateur authentifié est autorisé à afficher le document fourni.

Notes

Les exemples de code suivants supposent que l’authentification a exécuté et défini la propriété User.

public async Task<IActionResult> OnGetAsync(Guid documentId)
{
    Document = _documentRepository.Find(documentId);

    if (Document == null)
    {
        return new NotFoundResult();
    }

    var authorizationResult = await _authorizationService
            .AuthorizeAsync(User, Document, Operations.Read);

    if (authorizationResult.Succeeded)
    {
        return Page();
    }
    else if (User.Identity.IsAuthenticated)
    {
        return new ForbidResult();
    }
    else
    {
        return new ChallengeResult();
    }
}

Si l’autorisation réussit, la page d’affichage du document est retournée. Si l’autorisation échoue, mais que l’utilisateur est authentifié, le retour ForbidResult informe tout intergiciel d’authentification que l’autorisation a échoué. Un ChallengeResult est retourné lorsque l’authentification doit être effectuée. Pour les clients de navigateur interactifs, il peut être approprié de rediriger l’utilisateur vers une page de connexion.

L’approche d’autorisation dépend de la ressource. Par exemple, seul l’auteur d’un document est autorisé à mettre à jour le document. Par conséquent, le document doit être récupéré à partir du magasin de données avant que l’évaluation de l’autorisation puisse avoir lieu.

L’évaluation des attributs se produit avant la liaison de données et avant l’exécution du gestionnaire de pages ou de l’action qui charge le document. Pour ces raisons, l’autorisation déclarative avec un attribut [Authorize] ne suffit pas. Au lieu de cela, vous pouvez appeler une méthode d’autorisation personnalisée, un style connu sous le nom d’autorisation impérative.

Affichez ou téléchargez un exemple de code (procédure de téléchargement).

Créer une application ASP.NET Core avec des données utilisateur protégées par l’autorisation contient un exemple d’application qui utilise l’autorisation basée sur les ressources.

Utiliser l’autorisation impérative

L’autorisation est implémentée en tant que service IAuthorizationService et est inscrite dans la collection de services au sein de la classe Startup. Le service est mis à disposition via l’injection de dépendances aux gestionnaires de pages ou aux actions.

public class DocumentController : Controller
{
    private readonly IAuthorizationService _authorizationService;
    private readonly IDocumentRepository _documentRepository;

    public DocumentController(IAuthorizationService authorizationService,
                              IDocumentRepository documentRepository)
    {
        _authorizationService = authorizationService;
        _documentRepository = documentRepository;
    }

IAuthorizationService a deux AuthorizeAsync surcharges de méthode : l’une acceptant la ressource et le nom de la stratégie et l’autre acceptant la ressource et une liste d’exigences à évaluer.

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

Dans l’exemple suivant, la ressource à sécuriser est chargée dans un objet personnalisé Document. Une surcharge AuthorizeAsync est appelée pour déterminer si l’utilisateur actuel est autorisé à modifier le document fourni. Une stratégie d’autorisation « EditPolicy » personnalisée est prise en compte dans la décision. Pour plus d’informations sur la création de stratégies, consultez Autorisation personnalisée basée sur une stratégie.

Notes

Les exemples de code suivants supposent que l’authentification a exécuté et défini la propriété User.

public async Task<IActionResult> OnGetAsync(Guid documentId)
{
    Document = _documentRepository.Find(documentId);

    if (Document == null)
    {
        return new NotFoundResult();
    }

    var authorizationResult = await _authorizationService
            .AuthorizeAsync(User, Document, "EditPolicy");

    if (authorizationResult.Succeeded)
    {
        return Page();
    }
    else if (User.Identity.IsAuthenticated)
    {
        return new ForbidResult();
    }
    else
    {
        return new ChallengeResult();
    }
}

Écrire un gestionnaire basé sur les ressources

L’écriture d’un gestionnaire pour l’autorisation basée sur les ressources n’est pas très différente de l’écriture d’un gestionnaire d’exigences simples. Créez une classe d’exigences personnalisée et implémentez une classe de gestionnaire d’exigences. Pour plus d’informations sur la création d’une classe de conditions requises, consultez Configuration requise.

La classe de gestionnaire spécifie à la fois l’exigence et le type de ressource. Par exemple, un gestionnaire utilisant une ressourceSameAuthorRequirement et une ressource Document suit :

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

        return Task.CompletedTask;
    }
}

public class SameAuthorRequirement : IAuthorizationRequirement { }

Dans l’exemple précédent, imaginez que SameAuthorRequirement est un cas spécial d’une classe plus générique SpecificAuthorRequirement. La classe SpecificAuthorRequirement (non affichée) contient une propriété Name représentant le nom de l’auteur. La propriété Name peut être définie sur l’utilisateur actuel.

Inscrivez la configuration requise et le gestionnaire dans Startup.ConfigureServices :

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

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

services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationHandler>();
services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationCrudHandler>();
services.AddScoped<IDocumentRepository, DocumentRepository>();

Exigences opérationnelles

Si vous prenez des décisions en fonction des résultats des opérations CRUD (Create, Read, Update, Delete), utilisez la classe d’assistance OperationAuthorizationRequirement. Cette classe vous permet d’écrire un seul gestionnaire au lieu d’une classe individuelle pour chaque type d’opération. Pour l’utiliser, fournissez quelques noms d’opération :

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

Le gestionnaire est implémenté comme suit, à l’aide d’une exigence OperationAuthorizationRequirement et d’une ressourceDocument :

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

        return Task.CompletedTask;
    }
}

Le gestionnaire précédent valide l’opération à l’aide de la ressource, de l’identité de l’utilisateur et de la propriété requise Name.

Demande et interdiction avec un gestionnaire de ressources opérationnelles

Cette section montre comment les résultats des actions de contestation et d’interdiction sont traités et comment la contestation et l’interdiction diffèrent.

Pour appeler un gestionnaire de ressources opérationnelles, spécifiez l’opération lors de l’appel AuthorizeAsync dans votre gestionnaire de page ou action. L’exemple suivant détermine si l’utilisateur authentifié est autorisé à afficher le document fourni.

Notes

Les exemples de code suivants supposent que l’authentification a exécuté et défini la propriété User.

public async Task<IActionResult> OnGetAsync(Guid documentId)
{
    Document = _documentRepository.Find(documentId);

    if (Document == null)
    {
        return new NotFoundResult();
    }

    var authorizationResult = await _authorizationService
            .AuthorizeAsync(User, Document, Operations.Read);

    if (authorizationResult.Succeeded)
    {
        return Page();
    }
    else if (User.Identity.IsAuthenticated)
    {
        return new ForbidResult();
    }
    else
    {
        return new ChallengeResult();
    }
}

Si l’autorisation réussit, la page d’affichage du document est retournée. Si l’autorisation échoue, mais que l’utilisateur est authentifié, le retour ForbidResult informe tout intergiciel d’authentification que l’autorisation a échoué. Un ChallengeResult est retourné lorsque l’authentification doit être effectuée. Pour les clients de navigateur interactifs, il peut être approprié de rediriger l’utilisateur vers une page de connexion.

L’approche d’autorisation dépend de la ressource. Par exemple, seul l’auteur d’un document est autorisé à mettre à jour le document. Par conséquent, le document doit être récupéré à partir du magasin de données avant que l’évaluation de l’autorisation puisse avoir lieu.

L’évaluation des attributs se produit avant la liaison de données et avant l’exécution du gestionnaire de pages ou de l’action qui charge le document. Pour ces raisons, l’autorisation déclarative avec un attribut [Authorize] ne suffit pas. Au lieu de cela, vous pouvez appeler une méthode d’autorisation personnalisée, un style connu sous le nom d’autorisation impérative.

Affichez ou téléchargez un exemple de code (procédure de téléchargement).

Créer une application ASP.NET Core avec des données utilisateur protégées par l’autorisation contient un exemple d’application qui utilise l’autorisation basée sur les ressources.

Utiliser l’autorisation impérative

L’autorisation est implémentée en tant que service IAuthorizationService et est inscrite dans la collection de services au sein de la classe Startup. Le service est mis à disposition via l’injection de dépendances aux gestionnaires de pages ou aux actions.

public class DocumentController : Controller
{
    private readonly IAuthorizationService _authorizationService;
    private readonly IDocumentRepository _documentRepository;

    public DocumentController(IAuthorizationService authorizationService,
                              IDocumentRepository documentRepository)
    {
        _authorizationService = authorizationService;
        _documentRepository = documentRepository;
    }

IAuthorizationService a deux AuthorizeAsync surcharges de méthode : l’une acceptant la ressource et le nom de la stratégie et l’autre acceptant la ressource et une liste d’exigences à évaluer.

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

Dans l’exemple suivant, la ressource à sécuriser est chargée dans un objet personnalisé Document. Une surcharge AuthorizeAsync est appelée pour déterminer si l’utilisateur actuel est autorisé à modifier le document fourni. Une stratégie d’autorisation « EditPolicy » personnalisée est prise en compte dans la décision. Pour plus d’informations sur la création de stratégies, consultez Autorisation personnalisée basée sur une stratégie.

Notes

Les exemples de code suivants supposent que l’authentification a exécuté et défini la propriété User.

public async Task<IActionResult> OnGetAsync(Guid documentId)
{
    Document = _documentRepository.Find(documentId);

    if (Document == null)
    {
        return new NotFoundResult();
    }

    var authorizationResult = await _authorizationService
            .AuthorizeAsync(User, Document, "EditPolicy");

    if (authorizationResult.Succeeded)
    {
        return Page();
    }
    else if (User.Identity.IsAuthenticated)
    {
        return new ForbidResult();
    }
    else
    {
        return new ChallengeResult();
    }
}

Écrire un gestionnaire basé sur les ressources

L’écriture d’un gestionnaire pour l’autorisation basée sur les ressources n’est pas très différente de l’écriture d’un gestionnaire d’exigences simples. Créez une classe d’exigences personnalisée et implémentez une classe de gestionnaire d’exigences. Pour plus d’informations sur la création d’une classe de conditions requises, consultez Configuration requise.

La classe de gestionnaire spécifie à la fois l’exigence et le type de ressource. Par exemple, un gestionnaire utilisant une ressourceSameAuthorRequirement et une ressource Document suit :

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

        return Task.CompletedTask;
    }
}

public class SameAuthorRequirement : IAuthorizationRequirement { }

Dans l’exemple précédent, imaginez que SameAuthorRequirement est un cas spécial d’une classe plus générique SpecificAuthorRequirement. La classe SpecificAuthorRequirement (non affichée) contient une propriété Name représentant le nom de l’auteur. La propriété Name peut être définie sur l’utilisateur actuel.

Inscrivez la configuration requise et le gestionnaire dans Startup.ConfigureServices :

services.AddMvc();

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

services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationHandler>();
services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationCrudHandler>();
services.AddScoped<IDocumentRepository, DocumentRepository>();

Exigences opérationnelles

Si vous prenez des décisions en fonction des résultats des opérations CRUD (Create, Read, Update, Delete), utilisez la classe d’assistance OperationAuthorizationRequirement. Cette classe vous permet d’écrire un seul gestionnaire au lieu d’une classe individuelle pour chaque type d’opération. Pour l’utiliser, fournissez quelques noms d’opération :

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

Le gestionnaire est implémenté comme suit, à l’aide d’une exigence OperationAuthorizationRequirement et d’une ressourceDocument :

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

        return Task.CompletedTask;
    }
}

Le gestionnaire précédent valide l’opération à l’aide de la ressource, de l’identité de l’utilisateur et de la propriété requise Name.

Demande et interdiction avec un gestionnaire de ressources opérationnelles

Cette section montre comment les résultats des actions de contestation et d’interdiction sont traités et comment la contestation et l’interdiction diffèrent.

Pour appeler un gestionnaire de ressources opérationnelles, spécifiez l’opération lors de l’appel AuthorizeAsync dans votre gestionnaire de page ou action. L’exemple suivant détermine si l’utilisateur authentifié est autorisé à afficher le document fourni.

Notes

Les exemples de code suivants supposent que l’authentification a exécuté et défini la propriété User.

public async Task<IActionResult> OnGetAsync(Guid documentId)
{
    Document = _documentRepository.Find(documentId);

    if (Document == null)
    {
        return new NotFoundResult();
    }

    var authorizationResult = await _authorizationService
            .AuthorizeAsync(User, Document, Operations.Read);

    if (authorizationResult.Succeeded)
    {
        return Page();
    }
    else if (User.Identity.IsAuthenticated)
    {
        return new ForbidResult();
    }
    else
    {
        return new ChallengeResult();
    }
}

Si l’autorisation réussit, la page d’affichage du document est retournée. Si l’autorisation échoue, mais que l’utilisateur est authentifié, le retour ForbidResult informe tout intergiciel d’authentification que l’autorisation a échoué. Un ChallengeResult est retourné lorsque l’authentification doit être effectuée. Pour les clients de navigateur interactifs, il peut être approprié de rediriger l’utilisateur vers une page de connexion.