Проверки авторизации в коде

191

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

Объекты IPrincipal предоставляют метод IsInRole(), который позволяет проверить принадлежность пользователя к группе. Этот метод принимает имя роли в виде строки и возвращает true, если пользователь является членом этой роли.

Например, вот как проверить, что текущий пользователь является членом роли Supervisors:

if (User.IsInRole("Supervisors"))
{
	// Ничего не делать; страница доступна как обычно, поскольку
	// пользователь имеет привилегии администратора
}
else
{
	// Не разрешать доступ к этой странице
	// Взамен переадресовать на домашнюю страницу
	Response.Redirect("default.aspx");
}

В .NET предусмотрен другой способ применения правил ролей и пользователей. Вместо проверки методом IsInRole() можно использовать класс PrincipalPermission из пространства имен System.Security.Permissions.

Базовая стратегия заключается в создании объекта PrincipalPermission, который предоставляет необходимую информацию о пользователе или роли. Затем вызывается метод Demand(). Если текущий пользователь не отвечает требованиям, генерируется исключение SecurityException, которое можно перехватить (или обработать, используя настраиваемую страницу ошибки).

Существуют четыре перегрузки конструктора PrincipalPermission, получающие от одного до трех параметров, которые, в свою очередь, обрабатываются методом Demand() класса. Один параметр представляет имя пользователя, другой - имя роли, а третий - флаг, который запрашивает метод PrincipalPermission.Demand() проверить, аутентифицирован пользователь или нет (isAuthenticated). Последняя, четвертая, перегрузка принимает единственный параметр PermissionState. Этот параметр унаследован от базового класса для класса PrincipalPermission. Любой из этих параметров может быть опущен, передачей вместо него null-ссылки. Например, следующий код проверяет, является ли пользователь администратором:

try 
{
    PrincipalPermission pp = new PrincipalPermission (null, "Administrators");
    pp.Demand();
    
    // Если управление достигло этой точки, требование удовлетворено.
    // Текущий пользователь является администратором
}
catch (SecurityException)
{
    // Требование не выполнено. Текущий пользователь не является администратором
}

Преимущество этого подхода в том, что он не требует написания какой-либо условной логики. Вместо этого просто запрашиваются все привилегии, которые необходимы. В частности, это хорошо работает, когда нужно проверить, является ли пользователь членом нескольких групп. Недостаток состоит в том, что применение обработки исключений для управления потоком приложения происходит медленно. Часто проверки PrincipalPermission используются в дополнение к правилам web.config для дополнительной отказоустойчивости. Другими словами, Demand() можно вызывать для гарантии того, что даже если файл web.config непреднамеренно модифицирован, пользователи из "неправильных" групп допущены не будут.

Подход с PrincipalPermission также предоставляет возможность оценивать более сложные правила аутентификации. Например, рассмотрим ситуацию, когда пользователям UserA и UserB, принадлежащим к разным группам, открыт доступ к определенной функциональности. Если применяется объект IPrincipal, то придется вызывать IsInRole() дважды. Альтернативный подход заключается в создании нескольких объектов PrincipalPermission и слиянии их в один объект PrincipalPermission, с последующим вызовом Demand() только с этим объектом. Вот пример комбинирования двух ролей:

try
{
    PrincipalPermission pp1 = new PrincipalPermission(null, "Administrators");
    PrincipalPermission pp2 = new PrincipalPermission(null, "Guests");

    // Скомбинировать эти два разрешения
    PrincipalPermission pp3 = (PrincipalPermission)pp1.Union(pp2);
    pp3.Demand();

    // Если управление достигло этой точки, требование удовлетворено
    // Текущий пользователь принадлежит к одной из этих ролей
}
catch (SecurityException)
{
    // Требование не удовлетворено.
    // Текущий пользователь не относится ни к одной из ролей
}

В этом примере пользователь проверяется на принадлежность к любой из двух групп - Administrators или Guests. Можно также проверить на принадлежность пользователя к обеим группам. В этом случае вместо метода PrincipalPermission.Union() необходимо применить PrincipalPermission.Intersect().

Атрибут PrincipalPermission предлагает другой способ проверки удостоверения текущего пользователя. Он служит той же цели, что и класс PrincipalPermission, но используется декларативно. Другими словами, вы присоединяете его к данному классу или методу, a CLR проверяет его автоматически, когда запускается соответствующий код. В этом случае обработка исключений работает несколько иначе - на этот раз вы не можете перехватить исключение внутри функции, к которой относится атрибут. Вы должны это сделать в функции, которая вызывает данную. Если атрибут PrincipalPermission применяется к процедуре события (такой как Button_Click), понадобится перехватить исключение в глобальном событии Application_Error, которое можно найти в файле global.asax.

При использовании атрибута PrincipalPermission можно ограничить доступ для специфического пользователя или роли. Рассмотрим пример, который требует, чтобы пользователь, обращающийся к странице, принадлежал к группе Administrators сервера. Если пользователь не является членом группы Administrators на веб-сервере, то исполняющая среда ASP.NET генерирует исключение, связанное с безопасностью:

[PrincipalPermission(SecurityAction.Demand, Role="Administrators")]
public partial class Restricted_SecurePage : System.Web.UI.Page
{
    // ...
}

В этом примере должно перехватываться исключение в глобальном обработчике (Application_Error), поскольку ваш код не является вызывающим по отношению к веб-странице. В противном случае ASP.NET инициирует исключение и отобразит страницу ошибки ASP.NET в соответствии с конфигурацией в web.config. В следующем примере конкретный метод разрешается вызывать только пользователю с определенной ролью:

[PrincipalPermission(SecurityAction.Demand, Role="Administrators")]
private void DoSomething()
{
	// ...
}

В вызывающем этот метод коде, исключение SecurityException можно перехватить в блоке try/catch.

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

Изменение декларативных прав доступа означает необходимость в перекомпиляции приложения. Но зачем их использовать, если каждое изменение требует перекомпиляции? Разве вы не хотите иметь возможность управлять ролями, просто добавляя, удаляя или изменяя их? Да, и это требует более обобщенного кода, но его нельзя обеспечить с помощью декларативных привилегий.

Итак, когда же полезно применять декларативные привилегии? Декларативные привилегии особенно подходят для фиксированных ролей в приложениях, которые не могут быть удалены ни при каких обстоятельствах. Например, роль Administrators необходима большинству приложений и потому не может быть удалена. Поэтому декларативными привилегиями можно защитить функциональность, которая должна быть доступна только администраторам.

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