Пользовательские политики авторизации с IAuthorizationRequirementData
Рассмотрим следующий пример, реализующий настраиваемый 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();
Класс 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;
}
}
Настраиваемый MinimumAgePolicyProvider
:
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 использует только один поставщик политики авторизации. Если пользовательская реализация не обрабатывает все политики, включая политики по умолчанию и т. д., он должен вернуться к альтернативному поставщику. В предыдущем примере поставщик политики авторизации по умолчанию:
- Создано с параметрами из контейнера внедрения зависимостей.
- Используется, если этот пользовательский поставщик не может обрабатывать заданное имя политики.
Если поставщик настраиваемых политик может обрабатывать все ожидаемые имена политик, установка резервной политики GetFallbackPolicyAsync() не требуется.
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();
Политики ищутся по имени строки, поэтому параметры, например, age
встраиваются в имена политик. Это абстрагируется от разработчиков более строго типизированными атрибутами, производными от AuthorizeAttribute. Например, [MinimumAgeAuthorize()]
атрибут в этом примере ищет политики по имени строки.
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);
}
IAuthorizationRequirementData Использует MinimumAgeAuthorizeAttribute
интерфейс, позволяющий определению атрибута указать требования, связанные с политикой авторизации:
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;
}
}
Отображается GreetingsController
имя пользователя при удовлетворении минимальной политики возраста:
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")}!";
}
Полный пример можно найти в папке AuthRequirementsData репозитория AspNetCore.Docs.Samples .
Пример можно проверить с помощью dotnet user-jwts
и 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
ASP.NET Core