Этот цикл из трех статей представляет собой введение в использование системы безопасности Acegi (Acegi Security System) для обеспечения безопасности корпоративных Java-приложений. В первой статье этого цикла мы познакомились с Acegi и разобрали, как использовать фильтры безопасности для реализации простой системы безопасности, основанной на URL. В этой второй статье мы начнем обсуждение более продвинутых вариантов использования Acegi, начиная с написания политики управления доступом и сохранения ее в ApacheDS - сервере каталогов LDAP с открытым кодом. Также будет показано, как настроить взаимодействие Acegi с сервером каталогов для реализации политики управления доступом. В заключение этой статьи будет представлен пример приложения, использующего ApacheDS и Acegi для реализации безопасной политики управления доступом.
Реализация политики управления доступом обычно состоит из двух шагов:
- Сохранение информации о пользователях и их ролях на сервере каталогов.
- Написание кода для обеспечения безопасности, определяющего, кто может получить доступ к данным и использовать их.
Acegi освобождает от написания кода, так что в этой статье будет сначала показано, как сохранить информацию о пользователях и их ролях в ApacheDS, а затем как реализовать политику управления доступом на основе этой информации. В последней статье этого цикла будет рассказано, как настроить Acegi для обеспечения безопасного доступа к Java-классам.
Скачать пример приложения можно в любой момент изучения статьи. В разделе Ресурсы приведены ссылки для скачивания Acegi, Tomcat и ApacheDS, которые потребуются для запуска примеров кода и примера приложения.
Lightweight Directory Access Protocol (LDAP – упрощенный протокол для доступа к каталогам) - возможно, самый популярный протокол, определяющий форматы данных для стандартных операций над каталогом, таких как, чтение, редактирование, поиск и удаление информации, хранящейся в сервере каталогов. В этом разделе кратко объясняется, почему сервер каталогов лучше подходит для хранения информации о безопасности, чем файл свойств (properties file), и показывается, как организовать и разместить информацию о пользователях в каталоге LDAP.
Почему именно сервер каталогов?
В первой статье этого цикла был рассмотрен простой способ хранения информации о пользователе в виде формы в файле свойств (см. Часть 1. Листинг 6). В файле свойств имена пользователей, пароли и роли пользователей хранятся в текстовом формате. Для большинства реальных приложений – это неподходящий способ хранения информации о безопасности. По множеству причин сервер каталогов – это куда лучший выбор. Одна из причин в том, что реальные приложения могут использоваться большим числом пользователей, даже тысячами пользователей, особенно если приложение открывает часть своей функциональности клиентам и поставщикам. Часто выполнять поиск среди неорганизованной информации, хранящейся в текстовом файле, - это не очень эффективное решение, а сервер каталогов оптимизирован для подобного поиска.
Другую причину иллюстрирует файл свойств (см. Часть 1, Листинг 6), в котором одновременно хранится информация и о пользователях, и об их ролях. В реальном приложении для управления доступом обычно предпочитают определять и хранить информацию о пользователях и ролях по отдельности для облегчения поддержки базы пользователей. Сервер каталогов предоставляет почти неограниченную гибкость обновления и изменения информации о пользователях, например, для отслеживания повышений по службе или приема новых сотрудников. В разделе Ресурсы приведены ссылки на дополнительную информацию о преимуществах и использовании серверов каталогов.
Если необходимо хранить информацию в каталоге LDAP, то придется изучить несколько аспектов настройки каталога. В этой статье не приводится полное введение в администрирование LDAP, подобные статьи можно найти в разделе Ресурсы. Вместо этого мы представим только базовые принципы, которые нужно узнать, прежде чем приступить к использованию Acegi с каталогами LDAP.
Каталог LDAP хранит информацию в форме дерева элементов, как показано на рисунке 1:
Рисунок 1. Древовидная структура каталога LDAP
На рисунке 1 корневым элементом является элемент org. Корневой элемент может содержать данные, относящиеся к разным предприятиям. Например, производственное предприятие, которым мы занимались в первой статье цикла, показано как прямой элемент-потомок корневого элемента org. У производственного предприятия есть два элемента-потомка, названные departments (отделы) и partners (партнеры).
Элемент-потомок partners содержит партнеров различных типов. Три из них, показанные на рисунке 1, - это customers (клиенты), employees (сотрудники) и suppliers (поставщики). Отметим, что каждый из этих трех типов партнеров может выступать в качестве пользователя корпоративной системы. У каждого типа пользователей есть собственная роль в бизнесе и, следовательно, различные права на доступ в систему.
Точно так же элемент departments содержит различные отделы производственного предприятия, например, дочерние элементы engineering (инженерный) и marketing (маркетинговый). Каждый элемент-отдел содержит одну или несколько групп пользователей. Как показано на рисунке 1, группа engineers (инженеры) - это дочерний элемент отдела engineering.
Таким образом, элементы-потомки каждого отдела представляют собой группу пользователей. Эти элементы-потомки содержат различных пользователей (членов этой группы). Например, все инженеры, работающие в инженерном отделе, входят в группу engineers в отделе engineering.
Также можно отметить последний элемент-потомок элемента departments на рисунке 1. Элемент specialUser представляет собой пользователя, а не группу. При такой конфигурации каталога пользователи, такие как alice и bob, обычно содержатся в элементе partners. Я добавил этого специального пользователя в элемент departments, чтобы продемонстрировать гибкость Acegi, позволяющую пользователям находиться в любом месте каталога LDAP. Далее в этой статье будет показано, как настроить Acegi для использования specialUser.
Для идентификации конкретных элементов в дереве LDAP в протоколе LDAP используется концепция уникального имени (distinguished name - DN). У каждого элемента есть уникальное DN, содержащее полную информацию о его иерархии. Например, на рисунке 2 показаны DN для некоторых элементов, представленных на рисунке 1:
Рисунок 2. Уникальные имена элементов в каталоге LDAP
Сначала отметим DN корневого элемента на рисунке 2. Его уникальное имя - dc=org, это пара атрибут-значение, связанная с корневым элементом org. Каждый элемент имеет несколько атрибутов, связанных с ним. Атрибут dc определяет свойство "доменный компонент" (domain component) и определен в спецификации LDAP RFC 2256 (в разделе Ресурсы приведены ссылки на официальную документацию RFC). Корневой элемент в каталоге LDAP обычно представляется как доменный компонент.
Каждый LDAP-атрибут определяется в некотором RFC. LDAP позволяет использовать для создания DN множество атрибутов, но в примерах в этой статье используются только следующие четыре атрибута:
-
dc(domain component – доменный компонент) -
o(organization - организация) -
ou(organizational unit – подразделение организации) -
uid(user ID – идентификатор пользователя)
В примерах атрибут dc используется для задания домена, o для задания имени организации, ou для указания различных отделов организации и uid для пользователей.
Так как org – это корневой элемент, то в его DN нужно указать только его собственное имя (dc=org). Для сравнения, DN элемента manufacturingEnterprise имеет значение o=manufacturingEnterprise,dc=org. По мере продвижения вниз по элементам дерева DN каждого родительского элемента включается в DN его элементов-потомков.
LDAP группирует связанные типы атрибутов в виде классов объектов. Например, класс объектов organizationalPerson содержит все атрибуты, определяющие сотрудника организации: обращение, имя, почтовый адрес и т.д.
Классы объектов используют наследование, что позволяет LDAP определять базовые классы для хранения общих атрибутов. Классы-потомки затем наследуют базовым классам и используют атрибуты, объявленные в них. Отдельный элемент в каталоге LDAP может использовать несколько классов объектов. Примеры в этой статье используют следующие классы объектов:
- Класс объектов top – это базовый класс для всех классов объектов в LDAP.
- Класс объектов domain используется, когда другие классы объектов не подходят. В нем определен набор атрибутов, любой из которых может использоваться для определения объекта. Атрибут
dcв этом классе является обязательным.
- Класс объектов organization представляет элементы-организации, такие как элемент
manufacturingEnterpriseна рисунке 2.
- Класс объектов organizationalUnit представляет подразделения организации, такие как элемент
departmentsи его потомки на рисунок 1.
- Класс объектов groupOfNames представляет группу имен, например имен людей, работающих в департаменте. У него есть атрибут
member, содержащий список пользователей. Все групповые элементы на рисунке 1, такие как элементengineers, используют атрибутmemberдля определения членов группы. Более того, в примерах используется атрибутou(organizational unit) класса объектовgroupOfNamesдля определения бизнес-задачи группы.
- Класс объектов organizationalPerson представляет сотрудника организации, такого как элемент
aliceна рисунке 1.
В реальных приложениях в каталоге LDAP обычно хранится огромное количество информации о пользователях системы. Например, можно хранить имя пользователя, пароль, название должности, контактную и бухгалтерскую информацию для каждого пользователя. Для простоты в следующих примерах показано только как хранить имя пользователя и пароль.
Как упоминалось ранее, чтобы продемонстрировать, как Acegi работает с каталогами LDAP, в примерах используется ApacheDS – сервер каталогов LDAP с открытым кодом. LDAP-клиент с открытым кодом – Jxplorer – используется для выполнения простых действий над каталогом, например, для сохранения информации в ApacheDS. В разделе Ресурсы приведена информация о том, как скачать ApacheDS и JXplorer и настроить их для совместной работы.
Создание корневого элемента в ApacheDS
Чтобы создать дерево элементов, показанное на рисунке 1, сначала необходимо создать корневой элемент org в ApacheDS. Для этой цели ApacheDS предоставляет конфигурационный XML-файл. В этом файле определен набор bean-компонентов, которые можно настроить для реализации в сервере каталогов поведения, отвечающего требованиям приложения. Ниже приведена конфигурация для создания корневого элемента.
Конфигурационный файл server.xml можно найти в каталоге conf в каталоге, куда был установлен ApacheDS. В этом файле находится набор конфигураций bean-компонентов, сходных с конфигурациями фильтров Acegi. Нас интересует bean-компонент examplePartitionsConfiguration. Этот компонент управляет разделами в ApacheDS. При создании нового корневого элемента фактически создается новый раздел в каталоге LDAP.
Внесем изменения в компонент examplePartitionConfiguration, чтобы создать корневой элемент org, как показано в листинге 1:
Листинг 1. Изменения в конфигурации bean-компонента examplePartitionConfiguration
<bean id="examplePartitionConfiguration" class=
"org.apache.directory.server.core.partition.impl.btree.MutableBTreePartitionConfiguration"
>
<property name="suffix"><value>dc=org</value></property>
<property name="contextEntry">
<value>
objectClass: top
objectClass: domain
dc: org
</value>
</property>
<!-- другие параметры компонента examplePartitionConfiguration,
которые редактировать не нужно. -->
</bean>
|
В листинге 1 изменяются два свойства компонента examplePartitionConfiguration:
- свойство
suffix, определяющее DN корневого элемента,
- свойство
contextEntry, определяющее класс объектов, который будет использоваться корневым элементомorg. Корневой элементorgиспользует два типа классов объектов:topиdomain.
The Исходный код этой статьи содержит отредактированную версию файла server.xml. Чтобы следить за примером, скопируйте файл server.xml из исходного кода туда, где он должен находиться – в каталог conf в установочном каталоге ApacheDS.
На рисунке 3 показан снимок с экрана, на котором JXplorer отображает корневой элемент после того, как он был создан в ApacheDS:
Рисунок 3. Корневой элемент в JXplorer
Следующий этап в настройке LDAP сервера – это заполнение его информацией о пользователях и группах. Можно воспользоваться JXplorer для последовательного создания элементов в ApacheDS, но гораздо проще заполнить сервер с помощью формата для обмена данными в LDAP (LDAP Data Interchange Format - LDIF). LDIF – это известный формат, используемый во многих реализациях LDAP. Подготовка LDIF-файлов была подробно освещена в статьях developerWorks, так что эту тему мы разбирать не будем. В разделе Ресурсы приведены ссылки на дополнительную информацию по LDIF.
Вместо этого в доступном для загрузки исходном коде этой статьи можно посмотреть LDIF-файл, который представляет пользователей и отделы, показанные на рисунке 1. Этот LDIF-файл можно импортировать в ApacheDS с помощью JXplorer. Для импорта LDIF-файла необходимо использовать меню LDIF в JXplorer, как показано на рисунке 4:
Рисунок 4. Импорт LDIF-файла в ApacheDS
После того как LDIF-файл будет импортирован в ApacheDS, JXplorer будет показывать дерево элементов пользователей и отделов, как показано на рисунке 1. Теперь можно переходить к настройке Acegi для взаимодействия с LDAP-сервером.
Настройка Acegi для реализации LDAP
Из первой части известно, что Acegi использует для аутентификации APF-фильтр (Authentication Processing Filter –фильтр для выполнения аутентификации). APF-фильтр выполняет все серверные вычислительные задачи, такие как извлечение имени пользователя и пароля из запроса клиента, считывание параметров пользователя из серверного хранилища пользователей и использование этой информации для аутентификации пользователя.
Мы уже настраивали APF-фильтр в первой части, для реализации на основе файла свойств. Теперь база пользователей будет храниться в каталоге LDAP, так что потребуется настроить фильтр по-другому, для взаимодействия с LDAP-каталогом. Начнем с листинга 2, где показано, как был настроен APF-фильтр для реализации на основе файла свойств (properties) в разделе Фильтр для выполнения аутентификации в прошлой статье:
Листинг 2. Настройка APF-фильтра на использование файла properties
<bean id="authenticationProcessingFilter"
class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
<property name="authenticationManager" ref="authenticationManager" />
<property name="authenticationFailureUrl"
value="/login.jsp?login_error=1" />
<property name="defaultTargetUrl"
value="/index.jsp" />
<property name="filterProcessesUrl"
value="/j_acegi_security_check" />
</bean>
|
Если посмотреть на листинг 2, то видно, что в APF-фильтр передаются четыре параметра. Для хранения в LDAP–сервере необходимо изменить только первый параметр (менеджер аутентификации - authenticationManager). Три других параметра остаются без изменений.
Настройка менеджера аутентификации
В листинге 3 показано, как настроить менеджер аутентификации Acegi для взаимодействия с LDAP-сервером:
Листинг 3. Настройка менеджера аутентификации Acegi для взаимодействия с LDAP
<bean id="authenticationManager"
class="org.acegisecurity.providers.ProviderManager">
<property name="providers">
<list>
<ref local="ldapAuthenticationProvider" />
</list>
</property>
</bean>
|
В листинге 3 процессом аутентификации в Acegi управляет класс менеджера org.acegisecurity.providers.ProviderManager. Для выполнения своей работы менеджеру аутентификации требуются один или несколько провайдеров аутентификации. Для настройки одного или нескольких провайдеров можно воспользоваться параметром providers bean-компонента менеджера. В листинге 3 показан только один провайдер – провайдер для LDAP-аутентификации.
Провайдер для LDAP-аутентификации берет на себя все взаимодействие с используемым каталогом LDAP. Его также необходимо настроить, как будет показано ниже.
Настройка провайдера для LDAP-аутентификации
В листинге 4 приведена конфигурация провайдера для LDAP-аутентификации:
Листинг 4. Настройка провайдера для LDAP-аутентификации
<bean id="ldapAuthenticationProvider"
class="org.acegisecurity.providers.ldap.LdapAuthenticationProvider">
<constructor-arg><ref local="authenticator"/></constructor-arg>
<constructor-arg><ref local="populator"/></constructor-arg>
</bean>
|
Отметим, что название класса провайдера для LDAP-аутентификации - org.acegisecurity.providers.ldap.LdapAuthenticationProvider. В его конструктор передаются два параметра в виде двух тегов <constructor-arg>, как показано в листинге 4.
Первый параметра конструктора класса LdapAuthenticationProvider - это authenticator (аутентификатор), который аутентифицирует пользователя в LDAP-каталоге путем проверки имени и пароля пользователя. После того как пользователь был аутентифицирован, второй параметр populator (обработчик) извлекает информацию о правах доступа пользователя или его бизнес-ролях из LDAP-каталога.
В следующих разделах будет показано, как настроить bean-компоненты authenticator и populator.
Настройка bean-компонента authenticator
Bean-компонент authenticator проверяет наличие пользователя с указанным именам и паролем в LDAP-каталоге. Acegi предоставляет для этого компонента класс org.acegisecurity.providers.ldap.authenticator.BindAuthenticator, который выполняет требуемую функцию проверки имени и пароля пользователя.
Конфигурация для bean-компонента authenticator показана в листинге 5:
Листинг 5. Конфигурация bean-компонента authenticator
<bean id="authenticator"
class="org.acegisecurity.providers.ldap.authenticator.BindAuthenticator">
<constructor-arg><ref local="initialDirContextFactory"/></constructor-arg>
<property name="userDnPatterns">
<list>
<value>uid={0},ou=employees,ou=partners</value>
<value>uid={0},ou=customers,ou=partners</value>
<value>uid={0},ou=suppliers,ou=partners</value>
</list>
</property>
<property name="userSearch"><ref local="userSearch"/></property>
</bean>
|
В листинге 5 конструктор класса BindAuthenticator принимает один параметр в виде тега <constructor-arg>. Название этого параметра в листинге 5 - initialDirContextFactory. Этот параметр на самом деле – другой bean-компонент, конфигурация которого будет обсуждаться позже.
Сейчас достаточно просто знать, что назначение bean-компонента initialDirContextFactory – определить исходный контекст для дальнейших операций поиска. Исходный контекст – это DN, задающее определенный элемент в каталоге LDAP. После задания исходного контекста все дальнейшие операции по поиску, например, поиск конкретного пользователя, выполняются в потомках этого элемента.
Например, вернемся к элементу partners на рисунке 2, DN которого - ou=partners,o=manufacturingEnterprise,dc=org. Если указать элемент partners в качестве исходного контекста, то Acegi будет искать пользователей только внутри потомков элемента partners.
В дополнение к настройке конструктора BindAuthenticator необходимо также настроить два параметра bean-компонента authenticator (два тега <property> в листинге 5).
Первый тег <property> определяет свойство userDnPatterns, содержащее один или несколько DN-шаблонов. DN-шаблон определяет несколько LDAP-элементов, у которых есть что-то общее, например, группу всех потомков элемента employees на рисунке 2.
Bean-компонент authenticator Acegi собирает один DN из каждого DN-шаблона, настроенного в свойстве userDnPatterns этого bean-компонента. Например, первый DN-шаблон, настроенный в листинге 5, - uid={0},ou=employees,ou=partners. Bean-компонент authenticator заменяет {0} на имя, указанное пользователем во время аутентификации, например, alice. После замены {0} на имя пользователя DN-шаблон превращается в относительное DN (relative DN - RDN), uid=alice,ou=employees,ou=partners, которому требуется исходный контекст, чтобы стать полноценным DN.
Например, рассмотрим элемент alice на рисунке 2. Этот элемент является первым потомком элемента employees. Его DN: uid=alice,ou=employees,ou=partners,o=manufacturingEnterprise, dc=org. Если использовать строку o=manufacturingEnterprise,dc=org как исходный контекст и применить её после относительного DN uid=alice,ou=employees,ou=partners, то мы получим полный DN для alice.
После создания подобным способом DN пользователя из шаблона DN bean-компонент authenticator отправляет DN и пароль пользователя в LDAP-каталог. Каталог проверяет, что существует подобный DN с указанным паролем. Если проверка проходит успешно, то пользователь аутентифицируется. Этот процесс в терминологии LDAP называется присваивающей аутентификацией. LDAP предлагает и другие механизмы для аутентификации, но в примерах будет использоваться только присваивающая аутентификация.
Если DN, созданное с помощью первого DN-шаблона, не обнаруживается в каталоге, то bean-компонент authenticator пробует следующий DN-шаблон, настроенный в списке. Таким способом bean-компонент authenticator пробует все DN-шаблоны для создания правильного DN пользователя, которого необходимо аутентифицировать.
В предыдущем разделе "Настройка каталога LDAP" упоминалось, что я разрешил определенную гибкость хранения информации о пользователях в каталоге LDAP. Для этого был создан специальной пользователь specialUser в элементе departments, как было показано на рисунке 1.
Если попробовать создать DN для специального пользователя, используя все DN-шаблоны, сконфигурированные в листинге 5, то выяснится, что для этого не годится ни один из шаблонов. В результате, когда этот пользователь попробует войти в систему, bean-компонент authenticator не сможет сконструировать правильное DN, а, следовательно, и аутентифицировать пользователя.
Acegi обрабатывает подобные исключительные случаи, разрешая определять поисковые фильтры. Bean-компонент authenticator с помощью поисковых фильтров находит пользователей, которых не удалось аутентифицировать, создав DN из DN-шаблонов.
Второй тег <property> в листинге 5 имеет вложенный тег <ref>, ссылающийся на bean-компонент userSearch. Этот bean-компонент userSearch определяет поисковый запрос. В листинге 6 показано, как настроить bean-компонент userSearch для обработки специальных пользователей:
Листинг 6. Настройка поискового запроса для поиска специальных пользователей
<bean id="userSearch"
class="org.acegisecurity.ldap.search.FilterBasedLdapUserSearch">
<constructor-arg>
<value>ou=departments</value>
</constructor-arg>
<constructor-arg>
<value>(uid={0})</value>
</constructor-arg>
<constructor-arg>
<ref local="initialDirContextFactory" />
</constructor-arg>
<property name="searchSubtree">
<value>true</value>
</property>
</bean>
|
Параметры поискового запроса
В листинге 6 показано, что bean-компонент userSearch – это объект класса org.acegisecurity.ldap.search.FilterBasedLdapUserSearch, конструктор которого принимает три параметра. Первый параметр определяет элемент, где bean-компонент authenticator выполняет поиск пользователей. Значение первого параметра - ou=departments, это относительное DN, определяющее элемент departments, показанный на рисунке 2.
Второй параметр (uid={0}) определяет поисковый фильтр. Так как мы используем для определения пользователей атрибут uid, то можно найти пользователя, ища элемент, у которого атрибут uid имеет определенное значение. Как можно предположить, ноль в фигурных скобках просто говорит Acegi, что необходимо заменить {0} на имя аутентифицируемого пользователя, в данном случае specialUser.
Третий параметр – это ссылка на исходный контекст, которая уже обсуждалась при разборе конструктора класса BindAuthenticator в листинге 5. С учетом этого, после задания исходного контекста, все дальнейшие операции поиска происходят внутри потомков элемента, на который указывает исходный контекст. Обратите внимание, что относительное DN, указанное в виде значения первого параметра в листинге 6 (ou=departments), помещается перед исходным контекстом.
В дополнение к этим трем параметрам конструктора bean-компонент userSearch из листинге 6 также использует параметр searchSubtree. Если значение этого параметра установлено в true, то операция поиска проводится по поддереву (все потомки, потомки потомков и т.д.) элемента, который был указан в качестве значения первого параметра конструктора.
На этом конфигурация bean-компонента authenticator завершена. В следующем разделе разбирается конфигурация bean-компонента populator, также показанного в листинге 4.
Настройка bean-компонента populator
Bean-компонент populator считывает бизнес-роли пользователя, уже аутентифицированного bean-компонентом authenticator. В листинге 7 приведена XML конфигурация для bean-компонента populator:
Листинг 7. XML конфигурация bean-компонента populator
<bean id="populator"
class="org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator">
<constructor-arg>
<ref local="initialDirContextFactory"/>
</constructor-arg>
<constructor-arg>
<value>ou=departments</value>
</constructor-arg>
<property name="groupRoleAttribute">
<value>ou</value>
</property>
<property name="searchSubtree">
<value>true</value>
</property>
</bean>
|
В листинге 7 конструктор bean-компонента populator принимает два аргумента и параметр groupRoleAttribute. Первый параметр конструктора определяет исходный контекст, используемый bean-компонентом для считывания бизнес-ролей аутентифицированного пользователя. Не обязательно использовать один и тот исходный контекст для обоих bean- компонентов - authenticator и populator. Можно настроить отдельные исходные контексты для каждого компонента.
Второй параметр конструктора определяет относительное DN, которое bean-компонент populator добавляет перед исходным контекстом. Таким способом относительное DN формирует DN элемента, содержащего группу пользователей, например, элемента departments.
Параметр groupRoleAttribute bean-компонента populator определяет атрибут, хранящий информацию о бизнес-ролях участников группы. Если вернуться к разделу Настройка LDAP-каталога, то информация о бизнес-ролях каждой группы хранится в атрибуте ou. Поэтому ou указано как значение параметра groupRoleAttribute, как показано в листинге 7.
Как можно предположить, bean-компонент populator ищет в LDAP-каталоге элементы групп, к которым принадлежит аутентифицированный пользователь. Затем он считывает значения, связанные с атрибутом ou элементов групп, чтобы узнать бизнес-роли аутентифицированного пользователя.
На этом конфигурация bean-компонента populator завершена. На данный момент исходный контекст уже используется в трех местах: в листинге 5, листинге 6 и листинге 7. Так что пришло время настроить исходный контекст.
Конфигурирование исходного контекста
В листинге 8 показано, как задать исходный контекст в Acegi:
Листинг 8. XML-конфигурация исходного контекста
<bean id="initialDirContextFactory"
class="org.acegisecurity.ldap.DefaultInitialDirContextFactory">
<constructor-arg value="ldap://localhost:389/o=manufacturingEnterprise,dc=org"/>
<property name="managerDn">
<value>cn=manager,o=manufacturingEnterprise,dc=org</value>
</property>
<property name="managerPassword">
<value>secret</value>
</property>
</bean>
|
Название класса исходного контекста в Acegi в листинге 8: org.acegisecurity.ldap.DefaultInitialDirContextFactory. Это класс-фабрика, входящий в состав Acegi. Acegi внутри себя использует этот класс для создания объектов других классов, которые выполняют операции в каталоге, такие как поиск. При настройке класса-фабрики для исходного контекста необходимо указать следующие параметры:
- Сетевой адрес LDAP-каталога и корневой элемент каталога в качестве аргумента конструктора. Элемент, настроенный в исходном контексте, будет использоваться как корневой элемент. Это значит, что все последующие операции, такие как поиск (
search), будут выполняться в поддереве, определенном корневым элементом.
- DN и пароль, заданные соответственно, в виде параметров
managerDnиmanagerPassword. Acegi необходимы DN и пароль, чтобы иметь возможность аутентифицироваться на сервере каталогов, прежде чем выполнять любые операции поиска.
Мы уже разобрали, как хранить базу пользователей в каталоге LDAP и как настроить Acegi на использование информации из LDAP для аутентификации пользователей. В следующем разделе детально разбирается фильтр Acegi для выполнения аутентификации (Authentication Processing Filter – APF), чтобы продемонстрировать, как сконфигурированные bean-компоненты управляют процессом аутентификации.
Теперь, когда APF-фильтр настроен, пришло время начать взаимодействовать с каталогом LDAP для аутентификации пользователей. Некоторые действия, выполняемые APF в процессе общения с каталогом, могут показаться знакомыми по первой части, где разбиралось, как фильтр работает с различными службами при аутентификации пользователя. Диаграмма последовательности действий, изображенная на рисунке 5, очень похожа на диаграмму с рисунка 3 в первой части:
Рисунок 5. APF-фильтр выполняет аутентификацию LDAP-пользователя
Шаги с первого по девятый остаются без изменений, вне зависимости от того использует ли APF-фильтр файл properties для внутренней аутентификации или взаимодействует с LDAP-сервером. Первые девять шагов кратко перечисляются в списке, чтобы потом можно было продолжить изучение событий, относящихся непосредственно к LDAP, начиная с десятого шага:
- Предыдущий фильтры в цепочке тестирования передает объект: запрос, ответ и цепочку фильтрации в APF-фильтр.
- APF-фильтр создает объект-аутентификатор с именем пользователя, паролем и другой информацией, извлеченной из объекта запроса.
- APF-фильтр передает объект-аутентификатор менеджеру аутентификации.
- Менеджер аутентификации может содержать одного или несколько провайдеров для выполнения аутентификации. Каждый провайдер поддерживает единственный тип аутентификации. Менеджер проверяет, какой из его провайдеров поддерживает объект-аутентификатор, полученный из APF-фильтра.
- Менеджер аутентификации передает объект-аутентификатор провайдеру, который подходит для данного типа аутентификации.
- Провайдер аутентификации извлекает имя пользователя из объекта-аутентификатора и передает его в службу кэширования пользователей. Acegi поддерживает кэш уже аутентифицированных пользователей. В следующий раз, когда пользователь попробует войти в систему, Acegi сможет загрузить информацию о нем: имя пользователя, пароль и уровни доступа из кэша, вместо того чтобы обращаться к центральному хранилищу пользователей. Такой подход повышает производительность.
- Служба кэширования пользователей проверяет, имеется ли информация о пользователе в кэше.
- The user cache service returns the details of the user to the authentication provider. If the cache does not contain user details, it returns null.
- Провайдер аутентификации проверяет, что было получено из службы кэширования пользователей: информация о пользователе или null.
-
С этого момента выполнение аутентификации связано с использованием LDAP. Если из кэша был получен null, то провайдер для LDAP-аутентификации передает имя пользователя и пароль, извлеченные на шестом шаге, в bean-компонент
authenticator, сконфигурированный в листинге 5.
- Bean-компонент authenticator создает несколько вариантов DN пользователя, используя DN-шаблоны, сконфигурированные через свойство
userDnPatternsв листинге 5. Он пробует все доступные DN-шаблоны один за другим, создавая DN на основе этого DN-шаблона и отправляя его вместе с паролем пользователя, извлеченным из запроса пользователя, в LDAP-каталог. LDAP-каталог проверяет, существует ли такое DN, и правильно ли указан пароль. Если один из DN-шаблонов срабатывает, то пользователь считается привязанным к LDAP-каталогу, иauthenticatorпереходит к шагу 15.
- Если ни один из DN-шаблонов не срабатывает, это значит, что пользователя с таким именем и паролем не существует ни в одном из мест, определенных DN-шаблонами. Тогда bean-компонент
authenticatorвыполняет поиск пользователя в LDAP-каталоге согласно поисковому запросу, сконфигурированному в листинге 6. Если LDAP-каталог не может обнаружить пользователя, то аутентификация считается неудачной
- Если LDAP-каталог находит пользователя, он возвращает DN пользователя обратно в bean-компонент
authenticator.
- Bean-компонент
authenticatorотправляет DN пользователя и пароль в LDAP-каталог, чтобы проверить, что был введен правильный пароль. Если LDAP-каталог находит, что пароль был указан правильно, то пользователь считается привязанным к LDAP-каталогу.
- Bean-компонент
authenticatorотправляет информацию о пользователе обратно в провайдер для LDAP-аутентификации.
- Провайдер для LDAP-аутентификации передает управление bean-компоненту
populator.
- Bean-компонент
populatorищет группы, к которым принадлежит пользователь.
- LDAP-каталог возвращает информацию о ролях пользователя в bean-компонент
populator.
- Bean-компонент
populatorвозвращает информацию о ролях провайдеру для LDAP-аутентификации.
- Провайдер для LDAP-аутентификации возвращает информацию о пользователе вместе с информацией о его бизнес-ролях в APF-фильтр. После этого считается, что пользователь был успешно аутентифицирован.
Последние три шага (21, 22 и23) не зависят от метода аутентификации.
Настройка перехватывающего фильтра
Мы разобрали действия APF-фильтра для авторизации пользователя. Далее необходимо проверить, действительно ли успешно аутентифицированный пользователь авторизован для доступа к запрашиваемому ресурсу. Это задача перехватывающего фильтра Acegi (Interceptor Filter -IF). В этом разделе рассматривается, как настроить IF-фильтр для реализации политики управления доступом.
Вспомним, как мы настраивали IF-фильтр в листинге 7 первой части. Перехватывающий фильтр связывает ресурсы с ролями, гарантируя, что только пользователь, имеющий требуемую роль, может получить доступ к определенному ресурсу. Для демонстрации бизнес-ролей различных отделов нашего предприятия в листинге 9 в существующую конфигурацию IF-фильтра добавляется еще одна роль:
Листинг 9. Конфигурация перехватывающего фильтра
<bean id="filterInvocationInterceptor"
class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager" />
<property name="accessDecisionManager" ref="accessDecisionManager" />
<property name="objectDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/protected/engineering/**=ROLE_HEAD_OF_ENGINEERING
/protected/marketing/**=ROLE_HEAD_OF_MARKETING
/**=IS_AUTHENTICATED_ANONYMOUSLY
</value>
</property>
</bean>
|
В листинге 9 IF-фильтр принимает три параметра. Первый и третий параметры остались такими же, как были сконфигурированы изначально в первой части. Однако добавлен второй параметр - bean-компонент accessDecisionManager.
Bean-компонент accessDecisionManager отвечает за принятие решения по авторизации. Он использует определения значения для управления доступом, указанные в третьем параметре в листинге 9 для принятия решений по авторизации (управлению доступом). Третий параметр – это objectDefinitionSource.
Настройка bean-компонента accessDecisionManager
Bean-компонент accessDecisionManager определяет, может ли пользователь получить доступ к ресурсу. В Acegi представлено несколько подобных компонентов – менеджеров для принятия решений о доступе, которые отличаются механизмом, используемым ими для принятия подобных решений. В данной статье рассматривает только один тип менеджера для управления доступом, который конфигурируется в листинге 10:
Листинг 10. Конфигурация менеджера для управления доступом
<bean id="accessDecisionManager" class="org.acegisecurity.vote.AffirmativeBased">
<property name="decisionVoters">
<list>
<bean class="org.acegisecurity.vote.RoleVoter"/>
<bean class="org.acegisecurity.vote.AuthenticatedVoter" />
</list>
</property>
</bean>
|
В листинге 10 bean-компонент accessDecisionManager – это объект класса org.acegisecurity.vote.AffirmativeBased. Bean-компонент accessDecisionManager принимает только один параметр – список компонентов-избирателей (voters).
В Acegi компонент-избиратель определяет (голосует), имеет ли пользователь права на доступ к указанному ресурсу. При запросе от bean-компонента accessDecisionManager у компонента-избирателя есть один из трех вариантов ответа: доступ подтвержден (access-granted); в доступе отказано (access-denied); воздержаться от голосования, если не уверен.
Различные типы менеджеров для управления доступом отличаются тем, как они трактуют решения компонентов-избирателей. Менеджер для принятия решений AffirmativeBased, показанный в листинге 10, реализует простую логику решения: если хотя бы один из компонентов-избирателей проголосовал положительно, то пользователю разрешается доступ к запрошенному ресурсу.
Логика компонентов-избирателей
В Acegi представлено несколько типов реализации компонентов-избирателей. Bean-компонент accessDecisionManager передает информацию об аутентифицированном пользователе, включая его бизнес-роли, и объект objectDefinitionSource в компонент-избиратель. В примере используются компоненты-избиратели двух типов: RoleVoter и AuthenticatedVoter, как показано в листинге 10. Рассмотрим логику каждого из этих компонентов:
-
RoleVoter голосует, только если он смог обнаружить роль, начинающуюся с префикса
ROLE_в строке из объектаobjectDefinitionSource. Если компонент-избирательRoleVoterне смог найти ни одной такой строки, он воздерживается от голосования. Если он находит совпадающую роль в списке бизнес-ролей пользователя, то он подтверждает доступ, а если не находит подобной роли, то отказывает в доступе. В листинге 9 есть две роли с префиксомROLE_:ROLE_HEAD_OF_ENGINEERINGиROLE_HEAD_OF_MARKETING.
-
AuthenticatedVoter голосует, только если он смог обнаружить в объекте
objectDefinitionSourceстроки с какой-нибудь предопределенной ролью. В листинге 9, есть одна такая строка:IS_AUTHENTICATED_ANONYMOUSLY. Анонимная аутентификация означает, что пользователь не может быть аутентифицирован. Найдя эту строку,AuthenticatedVoterпроверяет, могут ли некоторые незащищенные ресурсы (невключенные ни в одну из строк с префиксомROLE_) быть доступны анонимно аутентифицированному пользователю. ЕслиAuthenticatedVoterобнаруживает, что запрашиваемый ресурс не защищен и объектobjectDefinitionSourceразрешает доступ к незащищенным ресурсам анонимно аутентифицированным пользователям, то он голосует за подтверждение доступа, в противном случае – за отказ в доступе.
В этой статье представлен пример приложения, демонстрирующего уже изученные основы Acegi и LDAP. LDAP-Acegi приложение показывает аутентифицированным пользователям главную страницу с инженерными и маркетинговыми документами соответственно. Как будет показано, LDAP-Acegi приложение позволяет пользователю alice просматривать инженерные документы, а пользователю bob – маркетинговые. Также оно позволяет специальному пользователю просматривать документы обоих типов. Все это было настроено в начале статьи при конфигурации сервера каталогов LDAP. Скачайте пример приложения, чтобы исследовать его более подробно.
В этой статье было рассмотрено, как хранить информацию о пользователях и их бизнес-ролях в каталоге LDAP. Также было подробно рассмотрено, как настраивать Acegi для взаимодействия с каталогом LDAP и реализовать политику управления доступом. В последней статье этого цикла будет показано, как настроить Acegi для обеспечения безопасного доступа к Java-классам.
| Описание | Имя | Размер | Метод загрузки |
|---|---|---|---|
| примеры кода из статьи | j-acegi2.zip | 10KB | HTTP |
Научиться
-
Securing Java applications with Acegi, Part 2: Working with an LDAP directory servers (EN): оригинал статьи.
-
Securing Java applications with Acegi, Part 1: Architectural overview and security filters (Bilal Siddiqui, developerWorks, март 2007 г.): статья с обзором архитектуры и компонентов системы безопасности Acegi.
-
Introduction to LDAP: Part 1: Installation and simple Java LDAP programming (EN) (Fred Simmons and Jeng Yoong Tan, developerWorks, апрель 2005 г.): подробный обзор использования LDAP для Java-программирования.
-
Storing Java objects in Apache Directory Server, Part 1 (Bilal Siddiqui, developerWorks, май 2006 г.): статья с дополнительными сведениями о ApacheDS.
-
Java security evolution and concepts, Part 1: Security nuts and bolts (EN) (Raghavan Srinivas, developerWorks, май 2000 г.): статья про основы безопасности в Java.
-
Role Based Access Control (RBAC): Web-сайт National Institute of Standards and Technology содержит информацию о стандартах RBAC (управлении доступом, основанном на ролях) и рабочих группах.
-
LDAP specification: группа IETF (Internet Engineering Task Force) поддерживает текущую спецификацию LDAP. Также можно ознакомиться с указанными RFC-документами, входящими в спецификацию LDAP:(EN)
- RFC 2256 объясняет классы объектов и типы атрибутов в LDAPV3.
- RFC 4513 описывает методы LDAP-аутентификации и алгоритмы безопасности.
- RFC 2254 описывает представление поисковых фильтров в LDAP.
- RFC 2253 describes UTF-8 string representation for distinguished names in LDAP.
-
Directory-info.com: Web-сайт c отличными материалами для изучения LDAP и каталогов в целом.(EN)
-
Технология Java: сотни статей по всем аспектам Java-программирования.
Получить продукты и технологии
-
ApacheDS: скачайте с Apache.org.(EN)
-
JXplorer: LDAP-клиент с открытым кодом, написанный на Java и использовавшийся в этой статье.(EN)
Обсудить
- Примите участие в обсуждении материала на форуме.
- Познакомьтесь с блогом сообщества developerWorks, слушайте podcast'ы developerWorks, чтобы быть в курсе последних веяний в технологиях.(EN)
Билал Сиддикви (Bilal Siddiqui) является инженером-электронщиком, консультантом по XML и соучредителем WaxSys, компании, чья деятельность направлена на упрощение электронного бизнеса. После окончания в 1995 г. Инженерно-технологического Университета, г. Лахор, и получения степени по электронной технике, он начал разрабатывать программные продукты для промышленных систем управления. В дальнейшем он занимался XML и использовал свой опыт в программировании C++ для разработки Web- и Wap-базируемых инструментов для XML-технологий, серверных парсинговых программных продуктов и служебных приложений. Билал – проповедник передовых технологий и часто публикуется в этой области.