Об авторе: старший программист компании Cetera Labs (www.cetera.ru).
Email: o.elifantiev@cetera.ru
Веб-разработчики с завидной регулярностью получают заказы на создание . Под порталами в данном случае понимаются внутренние сайты предприятий, предоставляющие информацию и сервисы для собственных сотрудников и наиболее близких партнёров.
Подавляющее большинство крупных предприятий уже активно использует единую авторизацию на основе служб каталогов1, доступ к которым осуществляется по Lightweight Directory Access Protocol (LDAP)2.
Совершенно естественно, что заказчики заинтересованы в интеграции разрабатываемого портала со службами каталогов, а разработчикам выгодно использовать внешнюю систему авторизации с точки зрения сокращения объёма работ в проекте. И хотя в ряд коробочных продуктов класса "Система управления информацией" встроена интеграция со службами каталогов, разработчикам, тем не менее, приходится решать различные технические и организационные вопросы.
Проект, который реализовывала наша компания в начале 2008 года, представляет собой функционально насыщенный корпоративный информационный портал холдинга, включающего в себя несколько территориально распределенных компаний. Подразделения холдинга большей частью объединены службой каталогов Microsoft Active Directory (AD)3.
Платформа для реализации проекта: Linux, Apache, PHP, MySQL, Cetera CMS4
Основные причины использования авторизации на основе AD в данном проекте:
Две основные задачи - импорт данных из AD и дальнейшая авторизация пользователей, просматривающих страницы портала.
Импорт данных из AD осуществляется через LDAP. LDAP позволяет получить доступ к информации домена - списку пользователей, групп, компьютеров домена и т.д.
Авторизация пользователя производится средствами Apache, а точнее его модуля mod_ntlm (доступен для версий Apache 1.3.х, для 2.2.х используется модуль mod_auth_sspi)5. Mod_ntlm авторизует пользователя на этапе обращения к странице, и, если пользователь проходит авторизацию, его данные (имена домена и пользователя) передаются в переменных сервера (для PHP это $_SERVER)
В рамках данного проекта требовалось получить следующую информацию о пользователе из AD:
Имя пользователя в домене (его логин) хранится в поле SAMAccountName. Title, Department и Company описывают должность, отдел и компанию. Email хранится в поле Mail, полное имя пользователя содержится в поле Name.
Всякая запись в каталоге LDAP состоит из одного или нескольких атрибутов и обладает уникальным именем (DN — англ. Distinguished Name). Имя может выглядеть, например, следующим образом: «cn=Иван Петров, ou=Сотрудники, dc=example, dc=com». Уникальное имя состоит из одного или нескольких относительных уникальных имен (RDN — англ. Relative Distinguished Name), разделённых запятой. Относительное уникальное имя имеет вид ИмяАтрибута=значение. На одном уровне каталога не может существовать двух записей с одинаковыми относительными уникальными именами. В силу такой структуры имени записи в каталоге LDAP можно легко представить в виде дерева.
Для выполнения поиска по структуре службы каталога требуется указать путь к корневому элементу, относительно которого будет осуществлен поиск. Также можно указать фильтр, состоящий из перечисления имен атрибутов записи и их значений в формате ИмяАтрибута=Значение.
Предположим, что требуется осуществить импорт сотрудников домена COMPANY.RU. Для этого путь поиска будет, например, такой:
cn=Users указывает на т.н. контейнер под название Users.
При выполнении такого поиска без дополнительной фильтрации в результатах могут присутствовать другие элементы помимо самих пользователей. Например, данные о группах. Для получения в результатах поиска лишь данных о пользователях укажем фильтр:
В некоторых случаях пользователи в AD могут размещаться в т.н. Organizational Units. В таком случае используем путь поиска:
Такой путь подразумевает, что данные об учетных записях находятся в Organizational Unit под названием Users-and-computers.
В процессе импорта может возникнуть потребность определения активности учетной записи пользователя.
При импорте может быть интересен атрибут учетной записи UserAccountControl 6, в котором сохраняются в двоичном виде различные свойства учетной записи. Интересным может быть признак ACCOUNTDISABLE (0x0002). Если данный флаг установлен в атрибуте UserAccountControl, учетная запись считается заблокированной.
Для поиска всех активных пользователей потребуется модифицировать фильтр. Он будет таким:
1.2.840.113556.1.4.804 - т.н. OID (Object IDentifier), данный OID применяется для отбора объектов, выбранный атрибут которых содержит либо все, либо любой из указанных в фильтре битов. Приведенный выше OID признает совпадение, если в атрибуте присутствует любой из указанных битов. 2 - это значение флага ACCOUNTDISABLE. Данный фильтр целиком можно расшифровать так: Атрибут objectcategory равен Person и в атрибуте UserAccountControl не присутствует бит 2 (0x0002)
<?
/**
* Данный код подключается к AD и получает список всех незаблокированных сотрудников
* контейнера Users из домена СOMPANY.RU
* Выводится имя сотрудника, его email, компания, отдел и должность в соответствии с данными,
* полученными из AD
*/
$ds=ldap_connect("1.2.3.4");
if ($ds) {
$r=ldap_bind($ds,'COMPANY\\admin','adminPassword');
$sr=ldap_search($ds,
"cn=Users, dc=COMPANY, dc=RU",
'(&(objectcategory=Person)(!(UserAccountControl:1.2.840.113556.1.4.804:=2)))');
echo "Number of entires returned is " . ldap_count_entries($ds, $sr) . "<br />";
$info = ldap_get_entries($ds, $sr);
// $info содержит результаты запроса...
for ($i=0; $i<$info["count"]; $i++) {
echo "Name: {$info[$i]['name'][0]}<br />\n";
echo "Email: {$info[$i]['mail'][0]}<br />\n";
echo "Company: {$info[$i]['company'][0]}<br />\n";
echo "Department: {$info[$i]['department'][0]}<br />\n";
echo "Title: {$info[$i]['title'][0]}<br />\n";
}
ldap_close($ds);
} else {
echo "Unable to connect to LDAP server";
}
?>
Дополнительная информация по работе с LDAP в PHP может быть получена в документации 7
Почти всегда в службе каталогов не содержится вся информация, необходимая для работы портала. Могут отсутствовать как "банальные" данные, например, номер телефона, так и нетипичные для службы каталогов сведения типа "перечень мест предыдущей работы для нужд корпоративной социальной сети". При этом необходимость этих данных для портала сложно недооценивать.
Нашим решением проблемы является хранение учётных записей в службе каталогов, а расширенной информации - в открытой БД портала. Предполагается, что управление списком и ролями пользователей осуществляется в службе каталогов, а всё остальное - задачи портала.
Недостатком решения является необходимость связывания и синхронизации перечней пользователей в службе каталогов и в БД портала.
Важнейшие преимущества:
Стандартом де-факто для корпоративных порталов является отображение дерева компаний холдинга и отделов с автоматической привязкой к дереву сотрудников. При этом часто в службе каталогов сотрудники хранятся единым плоским списком с указанием компаний и отделов в каких-либо свойствах пользователей службы каталогов.
Наше решение:
По итогам проекта мы считаем, что:
1 http://en.wikipedia.org/wiki/Directory_service
2 http://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol
3 http://ru.wikipedia.org/wiki/Active_Directory
4 http://www.cetera.ru/products/cms/
5 http://www.gknw.net/development/apache/
6 http://support.microsoft.com/?kbid=305144
7 http://ru2.php.net/manual/ru/ref.ldap.php
Проведите конкурс среди участников CMS Magazine
Узнайте цены и сроки уже завтра. Это бесплатно и займет ≈5 минут.