Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

ASP_NET_MVC_4_Framework_s_primerami_na_C_dlya_p

.pdf
Скачиваний:
29
Добавлен:
19.03.2016
Размер:
17.66 Mб
Скачать

Рисунок 16-1: Запускаем приложение

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

Фильтры авторизации - это фильтры, которые запускаются первыми, перед любым другим фильтром или методом действия. Как следует из названия, эти фильтры осуществляют вашу политику авторизации, гарантируя, что методы действий могут быть вызваны только пользователями, имеющими право доступа. Фильтры авторизации реализуют интерфейс IAuthorizationFilter, показанный в листинге 16-6.

Листинг 16-6: Интерфейс IAuthorizationFilter

namespace System.Web.Mvc

{

public interface IAuthorizationFilter

{

void OnAuthorization(AuthorizationContext filterContext);

}

}

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

Предупреждение. Не пишите свой код безопасности!

Мы никогда не пишем собственный код безопасности. История программирования полна обломков приложений, разработчики которых думали, что могут написать хороший код безопасности. На самом деле, это - очень редкий навык. Как правило, остается какая-нибудь забытая или непротестированная мелочь, которая и становится зияющей дырой в безопасности приложения. Если вы нам не верите, просто загуглите фразу ошибка безопасности (или security bug) и просмотрите топ-результаты.

Везде, где это возможно, мы будем использовать хорошо протестированный и надежный код безопасности. Для таких случаев MVC Framework предоставляет полнофункциональный фильтр авторизации, который можно наследовать, чтобы реализовать пользовательские правила авторизации. Мы стараемся его использовать каждый раз, когда он нам подходит, и рекомендуем вам делать то же самое. По крайней мере, если закрытые данные вашего приложения распространяться по всему интернету, вы сможете переложить часть вины на Microsoft.

Более безопасный подход - создать подкласс класса AuthorizeAttribute, который будет содержать самый сложный код и облегчит написание пользовательского кода авторизации. Чтобы это продемонстрировать, мы создадим пользовательский фильтр. Добавим в проект папку

391

Infrastructure и создадим в нем класс под названием CustomAuthAttribute.cs. Содержимое этого файла показано в листинге 16-7.

Листинг 16-7: Пользовательский фильтр авторизации

using System;

using System.Collections.Generic; using System.Linq;

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

namespace Filters.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 напрямую, - это получить доступ к AuthorizationContext, который передается в метод OnAuthorization. Из него можно получить гораздо больше информации, в том числе о маршрутизации и текущем контроллере и методе действия.

392

Мы не рекомендуем этот подход, и не только потому, что считаем написание собственного кода безопасности опасным. Хотя авторизация и является сквозной функциональностью, но включение логики в атрибуты авторизации, которая тесно связана со структурой контроллеров, нарушает принцип разделения обязанностей, вызывает проблемы с тестированием и поддержкой. Пусть лучше атрибуты авторизации будут простыми и сфокусированными на запросах, а информация о контроллере или методе, к которому применяется авторизация, поставляется из этого контроллера или метода.

Применяем пользовательский фильтр авторизации

Чтобы использовать пользовательский фильтр авторизации, мы просто применяем атрибут к методам действий или контроллерам, которые мы хотим защитить, как показано в листинге 16-8. Здесь показано применение фильтра к методу действия Index контроллера Home.

Листинг 16-8: Применяем пользовательский фильтр авторизации

using System;

using System.Collections.Generic; using System.Linq;

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

using Filters.Infrastructure;

namespace Filters.Controllers

{

public class HomeController : Controller

{

[CustomAuth(false)] public string Index()

{

return "This is the Index action on the Home controller";

}

}

}

Мы установили значение false для аргумента конструктора, что означает, что локальным запросам будет отказано в доступе к методу действия Index. Чтобы проверить это, запустите приложение - когда браузером будет запрошен корневой URL, конфигурация маршрутизации направит нас на метод действия Index. Если отправляющий запрос браузер работает на той же машине, что и Visual Studio, то вы увидите результат, изображенный на рисунке 16-2. Фильтр авторизирует запрос, если он поступит от браузера, работающего на другом компьютере (или если мы присвоим аргументу конструктора фильтра значение true и перезапустим приложение).

Рисунок 16-2: Пользовательский фильтр авторизации отказывает в доступе локальным запросам

393

Используем встроенный фильтр авторизации

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

Используя AuthorizeAttribute напрямую, мы можем определить правила авторизации с помощью двух доступных свойств этого класса, как показано в таблице 16-2.

Таблица 16-2: Свойства AuthorizeAttribute

Название Тип

Описание

Users

string

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

методу действия.

Roles

string

Разделенный запятыми список названий ролей. Чтобы получить доступ к методу

 

действия, пользователь должен обладать по крайней мере одной из этих ролей.

В листинге 16-9 показано, как можно использовать эти встроенные фильтры и их свойства для защиты метода действия.

Листинг 16-9: Используем встроенный фильтр авторизации

using System;

using System.Collections.Generic; using System.Linq;

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

using Filters.Infrastructure; namespace Filters.Controllers

{

public class HomeController : Controller

{

[Authorize(Users = "adam, steve, jacqui", Roles = "admin")] public string Index()

{

return "This is the Index action on the Home controller";

}

}

}

В листинге мы указали как пользователей, так и роли. Это означает, что авторизация будет пройдена, только если оба условия будут выполнены: имя пользователя - adam, steve или jacqui и пользователь является администратором. По умолчанию, каждый запрос будет аутентифицирован. Если мы не указываем имена пользователей или роли для аутентификации, то любой авторизованный пользователь сможет использовать метод действия.

Подсказка

AuthorizeAttribute отвечает за авторизацию, но не за аутентификацию. Вы

можете использовать любую из встроенных систем аутентификации ASP.NET или разработать свою собственную (хотя, как отмечалось ранее, это не очень хорошая идея). Пока система аутентификации использует стандартные API ASP.NET, AuthorizeAttribute сможет ограничивать доступ к контроллерам и действиям.

394

Для большинства приложений достаточно правил авторизации, которые обеспечивает AuthorizeAttribute. Если вы хотите реализовать что-то особенное,

то можете наследовать этот класс, как мы делали ранее в этой главе.

Использование фильтров для исключений

Если при вызове метода действия было выброшено необработанное исключение, будет запущен фильтр исключений. Исключения могут поступать из:

другого фильтра (фильтра авторизации, действия или результата);

самого метода действия;

при выполнении результата действия (подробная информация о результатах действия дана в главе 15).

Создаем фильтр исключения

Фильтры исключений должны реализовывать интерфейс IExceptionFilter, который показан в листинге 16-10.

Листинг 16-10: Интерфейс IExceptionFilter

namespace System.Web.Mvc

{

public interface IExceptionFilter

{

void OnException(ExceptionContext filterContext);

}

}

Когда появится необработанное исключение, будет вызван метод OnException. Параметром для этого метода является объект ExceptionContext, который наследует от ControllerContext и имеет ряд полезных свойств, с помощью которых можно получить информацию о запросе. Они приведены в таблице 16-3.

Таблица 16-3: Свойства ControllerContext

Название

Тип

Описание

Controller

ControllerBase

Возвращает объект контроллера для данного запроса

HttpContext

HttpContextBase Обеспечивает доступ к информации о запросе и доступ к ответу

IsChildAction bool

RequestContext RequestContext

RouteData RouteData

Возвращает true, если это дочернее действие (будет кратко обсуждаться позже в этой главе и подробно в главе 18)

Предоставляет доступ к объекту HttpContext и данным маршрутизации, хотя и то, и то доступно через другие свойства

Возвращает данные маршрутизации для данного запроса

В дополнение к свойствам, наследованным от класса ControllerContext, класс ExceptionContext определяет некоторые дополнительные свойства, которые также полезны при работе с исключениями. Они показаны в таблице 16-4.

395

Подсказка

Мы разделили эти таблицы, потому что есть и другие классы контекста фильтров, которые наследуют от ControllerContext и которые мы представим по ходу этой

главы.

Таблица 16-4: Дополнительные свойства ExceptionContext

Название

Тип

Описание

ActionDescriptorActionDescriptorПредоставляет подробную информацию о методе действия

Result ActionResult

Exception

Exception

ExceptionHandled bool

Результат для метода действия; фильтр может отменить запрос, установив для этого свойства иное значение, кроме null

Необработанное исключение

Возвращает true, если другой фильтр отметил это исключение как обработанное

Сгенерированное исключение доступно через свойство Exception. Фильтр исключения может сообщить, что он обработал исключение, установив для свойства ExceptionHandled значение true. Вызываются все фильтры исключений, примененные к действию, даже если ExceptionHandled уже содержит true, так что лучше всегда проверять, обработано ли исключение другим фильтром, чтобы не решать повторно уже решенную проблему.

Примечание

Если ни один из фильтров исключений не установил свойству ExceptionHandled значение true, MVC Framework будет использовать стандартную процедуру

обработки исключений ASP.NET, которая приведет к самому нежелательному результату - "желтому экрану смерти".

Свойство Result используется фильтром исключений, чтобы сообщить MVC Framework дальнейшую последовательность действий. В основном это регистрация исключения и отображение соответствующего сообщения для пользователя. Чтобы продемонстрировать выполнение этих задач, мы создали новый класс под названием RangeExceptionAttribute.cs, который добавили в папку Infrastructure нашего проекта. Содержимое этого файла показано в листинге 16-11.

Листинг 16-11: Реализуем фильтр исключений

using System;

using System.Web.Mvc;

namespace Filters.Infrastructure

{

public class RangeExceptionAttribute : FilterAttribute, IExceptionFilter

{

public void OnException(ExceptionContext filterContext)

{

if (!filterContext.ExceptionHandled && filterContext.Exception is ArgumentOutOfRangeException)

{

filterContext.Result

= new RedirectResult("~/Content/RangeErrorPage.html"); filterContext.ExceptionHandled = true;

}

}

}

}

396

Content.

Этот фильтр исключений обрабатывает экземпляры ArgumentOutOfRangeException, перенаправляя браузер пользователя к файлу RangeErrorPage.html из папки

Обратите внимание, что мы наследовали класс RangeExceptionAttribute от класса FilterAttribute наряду с реализацией интерфейса IExceptionFilter. Чтобы класс атрибута .NET работал как фильтр MVC, класс должен реализовать интерфейс IMvcFilter. Это можно сделать напрямую, но самый простой способ создать фильтр – это наследовать от класса FilterAttribute, который реализует необходимый интерфейс и предоставляет некоторые полезные базовые функции, такие как изменение стандартного порядка выполнения фильтров (к чему мы вернемся позже в этой главе).

Применяем фильтр исключений

Прежде чем мы сможем протестировать наш фильтр исключений, нужно создать для этого некоторую базу. Во-первых, создадим в проекте папку Content и в ней - файл RangeErrorPage.html. Он будет использоваться для отображения простого сообщения, которые вы можете видеть в листинге 16-12.

Листинг 16-12: Содержимое файла RangeErrorPage.html

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml"> <head>

<title>Range Error</title> </head>

<body>

<h2>Sorry</h2>

<span>One of the arguments was out of the expected range.</span> </body>

</html>

Далее нам нужно добавить метод действия в контроллер Home, который будет выбрасывать интересующее нас исключение. Он показан в листинге 16-13.

Листинг 16-13: Добавляем новое действие в контроллер Home

using System;

using System.Collections.Generic; using System.Linq;

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

using Filters.Infrastructure; namespace Filters.Controllers

{

public class HomeController : Controller

{

[Authorize(Users = "adam, steve, jacqui", Roles = "admin")] public string Index()

{

return "This is the Index action on the Home controller";

}

public string RangeTest(int id)

{

if (id > 100)

{

return String.Format("The id value is: {0}", id);

}

else

{

throw new ArgumentOutOfRangeException("id", id, "");

}

}

}

}

397

Если вы запустите приложение и перейдете по ссылке /Home/RangeTest/50, то увидите стандартную обработку исключения. В маршрутизации по умолчанию, которую для проекта создает Visual Studio, есть переменная сегмента под названием id, которой в этом URL мы установили значение 50, что и приведет к результату, показанному на рисунке 16-3. (Подробно маршрутизация и сегменты URL описаны в главах 13 и 14.)

Рисунок 16-3: Ответ стандартной обработки исключений

Мы можем применить фильтр исключений либо к контроллерам, либо к отдельным действиям, как показано в листинге 16-14.

Листинг 16-14: Применяем фильтр

[RangeException]

public string RangeTest(int id)

{

if (id > 100)

{

return String.Format("The id value is: {0}", id);

}

else

{

throw new ArgumentOutOfRangeException("id");

}

}

Если вы перезапустите приложение и снова перейдете по ссылке Home/RangeTest/50, то увидите результат, показанный на рисунке 16-4.

398

Рисунок 16-4: Эффект применения фильтра исключений

Используем представление для вывода ответа при выбросе исключения

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

Альтернативный подход - использовать представление для отображения конкретного сообщения о проблеме и предоставления пользователю некоторой контекстной информации и возможностей, с помощью которых можно данную проблему решить. Чтобы это продемонстрировать, мы внесли некоторые изменения в класс RangeExceptionAttribute, как показано в листинге 16-15.

Листинг 16-15: Возвращаем представление из фильтра исключений

using System;

using System.Web.Mvc;

namespace Filters.Infrastructure

{

public class RangeExceptionAttribute : FilterAttribute, IExceptionFilter

{

public void OnException(ExceptionContext filterContext)

{

if (!filterContext.ExceptionHandled && filterContext.Exception is ArgumentOutOfRangeException)

{

int val = (int)(((ArgumentOutOfRangeException) filterContext.Exception).ActualValue);

filterContext.Result = new ViewResult

{

ViewName = "RangeError",

ViewData = new ViewDataDictionary<int>(val) };

filterContext.ExceptionHandled = true;

}

}

}

}

Мы создали объект ViewResult и установили значения его свойств ViewName и ViewData, определяющие название представления и объект модели, который будет в него передан. Это немного запутанный код, потому что мы работаем с объектом ViewResult напрямую и не используем определенный в классе Controller метод View, который всегда применяем в методах действий. Мы не станем останавливаться на этом коде, потому что представления будут подробно рассмотрены в

399

главе 18, и встроенный фильтр исключений, который мы опишем в следующем разделе, позволит достичь того же эффекта более понятным способом. Сейчас мы просто хотим вам показать, как все работает «под капотом».

В объекте ViewResult мы указываем представление под названием RangeError и передаем значение int вызвавшего исключение аргумента в качестве объекта модели представления. Затем мы добавим папку Views/Shared в проект Visual Studio и создадим в нем файл RangeError.cshtml, содержимое которого показано в листинге 16-16.

Листинг 16-16: Файл представления RangeError.cshtml

@model int <!DOCTYPE html> <html>

<head>

<meta name="viewport" content="width=device-width" /> <title>Range Error</title>

</head>

<body>

<h2>Sorry</h2>

<span>The value @Model was out of the expected range.</span> <div>

@Html.ActionLink("Change value and try again", "Index") </div>

</body>

</html>

В файле представления мы используем стандартные теги HTML и Razor, чтобы предоставить пользователю (немного) более полезное сообщение. Наш пример приложения довольно ограничен, поэтому у нас нет каких-либо полезных страниц, на которые можно направить пользователя для решения этой проблемы. Мы создали ссылки на другой метод действия с помощью вспомогательного метода ActionLink, просто чтобы показать, что в представлении вы можете использовать какие угодно функции. Чтобы увидеть результат, перезапустите приложение и перейдите по ссылке /Home/RangeTest/50, как показано на рисунке 16-5.

Рисунок 16-5: Используем представление для отображения сообщения от фильтра исключения

400

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]