Фильтры авторизации

142

Фильтры авторизации запускаются после фильтров аутентификации, перед фильтрами действий и до того, как будут вызваны методы действий. Согласно своему названию, эти фильтры принудительно применяют выбранную политику авторизации, гарантируя, что методы действий могут быть вызваны только проверенными пользователями.

Отношение между фильтрами аутентификации и авторизации проще объяснить после того, как станет понятной работа фильтров авторизации. Фильтры авторизации реализуют интерфейс IAuthorizationFilter, который приведен в примере ниже:

namespace System.Web.Mvc 
{
    public interface IAuthorizationFilter
    {
        void OnAuthorization(AuthorizationContext filterContext);
    }
}

Если вы настолько уверены в собственной квалификации, можете создать класс, реализующий интерфейс IAuthorizationFilter, и построить собственную логику защиты.

Предостережение: написание кода защиты является небезопасным

История программирования изобилует случаями взлома приложений, разработчики которых были уверены, что они умеют писать качественный код защиты. К сожалению, этим навыком обладают очень немногие. Как правило, остаются шероховатости и непроверенные краевые условия, которые приводят к образованию брешей в системе безопасности приложения. В Интернете можно найти немало статей по этой теме.

Когда только возможно, я предпочитаю пользоваться кодом защиты, который хорошо протестирован и апробирован. В данном случае ASP.NET MVC Framework предоставляет полнофункциональный фильтр авторизации, от которого можно создать производный класс для реализации специальных политик авторизации. Я стараюсь применять его по возможности всегда, и рекомендую вам поступать аналогично. Во всяком случае, если ваши секретные данные приложения распространятся широко в Интернете, то вина за это частично ляжет на Microsoft.

Намного более безопасный подход предполагает создание подкласса для класса AuthorizeAttribute, который позаботится обо всех сложных аспектах и упростит, написание специального кода авторизации. Наилучший способ продемонстрировать это - создать специальный фильтр, для чего мы добавили в пример проекта папку Infrastructure и создали в ней новый файл класса по имени CustomAuthAttribute.cs. Содержимое этого файла показано в примере ниже:

using System.Web;
using System.Web.Mvc;

namespace Filter.Infrastructure
{
    public class CustomAuthAttribute : AuthorizeAttribute
    {
        private bool localAllowed;

        public CustomAuthAttribute(bool allowedParam)
        {
            localAllowed = allowedParam;
        }

        protected override bool AuthorizeCore(HttpContextBase httpContext)
        {
            if (httpContext.Request.IsLocal)
            {
                return localAllowed;
            }
            else
            {
                return true;
            }
        }
    }
}

Это довольно простой фильтр авторизации. Он позволяет предотвратить доступ локальным запросам (локальным является запрос, при котором браузер и сервер приложений функционируют на одном и том же устройстве, таком как машина разработки).

Для построения фильтра авторизации мы использовали простейший подход, который предусматривает создание подкласса для класса AuthorizeAttribute и затем переопределение метода AuthorizeCore(). Это обеспечивает возможность применения функциональности, встроенной в класс AuthorizeAttribute. Конструктор класса фильтра принимает значение bool, которое указывает, разрешены ли локальные запросы.

Интересной частью класса фильтра является реализация метода AuthorizeCore(), которая демонстрирует, каким образом инфраструктура MVC Framework проверяет, авторизует ли фильтр доступ для запроса. Этот метод принимает аргумент - объект HttpContextBase, через который можно получить информацию об обрабатываемом запросе. Получив в свое распоряжение встроенные средства базового класса AuthorizeAttribute, мы должны сосредоточиться только на собственной логике авторизации и возвращать из метода AuthorizeCore() значение true, если запрос необходимо авторизовать, и false - в противном случае.

Методу AuthorizeCore() передается объект HttpContextBase, предоставляющий доступ к информации о запросе, но не о контроллере или методе действия, к которому был применен атрибут авторизации. Главная причина в том, что разработчики, реализующие интерфейс IAuthorizationFilter напрямую, имеют доступ к передаваемому в метод OnAuthorization() объекту AuthorizationContext, через который можно получить более широкий спектр информации, включая детали маршрутизации, а также текущий контроллер и метод действия.

Такой подход применять не рекомендуется - и не только потому, что написание собственно кода защиты небезопасно. Хотя авторизация является сквозной ответственностью, встраивание в атрибуты авторизации логики, которая тесно связана со структурой контроллеров, нарушает принцип разделения ответственности и приводит к проблемам при тестировании и сопровождении. Сохраняйте атрибуты авторизации простыми и ориентированными на авторизацию на основе запросов.

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

using System.Web.Mvc;
using Filter.Infrastructure;

namespace Filter.Controllers
{
    public class HomeController : Controller
    {
        [CustomAuth(false)]
        public string Index()
        {
            return "Это метод действия Index в контроллере Home";
        }
    }
}

В качестве значения аргумента для фильтра указано false, т.е. локальным запросам будет запрещен доступ к методу действия Index(). Это можно проверить, запустив приложение. При запросе корневого URL в браузере конфигурация маршрутизации направит на метод действия Index(). Если браузер, отправивший запрос, функционирует на машине с запущенной средой Visual Studio, вы увидите результат, показанный на рисунке ниже:

Предложение ввести учетные данные для локального запроса специальным фильтром авторизации

Фильтр отклоняет авторизацию для запроса, а инфраструктура MVC Framework реагирует единственным способом, который ей известен: предлагает пользователю ввести свои учетные данные. Разумеется, имя пользователя или пароль не меняет тот факт, что запрос поступает от локальной машины, поэтому в данный момент нет ничего, чем можно было бы ответить на проблему аутентификации.

Тем не менее, фильтр авторизует этот запрос, если вы измените значение аргумента конструктора на true и перезапустите приложение. (Провести проверку, сделав запрос из другой машины, не получится, т.к. сервер IIS Express, выполняющий приложение, сконфигурирован на отклонение любых подключений, не являющихся локальными.)

Использование встроенного фильтра авторизации

Хотя мы используем класс AuthorizeAttribute в качестве базового для своего специального фильтра, он имеет собственную реализацию метода AuthorizeCore(), который полезен для выполнения задач авторизации общего назначения. В случае применении AuthorizeAttribute напрямую задать свою политику авторизации можно с помощью двух открытых свойств этого класса:

Свойство Users

Список с разделителями-запятыми имен пользователей, которым разрешен доступ к данному методу действия

Свойство Roles

Список с разделителями-запятыми имен ролей. Для доступа к данному методу действия пользователи должны принадлежать, по крайней мере, к одной из этих ролей

В примере ниже показано, как использовать этот встроенный фильтр для защиты метода действия с помощью описанных выше свойств:

using System.Web.Mvc;
using Filter.Infrastructure;

namespace Filter.Controllers
{
    public class HomeController : Controller
    {
        [Authorize(Users="admin")]
        public string Index()
        {
            return "Это метод действия Index в контроллере Home";
        }
    }
}

В примере определено, что пользователь admin авторизован для вызова метода действия Index(), но имеется также неявное условие: запрос является аутентифицированным. Если имена пользователей или роли не указаны, то выполнить метод действия может любой аутентифицированный пользователь. Для большинства приложений политики авторизации, предоставляемой AuthorizeAttribute, вполне достаточно. Если хотите реализовать что-то специфичное, можете создать класс, производный от AuthorizeAttribute, как это делалось ранее, или предоставить собственную конфигурацию с фильтрами аутентификации, которые рассматриваются в следующей статье.

Пройди тесты
Лучший чат для C# программистов