- PVSM.RU - https://www.pvsm.ru -
Однажды мне пришлось заняться разработкой Web-приложения для корпоративного использования на Python+Django. И самым первым вопросом, который пришлось решать — это прозрачная авторизация на сайте или Single Sign-On (SSO).
На предприятии широко используется служба каталогов на базе Microsoft Active Directory, и к настоящему моменту практически все корпоративные приложения позволяют использовать windows-авторизацию и не вводить постоянно логины/пароли, поэтому новое приложение просто должно было удовлетворять существующему положению вещей и реализовывать указанную выше возможность для «прозрачной» авторизации пользователей.
Хотя о вопросе реализации SSO для Django написано немало статей, однако для того, чтобы реализовать то, что мне было необходимо, пришлось затратить относительно много времени. Поэтому, чтобы избавить некоторых из вас от возможных долгих поисков информации и ее сборки в работающую схему, предлагаю вам свой мануал, как сделать прозрачную авторизацию в приложении Django с использованием учетных записей Active Directory.
Итак мы имеем:
Нужно сделать:
Изучив ряд опубликованных статей и описаний стало понятно, что добиться нужного результата можно, выполнив два основных шага:
Совершенно очевидно, что реализация принципа SSO в сети Windows AD возможно используя протокол Kerberos. Поэтому основной задачей первого этапа настройки будет установка Kerberos в среде Linux+Apache и настройка связи с контроллером домена Windows AD.
На srv-app добавляем в /etc/hosts:
192.168.7.105 srv-app.company.ru
На srv-app изменяем /etc/resolv.conf (В реальности этот файл в CentOS7 генерирует NetworkManager, поэтому изменения нужно вносить в /etc/sysconfig/network-scripts/ifcfg-eth0):
search company.ru
nameserver 192.168.7.110
На Контроллере домена DC-1:
[root@srv-app ~]# yum install mod_auth_kerb # Устанавливаем модуль для apache
[root@srv-app ~]# yum install krb5-workstation # Устанавливаем пакет для настройки и тестирования kerberos
[logging]
default = FILE:/var/log/krb5libs.log
kdc = FILE:/var/log/krb5kdc.log
admin_server = FILE:/var/log/kadmind.log
[libdefaults]
dns_lookup_realm = false
ticket_lifetime = 24h
renew_lifetime = 7d
forwardable = true
rdns = false
default_realm = COMPANY.RU
default_ccache_name = KEYRING:persistent:%{uid}
[realms]
COMPANY.RU = {
kdc = 192.168.7.110
admin_server = 192.168.7.110
}
[domain_realm]
.company.ru = COMPANY.RU
company.ru = COMPANY.RU
Немного объяснений по поводу содержимого конфигурационного файла:
Ранее, на нашем контроллере домена мы создали пользователя srv-apache с паролем P@ssw0rd. Попробуем залогиниться на КД при помощи утилиты kinit:
[root@dsrv-app ~]# kinit svc-apache@COMPANY.RU
Password for admin@COMPANY.RU: ****
Если ошибок нет, посмотрим какие билеты (tickets) у нас имеются:
[root@srv-app ~]# klist
Ticket cache: FILE:/tmp/krb5cc_0
Default principal: srv-apache@COMPANY.RU
Ticket cache: KEYRING:persistent:0:0
Default principal: svc-apache@COMPANY.RU
Valid starting Expires Service principal
20.12.2015 16:12:59 21.12.2015 02:12:59 krbtgt/COMPANY.RU@COMPANY.RU
renew until 27.12.2015 16:12:55
Таким образом мы залогинились на КД при помощи kerberos, теперь разорвем соединение, удалив
полученный билет:
[root@srv-app ~]# kdestroy
Если все работает, то для дальнейшей настройки нам необходимо создать файл krb5.keytab для сервиса аутентификации при помощи
Apache и mod_auth_kerb.
Сгенерировать keytab можно на контроллере домена DC-1 при помощи команды ktpass.exe:
ktpass.exe /princ HTTP/srv-app.company.ru@COMPANY.RU /mapuser svc-apache@COMPANY.RU /crypto ALL /ptype KRB5_NT_PRINCIPAL /mapop set /pass P@ssw0rd /out c:sharekeytab
В итоге мы получаем файл c:sharekeytab, который необходимо скопировать на srv-app и назвать /etc/krb5.keytab. Далее необходимо предоставить доступ к этому файлу пользователю, из под учетной записи которого выполняется сервер httpd. В нашем случае это apache. Для того чтобы apache мог прочитать этот файл просто разрешаем его чтение всем пользователям:
chmod a+r /etc/krb5.keytab
Проверить работает ли наш keytab можно следующим образом:
1. При помощи ktutil:
[root@srv-app ~]# ktutil
ktutil: rkt /etc/krb5.keytab
ktutil: list
slot KVNO Principal
---- ---- ---------------------------------------------------------------------
1 3 HTTP/srv-app.company.ru@COMPANY.RU
2 3 HTTP/srv-app.company.ru@COMPANY.RU
3 3 HTTP/srv-app.company.ru@COMPANY.RU
4 3 HTTP/srv-app.company.ru@COMPANY.RU
5 3 HTTP/srv-app.company.ru@cCOMPANY.RU
ktutil: q
2. При помощи kvno:
# логинимся на KDC
[root@srv-app ~]# kinit svc-apache
Password for svc-apache@COMPANY.RU:
# запрашивем тикет для сервиса <b>HTTP/srv-app.company.ru@COMPANY.RU</b> и печатает номера версий для каждого принципала в keytab
[root@srv-app ~]# kvno HTTP/srv-app.company.ru@COMPANY.RU
HTTP/srv-app.company.ru@COMPANY.RU: kvno = 3
# Удаляем тикет
[root@srv-app ~]# kdestroy
Ниже приведен файл /etc/httpd/conf.d/company_main.conf, который содержит конфигурационные инструкции для настройки Kerberos-аутенификации при обращении к URI "/":
<Location "/">
# Kerberos authentication:
AuthType Kerberos
AuthName "SRV-APP auth"
KrbMethodNegotiate on
KrbMethodK5Passwd off
KrbServiceName HTTP/srv-app.company.ru@COMPANY.RU
KrbAuthRealms COMPANY.RU
Krb5Keytab /etc/krb5.keytab
KrbLocalUserMapping On
Require valid-user
</Location>
Хочу обратить внимание на настройку KrbMethodK5Passwd off. Указанная настройка приводит к тому что при входе в указанный раздел сайта будет произведена kerberos-аутентификациия с использованием технологии Single Sign-On. При неуспешной аутентификации сразу будет ошибка «401 Unautorized». Однако, если изменить настройку на KrbMethodK5Passwd on, то после неуспешной авторизации Single Sign-On, будет предпринята попытка авторизации по имени и паролю.
И еще одна недокументированная возможность, которой мы воспользуемся: Настройка KrbLocalUserMapping On приводит к тому что в переменной REMOTE_USER будет помещено имя зарегистрированного пользователя (в случае KrbLocalUserMapping Off REMOTE_USER будет содержать username@COMPANY RU).
Дополнительную информацию по настройкам модуля mod_auth_kerb пожно прочитать здесь [1].
Еще раз повторюсь, что вся работа по настройке kerberos аутентификации в Linux проделана для того, чтобы иметь возможность входить на страницы портала опубликованного на Linux машине с использованием корпоративных аккаунтов, хранящихся в Active Directory, кроме того для упрощения жизни пользователям этот вход должен быть «прозрачным», без запроса пароля, что достигается иcпользованием технологии Single Sign-On (SSO), которая поддерживается в Windows 7 и выше и браузером Internet Explorer (и Mozilla Firefox).
Однако для того чтобы все проходило гладко, на рабочей станции, откуда осуществляется такой вход, должны быть выполнены следующие настройки:
Итак, в результате работы проведенной на первом этапе, мы получили следующие результаты:
Однако, несмотря на то, что сервер Apache авторизовал нашего пользователя, для Django-приложения он все еще остается неизвестным и, соответственно, весь отлаженный в Django механизм аутентификации/авторизации пользователей и использования сессий остается пока незадействованным.
Специально для таких целей в Django существует простое решение [2], включающее механизм авторизации в системе пользователей, уже аутентифицированных внешними приложениями, такими как IIS или Apache (способами аналогичными, примененному нами на этапе 1: mod_authnz_ldap, CAS, Cosign, WebAuth, mod_auth_sspi, mod_auth_krb).
Согласно описанию на официальном сайте djangoproject.org, для использования прозрачной аутентификации и авторизации в Django таким способом, достаточно задействовать механизм RemoteUserBackend, выполнив следующие шаги:
1. В файле настроек Django-проекта settings.py добавить django.contrib.auth.middleware.RemoteUserMiddleware в список MIDDLEWARE_CLASSES сразу после django.contrib.auth.middleware.AuthenticationMiddleware:
MIDDLEWARE_CLASSES = [
'...',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.RemoteUserMiddleware',
'...',
]
2. Там же заменить ModelBackend на RemoteUserBackend в списке AUTHENTICATION_BACKENDS (либо добавить этот список в settings.py):
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.RemoteUserBackend',
]
В результате этих изменений RemoteUserMiddleware будет извлекать username посредством request.META['REMOTE_USER'] и автоматически выполнять аутентификацию и авторизацию (login) этого пользователя при помощи RemoteUserBackend. Кроме того RemoteUserBackend при такой авторизации добавит нового пользователя в таблицу auth_user, задействуя таким образом стандартный для Django механизм работы с учетными записями.
Но, к сожалению, этот способ не позволит нам получать из Active Directory и использовать в нашем приложении нужную нам информацию (first_name, last_name, mail, участие в группах AD и др).
Итак, нам нужно получить доступ к Active Directory при помощи протокола LDAP. К счастью отличным Django-приложением для этих целей является django-auth-ldap [3]. Установить его можно стандартно при помощи pip:
pip install django-auth-ldap
После этого придется удалить из settings.py добавленные в предыдущем разделе MIDDLEWARE_CLASSES, а именно 'django.contrib.auth.middleware.RemoteUserMiddleware', а список AUTHENTICATION_BACKENDS должен выглядеть следующим образом:
AUTHENTICATION_BACKENDS = (
'django_auth_ldap.backend.LDAPBackend',
'django.contrib.auth.backends.ModelBackend',
)
Кроме того, в settings.py необходимо включить следующие конфигурационные параметры:
# Baseline LDAP configuration.
AUTH_LDAP_SERVER_URI = "ldap://DC-1.COMPANY.ru"
AUTH_LDAP_AUTHORIZE_ALL_USERS = True
AUTH_LDAP_PERMIT_EMPTY_PASSWORD = True
# Логин пользователя от чьего имени будут выполнятся запросы к LDAP (кроме авторизации)
AUTH_LDAP_BIND_DN = "cn=svc-apache,cn=Users,dc=company,dc=ru"
AUTH_LDAP_BIND_PASSWORD = "P@ssw0rd"
# Настройка будет пытаться найти пользователя в созданной нами OU Django и стандартной папке Users,
# сопоставляя введенный login пользователя с аттрибутами sAMAccountName
AUTH_LDAP_USER_SEARCH = LDAPSearchUnion(
LDAPSearch("ou=Django,dc=company,dc=ru", ldap.SCOPE_SUBTREE, "(sAMAccountName=%(user)s)"),
LDAPSearch("cn=Users,dc=company,dc=ru", ldap.SCOPE_SUBTREE, "(sAMAccountName=%(user)s)"),
)
# Set up the basic group parameters.
AUTH_LDAP_GROUP_SEARCH = LDAPSearch("ou=Groups,ou=Django,dc=company,dc=ru",
ldap.SCOPE_SUBTREE, "(objectClass=groupOfNames)"
)
AUTH_LDAP_GROUP_TYPE = GroupOfNamesType(name_attr="cn")
# Simple group restrictions
# AUTH_LDAP_REQUIRE_GROUP - если определено DN для этой настройки, то требуется присутсвие пользователя в этой группе
# в противном случае пользовталю будет отказано в аутентификации
# таким образом указываем, что для того чтобы пользователь был аутентифицирован он обязан находится в группе "active"
AUTH_LDAP_REQUIRE_GROUP = "cn=active,ou=Groups,ou=Django,dc=company,dc=ru"
# AUTH_LDAP_DENY_GROUP - если определено DN для этой настройки, то в случае члентсва пользователя в этой группе
# ему будет отказано в аутентификации
AUTH_LDAP_DENY_GROUP = "cn=disabled,ou=Groups,ou=Django,dc=company,dc=ru"
# Populate the Django user from the LDAP directory.
# Указываем как переносить данные из AD в стандартный профиль пользователя Django
AUTH_LDAP_USER_ATTR_MAP = {
"first_name": "givenName",
"last_name": "sn",
"email": "mail"
}
# Указываем как переносить данные из AD в расширенный профиль пользователя Django
AUTH_LDAP_PROFILE_ATTR_MAP = {
"employee_number": "employeeNumber"
}
# Указываем привязку стандартных флагов is_active, is_staff и is_superuser к членству в группах AD
# Флаг is_active при использовании django_remote_auth_ldap сам по себе не оказывает вляния на разрешение аутнтификации
# поэтому для создания обычного поведения Django также определяме настройку AUTH_LDAP_REQUIRE_GROUP (см.выше)
AUTH_LDAP_USER_FLAGS_BY_GROUP = {
"is_active": "cn=active,ou=Groups,ou=Django,dc=company,dc=ru",
"is_staff": "cn=staff,ou=Groups,ou=Django,dc=company,dc=ru",
"is_superuser": "cn=superuser,ou=Groups,ou=Django,dc=company,dc=ru"
}
# Указываем привязку флагов расширенного профиля к членству в группах AD
AUTH_LDAP_PROFILE_FLAGS_BY_GROUP = {
"is_awesome": "cn=awesome,ou=Groups,ou=Django,dc=company,dc=ru",
}
# This is the default, but I like to be explicit.
AUTH_LDAP_ALWAYS_UPDATE_USER = True
# Use LDAP group membership to calculate group permissions.
AUTH_LDAP_FIND_GROUP_PERMS = True
# Cache group memberships for an hour to minimize LDAP traffic
AUTH_LDAP_CACHE_GROUPS = True
AUTH_LDAP_GROUP_CACHE_TIMEOUT = 3600
Django-auth-ldap — это замечательное приложение. С его помощью в профиль пользователя Django можно загрузить практически любую информацию по нему из AD, даже есть возможность «подтянуть» группы в которых этот пользователь участвует в соответствующую модель Django.
Однако для авторизации при помощи django-auth-ldap все-таки необходимо запрашивать у пользователя его имя и пароль, что нам категорически не подходит.
Хотя в документации указано что, вроде бы, можно «скрестить» django-auth-ldap и RemoteUserBackend:
Но это не работает, так как нам надо. Пользователь действительно сможет авторизоваться, но это происходит ровно так, как если бы мы просто использовали RemoteUserBackend (см.предыдущий раздел). Информация из AD в профиль пользователя Django автоматически не загружается.
Конечно, это можно сделать самостоятельно, воспользовашись следующей рекомендацией:
from django_auth_ldap.backend import LDAPBackend
user = LDAPBackend().populate_user('alice')
if user is None:
raise Exception('No user named alice')
Но, как оказалось, все гораздо проще. Велосипед уже придуман, и нам остается только им воспользоватся. Приложение django-remote-auth-ldap [4] является небольшой надстройкой над django_auth_ldap и позволяет без лишних усилий авторизовать пользователя и загрузить его данные из AD во время авторизации.
Устанавливаем django-remote-auth-ldap стандартно (django-auth-ldap также необходим для работы этой надстройки):
pip install django-remote-auth-ldap
Далее, необходимо добавить в settings.py следующую настройку:
DRAL_CHECK_DOMAIN = False
Дело в том, что django-remote-auth-ldap, видимо разработан для работы c IIS, который переменную REMOTE_USER устанавливает в формате «DOMAIN/username», мы же выполнили настройку mod_auth_kerb так, что имя домена в REMOTE_USER не попадает.
Ну и снова рекомендации для настройки MIDDLEWARE_CLASSES и AUTHENTICATION_BACKENDS:
1. В файле настроек Django-проекта settings.py добавить django.contrib.auth.middleware.RemoteUserMiddleware в список MIDDLEWARE_CLASSES сразу после django.contrib.auth.middleware.AuthenticationMiddleware:
MIDDLEWARE_CLASSES = [
'...',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.RemoteUserMiddleware',
'...',
]
2. AUTHENTICATION_BACKENDS должен выглядеть так:
AUTHENTICATION_BACKENDS = [
'django_remote_auth_ldap.backend.RemoteUserLDAPBackend',
]
На этом все, прозрачная аутентификация в Django настроена.
Если пользователь входящий в ваше Django-приложение, уже авторизован в Active Directory, то:
1. Apache авторизует его при помощи Kerberos, допустит к страницам вашего приложения и запишет в REMOTE_USER имя авторизованного пользователя.
2. RemoteUserMiddleware «увидит» значение REMOTE_USER и иницирует аутентификацию и авторизацию указанного пользователя в Django с помощью django-remote-auth-ldap
3. django-remote-auth-ldap аутентифицирует и авторизует пользователя при помощи методов, унаследованных от приложения django-auth-ldap, которое «подтянет» в Django необходимую вам информацию из Active Directory.
Автор: mitshel
Источник [5]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/python/108709
Ссылки в тексте:
[1] здесь: http://modauthkerb.sourceforge.net/configure.html
[2] простое решение: https://docs.djangoproject.com/en/dev/howto/auth-remote-user/
[3] django-auth-ldap: http://pythonhosted.org/django-auth-ldap/
[4] django-remote-auth-ldap: https://pypi.python.org/pypi/django-remote-auth-ldap/0.1.0
[5] Источник: http://habrahabr.ru/post/274931/
Нажмите здесь для печати.