Autorização baseada em recursos e baseada em funçõesRole-based and resource-based authorization

Código de exemplo do GitHubGitHub Sample code

Nosso implementação de referência é uma aplicação ASP.NET Core.Our reference implementation is an ASP.NET Core application. Neste artigo, examinaremos duas abordagens gerais para autorização, usando a APIs fornecidas no ASP.NET Core de autorização.In this article we'll look at two general approaches to authorization, using the authorization APIs provided in ASP.NET Core.

  • Autorização baseada em funções.Role-based authorization. Autorizar uma ação com base nas funções atribuídas a um utilizador.Authorizing an action based on the roles assigned to a user. Por exemplo, algumas ações exigem uma função de administrador.For example, some actions require an administrator role.
  • Autorização baseada em recursos.Resource-based authorization. Autorizar uma ação com base num determinado recurso.Authorizing an action based on a particular resource. Por exemplo, cada recurso tem um proprietário.For example, every resource has an owner. O proprietário pode eliminar o recurso; não não a outros utilizadores.The owner can delete the resource; other users cannot.

Um aplicativo típico vai empregar uma mistura de ambos.A typical app will employ a mix of both. Por exemplo, para eliminar um recurso, o utilizador tem de ser o proprietário do recurso ou um administrador.For example, to delete a resource, the user must be the resource owner or an admin.

Autorização baseada em funçõesRole-Based Authorization

O [Tailspin Surveys] Tailspin aplicativo define as seguintes funções:The Tailspin Surveys application defines the following roles:

  • Administrador.Administrator. Pode efetuar todas as operações de CRUD em qualquer pesquisa que pertence a esse inquilino.Can perform all CRUD operations on any survey that belongs to that tenant.
  • Criador.Creator. Pode criar pesquisas de novoCan create new surveys
  • Leitor.Reader. Pode ler qualquer pesquisas que pertencem a esse inquilinoCan read any surveys that belong to that tenant

Funções de aplicam a utilizadores do aplicativo.Roles apply to users of the application. No aplicativo de pesquisas, um utilizador é um administrador, criador ou leitor.In the Surveys application, a user is either an administrator, creator, or reader.

Para uma discussão sobre como definir e gerir funções, consulte funções de aplicação.For a discussion of how to define and manage roles, see Application roles.

Independentemente de como gerir as funções, seu código de autorização terá um aspeto semelhante.Regardless of how you manage the roles, your authorization code will look similar. ASP.NET Core tem uma abstração chamada políticas de autorização.ASP.NET Core has an abstraction called authorization policies. Com esta funcionalidade, definir políticas de autorização no código e, em seguida, aplicar essas políticas a ações do controlador.With this feature, you define authorization policies in code, and then apply those policies to controller actions. A política é dissociada do controlador.The policy is decoupled from the controller.

Criar políticasCreate policies

Para definir uma política, primeiro crie uma classe que implementa IAuthorizationRequirement.To define a policy, first create a class that implements IAuthorizationRequirement. É mais fácil de derivar de AuthorizationHandler.It's easiest to derive from AuthorizationHandler. Na Handle método, examine as afirmação ou afirmações relevantes.In the Handle method, examine the relevant claim(s).

Eis um exemplo da aplicação Tailspin Surveys:Here is an example from the Tailspin Surveys application:

public class SurveyCreatorRequirement : AuthorizationHandler<SurveyCreatorRequirement>, IAuthorizationRequirement
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, SurveyCreatorRequirement requirement)
    {
        if (context.User.HasClaim(ClaimTypes.Role, Roles.SurveyAdmin) ||
            context.User.HasClaim(ClaimTypes.Role, Roles.SurveyCreator))
        {
            context.Succeed(requirement);
        }
        return Task.FromResult(0);
    }
}

Essa classe define o requisito de um utilizador criar uma nova pesquisa.This class defines the requirement for a user to create a new survey. O utilizador tem de ser a função SurveyAdmin ou SurveyCreator.The user must be in the SurveyAdmin or SurveyCreator role.

Na sua classe de inicialização, defina uma política com o nome que inclui um ou mais requisitos.In your startup class, define a named policy that includes one or more requirements. Se existirem vários requisitos, o utilizador tem de cumprir cada requisito ser autorizado.If there are multiple requirements, the user must meet every requirement to be authorized. O código a seguir define duas políticas:The following code defines two policies:

services.AddAuthorization(options =>
{
    options.AddPolicy(PolicyNames.RequireSurveyCreator,
        policy =>
        {
            policy.AddRequirements(new SurveyCreatorRequirement());
            policy.RequireAuthenticatedUser(); // Adds DenyAnonymousAuthorizationRequirement
            // By adding the CookieAuthenticationDefaults.AuthenticationScheme, if an authenticated
            // user is not in the appropriate role, they will be redirected to a "forbidden" page.
            policy.AddAuthenticationSchemes(CookieAuthenticationDefaults.AuthenticationScheme);
        });

    options.AddPolicy(PolicyNames.RequireSurveyAdmin,
        policy =>
        {
            policy.AddRequirements(new SurveyAdminRequirement());
            policy.RequireAuthenticatedUser();  
            policy.AddAuthenticationSchemes(CookieAuthenticationDefaults.AuthenticationScheme);
        });
});

Este código também define o esquema de autenticação, que informa ao ASP.NET que middleware de autenticação deve ser executada se a autorização falhar.This code also sets the authentication scheme, which tells ASP.NET which authentication middleware should run if authorization fails. Neste caso, especificamos o middleware de autenticação de cookie, porque o middleware de autenticação de cookie pode redirecionar o utilizador para uma página de "dizer proibido".In this case, we specify the cookie authentication middleware, because the cookie authentication middleware can redirect the user to a "Forbidden" page. A localização da página proibido é definida AccessDeniedPath opção para o middleware de cookie; veja Configurar o middleware de autenticação.The location of the Forbidden page is set in the AccessDeniedPath option for the cookie middleware; see Configuring the authentication middleware.

Autorizar ações do controladorAuthorize controller actions

Por fim, para autorizar uma ação num controlador MVC, definir a política no Authorize atributo:Finally, to authorize an action in an MVC controller, set the policy in the Authorize attribute:

[Authorize(Policy = PolicyNames.RequireSurveyCreator)]
public IActionResult Create()
{
    var survey = new SurveyDTO();
    return View(survey);
}

Nas versões anteriores do ASP.NET, definiria a funções propriedade no atributo:In earlier versions of ASP.NET, you would set the Roles property on the attribute:

// old way
[Authorize(Roles = "SurveyCreator")]

Isto ainda é suportado no ASP.NET Core, mas tem algumas desvantagens em comparação com diretivas de autorização:This is still supported in ASP.NET Core, but it has some drawbacks compared with authorization policies:

  • Parte do princípio de um tipo de afirmação específico.It assumes a particular claim type. Podem verificar as políticas de qualquer tipo de afirmação.Policies can check for any claim type. As funções são apenas um tipo de afirmação.Roles are just a type of claim.
  • O nome da função está hard-coded para o atributo.The role name is hard-coded into the attribute. Com as políticas, a lógica de autorização é tudo num único local, facilitando a atualização ou carga uniforme de definições de configuração.With policies, the authorization logic is all in one place, making it easier to update or even load from configuration settings.
  • As políticas permitem decisões de autorização mais complexas (por exemplo, idade > = 21) que não podem ser expressos pela associação de função simples.Policies enable more complex authorization decisions (e.g., age >= 21) that can't be expressed by simple role membership.

Autorização do recurso com base emResource based authorization

Recursos com base em autorização ocorre sempre que a autorização depende de um recurso específico que será afetado por uma operação.Resource based authorization occurs whenever the authorization depends on a specific resource that will be affected by an operation. Na aplicação Tailspin Surveys, cada pesquisa tem um proprietário e os contribuintes de zero-para-muitos.In the Tailspin Surveys application, every survey has an owner and zero-to-many contributors.

  • O proprietário pode ler, atualizar, eliminar, publicar e anular a publicação de pesquisa.The owner can read, update, delete, publish, and unpublish the survey.
  • O proprietário pode atribuir contribuidores à pesquisa.The owner can assign contributors to the survey.
  • Os contribuintes podem ler e atualizar a pesquisa.Contributors can read and update the survey.

Tenha em atenção que o "proprietário" e "Contribuidor" não são funções de aplicação eles são armazenados por pesquisa, na base de dados de aplicação.Note that "owner" and "contributor" are not application roles; they are stored per survey, in the application database. Para verificar se um utilizador pode eliminar uma pesquisa, por exemplo, a aplicação verifica se o usuário é o proprietário para essa pesquisa.To check whether a user can delete a survey, for example, the app checks whether the user is the owner for that survey.

No ASP.NET Core, implementar a autorização baseada em recursos, derivando de AuthorizationHandler e substituindo o processar método.In ASP.NET Core, implement resource-based authorization by deriving from AuthorizationHandler and overriding the Handle method.

public class SurveyAuthorizationHandler : AuthorizationHandler<OperationAuthorizationRequirement, Survey>
{
    protected override void HandleRequirementAsync(AuthorizationHandlerContext context, OperationAuthorizationRequirement operation, Survey resource)
    {
    }
}

Tenha em atenção que esta classe tem rigidez de tipos de objetos de pesquisa.Notice that this class is strongly typed for Survey objects. Registre-se a classe de injeção de dependência na inicialização:Register the class for DI on startup:

services.AddSingleton<IAuthorizationHandler>(factory =>
{
    return new SurveyAuthorizationHandler();
});

Para efetuar verificações de autorização, utilize o IAuthorizationService interface, que pode injetar os controladores.To perform authorization checks, use the IAuthorizationService interface, which you can inject into your controllers. O código a seguir verifica se um utilizador pode ler uma pesquisa:The following code checks whether a user can read a survey:

if (await _authorizationService.AuthorizeAsync(User, survey, Operations.Read) == false)
{
    return StatusCode(403);
}

Uma vez que introduzimos uma Survey objeto, essa chamada invocará o SurveyAuthorizationHandler.Because we pass in a Survey object, this call will invoke the SurveyAuthorizationHandler.

Em seu código de autorização, uma boa abordagem é agregar todas as permissões do utilizador baseada em funções e baseada em recursos, em seguida, verifique o agregado definido em relação a operação pretendida.In your authorization code, a good approach is to aggregate all of the user's role-based and resource-based permissions, then check the aggregate set against the desired operation. Eis um exemplo a partir da aplicação de inquéritos.Here is an example from the Surveys app. O aplicativo define vários tipos de permissão:The application defines several permission types:

  • AdministraçãoAdmin
  • ContribuinteContributor
  • CriadorCreator
  • ProprietárioOwner
  • LeitorReader

O aplicativo também define um conjunto de operações possíveis em inquéritos:The application also defines a set of possible operations on surveys:

  • CriarCreate
  • LeituraRead
  • AtualizarUpdate
  • EliminarDelete
  • PublicarPublish
  • Anular publicaçãoUnpublish

O código seguinte cria uma lista de permissões para um usuário específico e pesquisa.The following code creates a list of permissions for a particular user and survey. Tenha em atenção que este código analisa as funções de aplicação do utilizador e os campos de proprietário/Contribuidor na pesquisa.Notice that this code looks at both the user's app roles, and the owner/contributor fields in the survey.

public class SurveyAuthorizationHandler : AuthorizationHandler<OperationAuthorizationRequirement, Survey>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, OperationAuthorizationRequirement requirement, Survey resource)
    {
        var permissions = new List<UserPermissionType>();
        int surveyTenantId = context.User.GetSurveyTenantIdValue();
        int userId = context.User.GetSurveyUserIdValue();
        string user = context.User.GetUserName();

        if (resource.TenantId == surveyTenantId)
        {
            // Admin can do anything, as long as the resource belongs to the admin's tenant.
            if (context.User.HasClaim(ClaimTypes.Role, Roles.SurveyAdmin))
            {
                context.Succeed(requirement);
                return Task.FromResult(0);
            }

            if (context.User.HasClaim(ClaimTypes.Role, Roles.SurveyCreator))
            {
                permissions.Add(UserPermissionType.Creator);
            }
            else
            {
                permissions.Add(UserPermissionType.Reader);
            }

            if (resource.OwnerId == userId)
            {
                permissions.Add(UserPermissionType.Owner);
            }
        }
        if (resource.Contributors != null && resource.Contributors.Any(x => x.UserId == userId))
        {
            permissions.Add(UserPermissionType.Contributor);
        }

        if (ValidateUserPermissions[requirement](permissions))
        {
            context.Succeed(requirement);
        }
        return Task.FromResult(0);
    }
}

Numa aplicação multi-inquilino, tem de se certificar de que as permissões não "vazam" para dados de outro inquilino.In a multi-tenant application, you must ensure that permissions don't "leak" to another tenant's data. Na aplicação de inquéritos, a permissão de contribuinte é permitida em inquilinos — pode atribuir alguém de outro inquilino como um contribuinte.In the Surveys app, the Contributor permission is allowed across tenants — you can assign someone from another tenant as a contributor. Os outros tipos de permissão são restritos a recursos que pertencem ao inquilino desse utilizador.The other permission types are restricted to resources that belong to that user's tenant. Para impor a este requisito, o código verifica o ID de inquilino antes de conceder a permissão.To enforce this requirement, the code checks the tenant ID before granting the permission. (O TenantId campo como atribuído quando a pesquisa é criada.)(The TenantId field as assigned when the survey is created.)

A próxima etapa é verificar a operação (leitura, atualização, eliminação, etc.) contra as permissões.The next step is to check the operation (read, update, delete, etc) against the permissions. A aplicação de inquéritos implementa neste passo, ao utilizar uma tabela de pesquisa de funções:The Surveys app implements this step by using a lookup table of functions:

static readonly Dictionary<OperationAuthorizationRequirement, Func<List<UserPermissionType>, bool>> ValidateUserPermissions
    = new Dictionary<OperationAuthorizationRequirement, Func<List<UserPermissionType>, bool>>

    {
        { Operations.Create, x => x.Contains(UserPermissionType.Creator) },

        { Operations.Read, x => x.Contains(UserPermissionType.Creator) ||
                                x.Contains(UserPermissionType.Reader) ||
                                x.Contains(UserPermissionType.Contributor) ||
                                x.Contains(UserPermissionType.Owner) },

        { Operations.Update, x => x.Contains(UserPermissionType.Contributor) ||
                                x.Contains(UserPermissionType.Owner) },

        { Operations.Delete, x => x.Contains(UserPermissionType.Owner) },

        { Operations.Publish, x => x.Contains(UserPermissionType.Owner) },

        { Operations.UnPublish, x => x.Contains(UserPermissionType.Owner) }
    };

SeguinteNext