Políticas de autorização personalizadas com IAuthorizationRequirementData
Considere o seguinte exemplo que implementa um personalizado MinimumAgeAuthorizationHandler
:
using AuthRequirementsData.Authorization;
using Microsoft.AspNetCore.Authorization;
var builder = WebApplication.CreateBuilder();
builder.Services.AddAuthentication().AddJwtBearer();
builder.Services.AddAuthorization();
builder.Services.AddControllers();
builder.Services.AddSingleton<IAuthorizationHandler, MinimumAgeAuthorizationHandler>();
var app = builder.Build();
app.MapControllers();
app.Run();
A classe MinimumAgeAuthorizationHandler
:
using Microsoft.AspNetCore.Authorization;
using System.Globalization;
using System.Security.Claims;
namespace AuthRequirementsData.Authorization;
class MinimumAgeAuthorizationHandler : AuthorizationHandler<MinimumAgeAuthorizeAttribute>
{
private readonly ILogger<MinimumAgeAuthorizationHandler> _logger;
public MinimumAgeAuthorizationHandler(ILogger<MinimumAgeAuthorizationHandler> logger)
{
_logger = logger;
}
// Check whether a given MinimumAgeRequirement is satisfied or not for a particular
// context.
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
MinimumAgeAuthorizeAttribute requirement)
{
// Log as a warning so that it's very clear in sample output which authorization
// policies(and requirements/handlers) are in use.
_logger.LogWarning("Evaluating authorization requirement for age >= {age}",
requirement.Age);
// Check the user's age.
var dateOfBirthClaim = context.User.FindFirst(c => c.Type ==
ClaimTypes.DateOfBirth);
if (dateOfBirthClaim != null)
{
// If the user has a date of birth claim, check their age.
var dateOfBirth = Convert.ToDateTime(dateOfBirthClaim.Value,
CultureInfo.InvariantCulture);
var age = DateTime.Now.Year - dateOfBirth.Year;
if (dateOfBirth > DateTime.Now.AddYears(-age))
{
// Adjust age if the user hasn't had a birthday yet this year.
age--;
}
// If the user meets the age criterion, mark the authorization requirement
// succeeded.
if (age >= requirement.Age)
{
_logger.LogInformation(
"Minimum age authorization requirement {age} satisfied",
requirement.Age);
context.Succeed(requirement);
}
else
{
_logger.LogInformation("Current user's DateOfBirth claim ({dateOfBirth})"
+ " does not satisfy the minimum age authorization requirement {age}",
dateOfBirthClaim.Value,
requirement.Age);
}
}
else
{
_logger.LogInformation("No DateOfBirth claim present");
}
return Task.CompletedTask;
}
}
O MinimumAgePolicyProvider
personalizado:
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Options;
namespace AuthRequirementsData.Authorization;
class MinimumAgePolicyProvider : IAuthorizationPolicyProvider
{
const string POLICY_PREFIX = "MinimumAge";
public DefaultAuthorizationPolicyProvider FallbackPolicyProvider { get; }
public MinimumAgePolicyProvider(IOptions<AuthorizationOptions> options)
{
FallbackPolicyProvider = new DefaultAuthorizationPolicyProvider(options);
}
public Task<AuthorizationPolicy> GetDefaultPolicyAsync() =>
FallbackPolicyProvider.GetDefaultPolicyAsync();
public Task<AuthorizationPolicy?> GetFallbackPolicyAsync() =>
FallbackPolicyProvider.GetFallbackPolicyAsync();
public Task<AuthorizationPolicy?> GetPolicyAsync(string policyName)
{
if (policyName.StartsWith(POLICY_PREFIX, StringComparison.OrdinalIgnoreCase) &&
int.TryParse(policyName.Substring(POLICY_PREFIX.Length), out var age))
{
var policy = new AuthorizationPolicyBuilder(
JwtBearerDefaults.AuthenticationScheme);
policy.AddRequirements(new MinimumAgeRequirement(age));
return Task.FromResult<AuthorizationPolicy?>(policy.Build());
}
return Task.FromResult<AuthorizationPolicy?>(null);
}
}
ASP.NET Core usa apenas um provedor de política de autorização. Se a implementação personalizada não manipular todas as políticas, incluindo políticas padrão, etc., ela deverá voltar para um provedor alternativo. No exemplo anterior, um provedor de política de autorização padrão é:
- Construído com opções do contêiner de injeção de dependência.
- Usado se esse provedor personalizado não for capaz de lidar com um determinado nome de política.
Se um provedor de política personalizado for capaz de lidar com todos os nomes de política esperados, a definição da política de fallback com GetFallbackPolicyAsync() não será necessária.
class MinimumAgePolicyProvider : IAuthorizationPolicyProvider
{
const string POLICY_PREFIX = "MinimumAge";
public DefaultAuthorizationPolicyProvider FallbackPolicyProvider { get; }
public MinimumAgePolicyProvider(IOptions<AuthorizationOptions> options)
{
FallbackPolicyProvider = new DefaultAuthorizationPolicyProvider(options);
}
public Task<AuthorizationPolicy> GetDefaultPolicyAsync() =>
FallbackPolicyProvider.GetDefaultPolicyAsync();
public Task<AuthorizationPolicy?> GetFallbackPolicyAsync() =>
FallbackPolicyProvider.GetFallbackPolicyAsync();
As políticas são pesquisadas pelo nome da cadeia de caracteres, portanto, os parâmetros, por exemplo, age
, são inseridos nos nomes da política. Isso é abstraído dos desenvolvedores pelos atributos mais fortemente tipados derivados de AuthorizeAttribute. Por exemplo, o atributo [MinimumAgeAuthorize()]
nesse exemplo pesquisa políticas por nome de cadeia de caracteres.
public Task<AuthorizationPolicy?> GetPolicyAsync(string policyName)
{
if (policyName.StartsWith(POLICY_PREFIX, StringComparison.OrdinalIgnoreCase) &&
int.TryParse(policyName.Substring(POLICY_PREFIX.Length), out var age))
{
var policy = new AuthorizationPolicyBuilder(
JwtBearerDefaults.AuthenticationScheme);
policy.AddRequirements(new MinimumAgeRequirement(age));
return Task.FromResult<AuthorizationPolicy?>(policy.Build());
}
return Task.FromResult<AuthorizationPolicy?>(null);
}
O MinimumAgeAuthorizeAttribute
usa a interface IAuthorizationRequirementData que permite que a definição de atributo especifique os requisitos associados à política de autorização:
using Microsoft.AspNetCore.Authorization;
namespace AuthRequirementsData.Authorization;
class MinimumAgeAuthorizeAttribute : AuthorizeAttribute, IAuthorizationRequirement,
IAuthorizationRequirementData
{
public MinimumAgeAuthorizeAttribute(int age) => Age = age;
public int Age { get; }
public IEnumerable<IAuthorizationRequirement> GetRequirements()
{
yield return this;
}
}
O GreetingsController
exibe o nome do usuário quando ele atende à política de idade mínima:
using AuthRequirementsData.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace AuthRequirementsData.Controllers;
[ApiController]
[Route("api/[controller]")]
public class GreetingsController : Controller
{
[MinimumAgeAuthorize(16)]
[HttpGet("hello")]
public string Hello() => $"Hello {(HttpContext.User.Identity?.Name ?? "world")}!";
}
O exemplo completo pode ser encontrado na pasta AuthRequirementsData do repositório AspNetCore.Docs.Samples .
O exemplo pode ser testado com dotnet user-jwts
e curl:
dotnet user-jwts create --claim http://schemas.xmlsoap.org/ws/2005/05/identity/claims/dateofbirth=1989-01-01
curl -i -H "Authorization: Bearer <token from dotnet user-jwts>" http://localhost:<port>/api/greetings/hello