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

33

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

Идентификационные данные

Идентифицировать пользователя, запускающего приложение, можно за счет применения идентификационных данных (identity). Класс WindowsIdentity позволяет представлять пользователя Windows. Помимо учетной записи Windows для идентификации пользователя можно также использовать другие классы, реализующие интерфейс IIdentity. Этот интерфейс позволяет получать доступ к имени пользователя, а также к информации о том, прошел ли данный пользователь аутентификацию, и о применяемом типе аутентификации.

Принципалом (principal) называется объект, в котором содержатся идентификационные данные пользователя и роли, к которым он принадлежит. Интерфейс IPrincipal имеет свойство Identity, которое возвращает объект Ildentity, и метод IsInRole, с помощью которого можно проверить, действительно ли пользователь является членом конкретной роли.

Роль (role) представляет собой коллекцию пользователей, обладающих одинаковыми правами доступа, и служит единицей для администрирования пользователей. В качестве ролей могут выступать как группы Windows, так и определяемые самим разработчиком наборы строк.

В .NET доступны следующие классы принципалов: WindowsPrincipal и GenericPrincipal. Однако помимо них также можно создавать собственные специальные классы принципалов, реализующие интерфейс IPrincipal.

В следующем примере демонстрируется создание консольного приложения, предоставляющего доступ к принципалу, которое, в свою очередь, позволяет получать доступ к лежащей в основе учетной записи Windows.

Сначала понадобится импортировать пространства имен System.Security.Principal и System.Threading. Далее нужно указать, что .NET должна автоматически подключать принципал к соответствующей учетной записи Windows, поскольку из соображений безопасности .NET автоматически не заполняет свойство потока CurrentPrincipal. Сделать это можно следующим образом:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;
using System.Threading;

namespace ConsoleApplication1
{
    class Program
    {

        static void Main(string[] args)
        {
            AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);
        }
    }
}

Для получения доступа к деталям учетной записи Windows можно использовать метод WindowsIdentity.GetCurrent(), однако он больше подходит в ситуации, когда доступ к принципалу требуется получить лишь один раз. Если нужен многократный доступ к принципалу, лучше с помощью метода SetPrincipalPolicy установить политику так, чтобы текущий поток сам предоставлял доступ к принципалу. Этот метод указывает, что принципал в текущем потоке должен хранить объект Windows Identity.

Все предназначенные для идентификации классы, подобные Windows Identity, реализуют интерфейс IIdentity. Этот интерфейс имеет три свойства (AuthenticationType, IsAuthenticated и Name), которые должны быть обязательно реализованы во всех производных идентификационных классах.

Добавьте следующий код для получения доступа к свойствам принципала из объекта Thread:

WindowsPrincipal principial = (WindowsPrincipal)Thread.CurrentPrincipal;
            WindowsIdentity identity = (WindowsIdentity)principial.Identity;

            Console.WriteLine("Тип идентификации: " + identity.ToString());
            Console.WriteLine("Имя: " + identity.Name);
            Console.WriteLine("Пользователи? " + principial.IsInRole(WindowsBuiltInRole.User));
            Console.WriteLine("Администраторы? " + principial.IsInRole(WindowsBuiltInRole.Administrator));
            Console.WriteLine("Аутентифицирован: " + identity.AuthenticationType);
            Console.WriteLine("Анонимный? " + identity.IsAnonymous);
            Console.WriteLine("маркер: " + identity.Token);

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

Результат аутентификации

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

Декларативное обеспечение безопасности на основе ролей

Безопасность на основе ролей (role-based security) особенно полезна в ситуациях, когда получение доступа к ресурсам играет критически важную роль. Главным тому примером может служить сфера финансовой деятельности, где исполняемые сотрудниками роли определяют то, к какой информации они могут получать доступ, и какие действия они могут предпринимать.

Безопасности на основе ролей идеально подходит для применения вместе с учетными записями Windows или специальным каталогом пользователей для управления доступом к веб-ресурсам. Например, на веб-сайте незарегистрированным пользователям может предоставляться лишь ограниченный доступ к его содержимому, а платным подписчикам — напротив, дополнительный доступ к специальному содержимому.

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

Рассмотрим сценарий с приложением внутренней сети, которое полагается на использование учетных записей Windows. Предположим, что в системе имеются группы под названием Managers (Менеджеры) и Assistants (Ассистенты), а пользователи назначаются этим группам в соответствии с их ролями в организации. Кроме того, в приложении имеется функция, позволяющая отображать информацию о сотрудниках, доступ к которой должен быть только у пользователей из группы Managers. Конечно, можно легко написать код, проверяющий, является ли текущий пользователь членом группы Managers, и разрешен ли ему доступ к данной функции.

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

Более подходящим подходом в таком сценарии будет создание разрешения по имени, например, ReadEmployeeDetails, и назначение его группам по мере необходимости. Если в коде будет осуществляться проверка разрешения ReadEmployeeDetails, то для обновления приложения так, чтобы оно позволяло получать доступ к информации о сотрудниках и членам группы Personnel, потребуется просто создать такую группу, поместить в нее желаемых пользователей и назначить ей разрешение ReadEmployeeDetails.

Как и при защите доступа кода, запросы безопасности на основе ролей (например, такие как "пользователь должен обязательно находиться в группе администраторов") можно реализовать либо императивно за счет вызова метода IsInRole() из класса IPrincipal, либо с помощью атрибутов. Требования относительно полномочий можно задавать декларативно на уровне класса или метода с помощью атрибута [PrincipalPermission].

using System;
using System.Security;
using System.Security.Principal;
using System.Security.Permissions;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);
            try
            {
                ShowMessage();
            }
            catch (SecurityException exception)
            {
                Console.WriteLine("Исключение " + exception.Message);
            }
            finally
            {
                Console.ReadLine();
            }
        }
         
         [PrincipalPermission(SecurityAction.Demand, Role="BUILTIN\\Users")]
         static void ShowMessage()
         {
             Console.WriteLine("Текущий принципиал зарегистрировался локально и является членом группы Users");
         }
        
    }
}

При выполнении этого приложения в контексте пользователя, не являющегося членом локальной группы Windows под названием Users (Пользователи), метод ShowMessage() будет генерировать исключение.

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

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