Op rollen en resources gebaseerde autorisatie

Voorbeeldcode van

Onze referentie-implementatie is een ASP.NET Core toepassing. In dit artikel kijken we naar twee algemene benaderingen voor autorisatie, met behulp van de autorisatie-API's die zijn opgegeven in ASP.NET Core.

  • Autorisatie op basis van rollen. Autoriseren van een actie op basis van de rollen die zijn toegewezen aan een gebruiker. Voor sommige acties is bijvoorbeeld een beheerdersrol vereist.
  • Op resources gebaseerde autorisatie. Autoriseren van een actie op basis van een bepaalde resource. Elke resource heeft bijvoorbeeld een eigenaar. De eigenaar kan de resource verwijderen; andere gebruikers kunnen dat niet.

Een typische app maakt gebruik van een combinatie van beide. Als u bijvoorbeeld een resource wilt verwijderen, moet de gebruiker de resource-eigenaar of beheerder zijn.

Autorisatie op basis van rollen

De toepassing Tailspin Surveys definieert de volgende rollen:

  • Beheerder. Kan alle CRUD-bewerkingen uitvoeren op elke enquête die bij die tenant hoort.
  • Schepper. Kan nieuwe enquêtes maken
  • Lezer. Kan enquêtes lezen die deel uitmaken van die tenant

Rollen zijn van toepassing op gebruikers van de toepassing. In de toepassing Surveys is een gebruiker een beheerder, maker of lezer.

Zie Toepassingsrollen voor een discussie over het definiëren en beheren [van rollen.]

Uw autorisatiecode ziet er ongeveer hetzelfde uit, ongeacht hoe u de rollen beheert. ASP.NET Core heeft een abstractie die autorisatiebeleid wordt genoemd. Met deze functie definieert u autorisatiebeleid in code en pas u dit beleid vervolgens toe op controlleracties. Het beleid wordt losgekoppeld van de controller.

Beleidsregels maken

Als u een beleid wilt definiëren, maakt u eerst een klasse die IAuthorizationRequirement implementeert. Het is het gemakkelijkst om af te leiden van AuthorizationHandler . Bekijk in Handle de methode de relevante claim(s).

Hier is een voorbeeld van de toepassing Tailspin Surveys:

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

Deze klasse definieert de vereiste dat een gebruiker een nieuwe enquête moet maken. De gebruiker moet de rol SurveyAdmin of SurveyCreator hebben.

Definieer in uw opstartklasse een benoemd beleid dat een of meer vereisten bevat. Als er meerdere vereisten zijn, moet de gebruiker aan elke vereiste voldoen om te worden geautoriseerd. De volgende code definieert twee beleidsregels:

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

Deze code stelt ook het verificatieschema in, waarmee wordt ASP.NET welke verificatie-middleware moet worden uitgevoerd als de autorisatie mislukt. In dit geval geven we de cookieverificatie-middleware op, omdat de cookieverificatie-middleware de gebruiker kan omleiden naar de pagina 'Verboden'. De locatie van de pagina Verboden is ingesteld in de optie AccessDeniedPath voor de cookie-middleware. Zie [Configuring the authentication middleware (De verificatie-middleware configureren).]

Controlleracties autor toestemming geven

Als u ten slotte een actie in een MVC-controller wilt autorschrijven, stelt u het beleid in het Authorize kenmerk in:

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

In eerdere versies van ASP.NET stelt u de eigenschap Roles in op het kenmerk :

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

Dit wordt nog steeds ondersteund in ASP.NET Core, maar er zijn enkele nadelen ten opzichte van autorisatiebeleid:

  • Er wordt uit van een bepaald claimtype uitgenomen. Beleidsregels kunnen controleren op elk claimtype. Rollen zijn slechts een type claim.
  • De naam van de rol is in code gecodeerd in het kenmerk . Met beleidsregels is de autorisatielogica allemaal op één plek, waardoor het eenvoudiger is om configuratie-instellingen bij te werken of zelfs te laden.
  • Beleid maakt complexere autorisatiebeslissingen mogelijk (bijvoorbeeld leeftijd >= 21) die niet kunnen worden uitgedrukt door eenvoudig rollidmaatschap.

Op resources gebaseerde autorisatie

Op resources gebaseerde autorisatie vindt plaats wanneer de autorisatie afhankelijk is van een specifieke resource die wordt beïnvloed door een bewerking. In de toepassing Tailspin Surveys heeft elke enquête een eigenaar en nul-op-veel-inzenders.

  • De eigenaar kan de enquête lezen, bijwerken, verwijderen, publiceren en de publicatie ongedaan maken.
  • De eigenaar kan inzenders toewijzen aan de enquête.
  • Inzenders kunnen de enquête lezen en bijwerken.

Houd er rekening mee dat 'eigenaar' en 'inzender' geen toepassingsrollen zijn; Ze worden per enquête opgeslagen in de toepassingsdatabase. Om te controleren of een gebruiker bijvoorbeeld een enquête kan verwijderen, controleert de app of de gebruiker de eigenaar van die enquête is.

Implement ASP.NET Core op resources gebaseerde autorisatie door deze af te vragen van AuthorizationHandler en de handle-methode te overschrijven.

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

U ziet dat deze klasse sterk wordt getypt voor enquêteobjecten. Registreer de klasse voor di bij het opstarten:

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

Als u autorisatiecontroles wilt uitvoeren, gebruikt u de interface IAuthorizationService, die u in uw controllers kunt injecteren. Met de volgende code wordt gecontroleerd of een gebruiker een enquête kan lezen:

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

Omdat we een Survey -object doorgeven, roept deze aanroep de SurveyAuthorizationHandler aan.

In uw autorisatiecode is het een goede benadering om alle machtigingen op basis van rollen en resources van de gebruiker te aggregeren en vervolgens de aggregatieset te controleren op basis van de gewenste bewerking. Hier is een voorbeeld van de app Surveys. De toepassing definieert verschillende machtigingstypen:

  • Beheerder
  • Inzender
  • Creator
  • Eigenaar
  • Lezer

De toepassing definieert ook een reeks mogelijke bewerkingen voor enquêtes:

  • Maken
  • Raadplegen
  • Bijwerken
  • Verwijderen
  • Publiceren
  • Publicatie ongedaan maken

Met de volgende code maakt u een lijst met machtigingen voor een bepaalde gebruiker en enquête. U ziet dat deze code zowel de app-rollen van de gebruiker als de velden eigenaar/inzender in de enquête bekijkt.

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

In een multitenant-toepassing moet u ervoor zorgen dat machtigingen niet 'lekken' naar de gegevens van een andere tenant. In de app Surveys is de machtiging Inzender toegestaan voor alle tenants die u als inzender kunt toewijzen aan iemand van — een andere tenant. De andere machtigingstypen zijn beperkt tot resources die deel uitmaken van de tenant van die gebruiker. Om deze vereiste af te dwingen, controleert de code de tenant-id voordat de machtiging wordt verleend. (Het TenantId veld zoals toegewezen wanneer de enquête wordt gemaakt.)

De volgende stap is het controleren van de bewerking (zoals lezen, bijwerken of verwijderen) op de machtigingen. De app Surveys implementeert deze stap met behulp van een opzoektabel met functies:

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

Volgende