V minulém článku jsme si ukázali jak vytvořit a využít policy-based autorizaci pomocí autorizačního atributu [Authorize(Policy = "PolicyName")].

Dnes si ukážeme jak využít policy pro resource-based autorizaci - autorizace na úrovni objektů.

Pokud se podíváme do implementace metody AddAuthorization, kterou registrujeme služby pro autorizaci ve Startup.cs, tak můžeme vidět několik dalších zaregistrovaných služeb. Kromě základních handlerů a providerů pro Autorizaci se zde nachází taky registrace služby IAuthorizationService, která obsahuje dvě metody:


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

V první metodě můžeme využít zaregistrované policy a zadat jen její název, a nebo můžete přímo předat kolekci implementací IAuthorizationRequirement, které slouží jako zdroj dat pro vyhodnocení autorizačního handleru.

Příklad - máme zdroj (metodu kontroleru) a vrací detail článku, který může získat jen její autor.

Vytvoříme autorizační requirement a handler:

public class AuthorAuthorizationRequirement : IAuthorizationRequirement{ }

public class AuthorAuthorizationHandler : AuthorizationHandler<AuthorAuthorizationRequirement, Article>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, AuthorAuthorizationRequirement requirement, Article resource)
    {
        if(context.User.Identity.Name == resource.Author) //Pokud se autentizovaný uživatel shoduje s autorem článku, má právo získat článek
        {
            context.Succeed(requirement);
        }
        return Task.CompletedTask;
    }
}

A potom hadler i policy zaregistrujeme:

services.AddAuthorization(options =>
{
     options.AddPolicy("Editor", policy => policy.Requirements.Add(new AuthorAuthorizationRequirement()));
});
services.AddSingleton<IAuthorizationHandler, AuthorAuthorizationHandler>();

Pomocí dependency injection získáme základní implementaci IAuthorizationService a zavoláme metodu AuthorizeAsync.

[Route("articles")]
[ApiController]
public class ArticlesController : ControllerBase
{
    private readonly IAuthorizationService authorizationService;
    private readonly IArticleService articleService;

    public ArticlesController(IAuthorizationService authorizationService, IArticleService articleService)
    {
        this.authorizationService = authorizationService;
        this.articleService = articleService;
    }
 
    [HttpGet]
    [Route("{id}")]
    public async Task<IActionResult> Get(int id)
    {
        var article = articleService.GetArticle(id); //Získáme článek
        var authResult = await authorizationService.AuthorizeAsync(User, article, "Editor"); //Ověříme zda má právo
        if(authResult.Succeeded)
        {
            return Ok(article);
        }
        return Forbid();
    }
}

Pokud jste dříve pracovali s autorizací v ASP.NET, tak nebylo moc jednoduchých možností jak autorizovat uživatele jinak než podle Rolí [Authorize(Roles="Admin")].

Při složitejších scénářích jste si museli vše řešit pomocí vlastní infrastruktury. Pomocí Policy-Based autorizaci máte více možností jak tyhle scénáře jednoduše řešit.

Policy-Based

Pomocí Policy-Based můžete jednoduše:

  • Autorizovat uživatele více způsoby - Role, Claim,...
  • Implementovat vlastní způsob autorizace
  • Oddělit logiku autorizace do vlastní knihovny
  • Lépe testovat
  • Mít méně vlastní infrastruktury

Policy je potřeba zaregistrovat na IServiceCollection - AddAuthorization na AuthorizationOptions.

services.AddAuthorization(options =>
{
    options.AddPolicy("AdminOrEditor", policy => policy.RequireRole("Admin", "Editor"));
});

Potom stačí nad zdroj uvést [Authorize(Policy="AdminOrEditor")]. AuthorizationPolicyBuilder obsahuje několik základních metod, které se hodí na jednoduché scénáře:

  • RequireRole
  • RequireClaim
  • RequireUserName
  • RequireAssertion
  • RequireAuthenticatedUser

Pokud se blíže podíváme na implementaci RequireRole, tak zjistíme, že metoda dělá základní validace a přidává do kolekce Requirements implementaci IAuthorozationRequirement -  RoleAuthorizationRequirement.

Pomocí těchto tříd můžete jednoduše implementovat vlastní autorizaci.

 

Příklad - máme zdroj, kde mají přístup jen uživatelé, kteří mají email společnosti - @company.com.

Vytvoříme třídu poděděním IAuthorizeRequirement a vytvoříme vlastní requirement.

public class CompanyDomainRequirement : IAuthorizationRequirement
{
    public CompanyDomainRequirement(string domain)
    {
        Domain = domain;
    }

    public string Domain { get; }
}

Potom vytvoříme handler, který bude kontrolovat email uživatele a na základě úspěšné validace se zavolá context.Succeed(requirement).

public class CompanyEmailHandler : AuthorizationHandler<CompanyDomainRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CompanyDomainRequirement requirement)
    {
        if (context.User.HasClaim(s => s.Type == ClaimTypes.Email))
        {
            var claim = context.User.FindFirst(s => s.Type == ClaimTypes.Email);
            if (claim.Value.Contains($"@{requirement.Domain}"))
            {
                context.Succeed(requirement);
            }
        }
        return Task.CompletedTask;
    }
}

 

A nakonec stačí k vytvořené policy zaregistrovat náš requirement a handler.


services.AddAuthorization(options =>
{
    options.AddPolicy("CompanyEmailPolicy", policy => policy.Requirements.Add(new CompanyDomainRequirement("company.com")));
});
services.AddSingleton<IAuthorizationHandler, CompanyEmailHandler>();



Potom stačí na konkrétním zdroji použít policy, která umožnít přístup jen uživatelů, s firemním emailem - [Authorize(Policy="CompanyEmailPolicy")].