Авторизация, основанная на доступе к ресурсу

Часто авторизация зависит от ресурса, к которому нужен доступ. Например, у вас есть документ, который может обновлять только его автор, а ресурс загружается из репозитория, прежде чем проходит авторизация. Это нельзя сделать с помощью атрибута Authorize, поскольку оценка атрибута происходит до связывания данных и перед тем, как код для загрузки ресурса запускается внутри метода действия. Вместо декларативной авторизации мы должны использовать императивную авторизацию, когда разработчик вызывает функцию авторизации внутри собственного кода.

Авторизация внутри кода

Авторизация реализуется в виде сервиса, IAuthorizationService, регистрируется в коллекции сервисов и теперь доступна для контроллеров с помощью DI.

public class DocumentController : Controller
{
    IAuthorizationService _authorizationService;

    public DocumentController(IAuthorizationService authorizationService)
    {
        _authorizationService = authorizationService;
    }
}

У IAuthorizationService есть два метода: одному вы передаете ресурс и имя политики, а другому вы передаете ресурс и список требований, которые нужно оценить.

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

Чтобы вызвать сервис, загружающий ресурс, внутри метода действия, вызовите метод AuthorizeAsync, который вам требуется. Например:

public async Task<IActionResult> Edit(Guid documentId)
{
    Document document = documentRepository.Find(documentId);

    if (document == null)
    {
        return new HttpNotFoundResult();
    }

    if (await authorizationService.AuthorizeAsync(User, document, "EditPolicy"))
    {
        return View(document);
    }
    else
    {
        return new ChallengeResult();
    }
}

Написание обработчика на основе ресурса

Написание обработчика для авторизации на основе ресурса не слишком отличается от написания обычного обработчика для требований. Вы создаете обычное требование, а затем реализуете обработчик, указывая требование, а также тип ресурса. Например, обработчик, который может принять ресурс Document, выглядит вот так:

public class DocumentAuthorizationHandler : AuthorizationHandler<MyRequirement, Document>
{
          public override Task HandleRequirementAsync(AuthorizationContext context,
                                                MyRequirement requirement,
                                                Document resource)
     {
         // Validate the requirement against the resource and identity.

        return Task.CompletedTask;
    }
}

Не забудьте зарегистрировать обработчик в методе ConfigureServices:

services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationHandler>();

Требования по операциям

Если вы принимаете решения, основываясь на операциях, таких как read, write, update и delete, то уже определенный класс OperationAuthorizationRequirement существует в пространстве имен Microsoft.AspNet.Authorization.Infrastructure. При помощи этого класса вы можете написать один обработчик с параметризованным операционным именем, а не создавать отдельные классы для каждой операции, которые должны предоставлять имя операции:

public static class Operations
{
    public static OperationAuthorizationRequirement Create =
        new OperationAuthorizationRequirement { Name = "Create" };
    public static OperationAuthorizationRequirement Read =
        new OperationAuthorizationRequirement   { Name = "Read" };
    public static OperationAuthorizationRequirement Update =
        new OperationAuthorizationRequirement { Name = "Update" };
    public static OperationAuthorizationRequirement Delete =
        new OperationAuthorizationRequirement { Name = "Delete" };
}

Обработчик может быть реализован следующим образом, где используется гипотетический класс Document в качестве ресурса:

public class DocumentAuthorizationHandler :
    AuthorizationHandler<OperationAuthorizationRequirement, Document>
{
          public override Task HandleRequirementAsync(AuthorizationContext context,
                                                OperationAuthorizationRequirement requirement,
                                                Document resource)
     {
         // Validate the operation using the resource, the identity and
         // the Name property value from the requirement.

        return Task.CompletedTask;
    }
}

Обработчик работает с OperationAuthorizationRequirement. При оценке код внутри обработчика должен принимать во внимание свойство Name предоставляемого требования.

Чтобы вызвать обработчик, при вызове вам нужно указать операцию AuthorizeAsync() в методе действия. Например:

if (await authorizationService.AuthorizeAsync(User, document, Operations.Read))
{
    return View(document);
}
else
{
    return new ChallengeResult();
}

В данном примере проверяется, способен ли User выполнить операцию Read для текущего экземпляра document. Если авторизация успешна, возвращается представление документа. Если авторизация провалилась, возвращенный ChallengeResult() обратится к связующему ПО авторизации, которое вернет соответствующий ответ, например, код статуса 401 или 403, или перенаправит пользователя на страницу с логином.

Поделись хорошей новостью с друзьями!
Следи за новостями!