Аутентификация

Материал из Eludia
Перейти к: навигация, поиск
Иногда привратник допрашивает его, выпытывает, откуда он родом и многое другое, но вопросы задаёт безучастно, как важный господин, и под конец непрестанно повторяет, что пропустить его он ещё не может.

Ф. Кафка, «Процесс».

Содержание

Поскольку работа по протоколу HTTP представляет собой обмен запросами/ответами без малейшей гарантии сохранности соединения, опознание пользователя приходится производить на каждый такой запрос.

Подавляющее большинство WEB-приложений, подразумевающих какую-либо персонализацию, подразумевают следующий механизм:

  • при первом обращении пользователя, подтвердившего свою идентичность, создают в некоей БД временный объект, связанный с ним (сессию);
  • привязывают сессию в своей БД к каким-либо реквизитам HTTP-запроса, которые должны повторяться при последующих обращениях того же пользователя;
  • выдают на клиент уникальный id сессии;
  • все последующие запросы с этим id и подходящие по реквизитам, считают пришедшими от данного пользователя;
  • достаточно старые сессии автоматически удаляют.

Такой механизм реализован и в Eludia.pm. Сессии хранятся в таблице sessions и привязываются к IP-адресу пользователя. Кроме того, если не установлен параметр $preconf -> {core_no_cookie_check}, то генерируется cookie с именем "client_cookie" и случайным значением — и сессия дополнительно привязывается к ней.

Многие подобные технологии предполагают передачу значения номера сессии через cookie. В Eludia.pm номер сессии всегда передаётся в качестве параметра запроса $_REQUEST {sid}, об этом заботится URL rewriting. Такой подход имеет существенное преимущество: с одного рабочего места одним браузером можно работать в произвольном числе сессий от имени различных пользователей (разумеется, для того, кто имеет возможность авторизоваться от имени каждого из них).

Время жизни сессии в минутах определяется параметром $conf -> {session_timeout}. Если браузер открыт на какое-либо Eludia-приложение, но никаких действий не производится, то за 1 минуту до истечения срока сессии автоматически посылается специальный запрос с параметром keepalive, равным её номеру — и обработчик продлевает этот срок.

Форма "логин/пароль"

Logon.gif

Eludia.pm — "движок" для приложений с обязательной аутентификацией, поэтому работа вне сессии невозможна. Если параметр sid пуст или не соответствует номеру актуальной сессии, то по умолчанию клиент перенаправляется на форму аутентификации, каковой всегда считается экран типа logon.

Более подробно: сначала идёт перенаправление на адрес '/?type=_boot', в ответ приходит страница, проверяющая, что javaScript включён и функция window.open не подавлена — и если всё это выполнено, то идёт перенаправление уже средствами javaScript на адрес '/?type=logon'. Впрочем, проверочную страницу можно отключить параметром $preconf -> {core_skip_boot}.

В стандартном случае процедура select_logon может возвращать пустое значение, а draw_logon — сводиться к вызову draw_logon_form. В результате на экране отобразится стандартная для заданного $_SKINа форма аутентификации с полями login и password, вызывающая действие execute. Если этого недостаточно, разработчик приложения должен выбрать в select_logon какие-то дополнительные данные и переписать draw_logon с их использованием. Менять имя действия можно, но начинаться оно должно со строки execute.

Процедура do_execute_logon, соответственно, должна:

  • выяснить, имеется ли в данной системе пользователь с именем $_REQUEST {login};
  • проверить, соответствует ли ему пароль $_REQUEST {password};
  • если всё в порядке, то завести сессию: например, при помощи start_session.

В простых случаях логин хранится в таблице users вместе с зашифрованным паролем — соответственно, проверка производится средствами SQL:

sub do_execute_logon {

 my $_USER = sql_select_hash ("SELECT * FROM users WHERE login = ?", $_REQUEST {login});

 $_USER -> {id} or die ("#login#:No such user");

 sql_select_scalar (
   "SELECT id FROM users WHERE id = ? AND password = PASSWORD(?)", 
   $_USER -> {id}, 
   $_REQUEST {password}
 ) or die ("#password#:Bad password");

 start_session ($_USER -> {id});

 delete $_REQUEST {$_} foreach (qw(type login password));

}

Впрочем, достаточно распространён вариант, в котором первичным источником данных о пользователях, а также средством проверки их подлинности является LDAP-каталог.

Автоматический заход

Как правило, необходимость часто вводить логин и пароль раздражает и подталкивает к обдумыванию различных сценариев автоматизации этого процесса.

Аутентификация по cookie

Как уже отмечалось, Eludia.pm использует для передачи номера сессии не cookies, а параметр запроса. Однако можно выставить опцию $preconf -> {core_auth_cookie} — при этом значение $_REQUEST {sid} будет дублироваться в одноимённой cookie и восстанавливаться из неё после перезапуска браузера.

REMOTE_USER

Если вам посчастливилось писать приложение, будучи твёрдо уверенным, что опознание пользователя берёт на себя тот же WEB-сервер, в который загружен ваш код (или, во всяком случае, вы видите устанавливаемые им переменные контекста — скажем, через FastCGI), то всё достаточно просто. Примеры таких конфигураций:

  • Доменная аутентификация:
  • аутентификация по клиентским сертификатам;
  • наконец, Basic-аутентификация.

На этапе извлечения данных для логин-формы (то есть в select_logon) вам уже должен быть известен логин пользователя, который успешно прошёл процедуру аутентификации: он доступен в качестве значения $ENV {REMOTE_USER}. Разумеется, при этом необходима возможность достать из некоторого источника данных по такому имени все необходимые реквизиты пользователя (ФИО, mail и т. д.): вряд ли кого устроит, чтобы вместо фамилии на всех формах использовался login. Наиболее характерным примером такого источника данных является LDAP-каталог.

Если всё вышеперечисленное у вас в наличии имеется, то остаётся лишь в select_logon:

  • найти по логину учётную карточку во внешнем источнике данных;
  • импортировать её в таблицу users;
  • завести сессию;
  • сделать redirect.

Вот пример из реальной жизни (некоторые подпрограммы здесь не приведены, но их смысл понятен из названий):

sub select_logon {

	my $re = "^$preconf->{ldap}->{prefix}";
	
	$re =~ s{\\}{\\\\};

        if ($ENV {REMOTE_USER} =~ /$re/) {
       
        	my $login = $';
		_ldap_reconnect ();
		my $mesg = _ldap_bind ();
		
		return {} if $mesg -> code;
		
		$mesg = $ldap -> search (
			base   => $preconf -> {ldap} -> {base},
			filter => "($preconf->{ldap}->{fields}->{login}=$login)",
		);

		return {} if $mesg -> code;
		my ($entry) = $mesg -> entries;
		$entry or return {};
		our $_USER = _ldap_entry_2_user ($entry);
		do_execute_logon ();
		redirect ({});

	}	
	
	return {};
	
}

SSO

Ntlm.jpg

Все известные автору инсталляции Eludia.pm предполагают, что клиенты работают под Windows и используют MSIE. Кроме того, большинство рабочих мест (но, что важно, не все) находятся в ЛВС с доменной Windows-аутентификацией. При этом естественным образом возникает желание реализовать Single Sign On: заставить WEB-приложение учитывать логин/пароль, введённые при входе в Windows.

Если само приложение работает на Windows—сервере, то с этим проблем нет: всё сводится к использованию $ENV {REMOTE_USER}, как в предыдущем пункте. Однако в силу различных соображений приложения на базе Eludia.pm обычно работают под UNIX/Linux — и тут с доменной аутентификацией всё не так просто.

Winbindd

Возможно, идеологически правильным ходом является установка Apache/mod_auth_ntlm_winbind — и дальнейшее использование $ENV {REMOTE_USER}, как описано выше. Недостатком такого подхода (помимо ощутимой сложности администрирования samba/winbindd, да и ОС в целом) является то, что в качестве front end должен выслупать Apache2 — в то время как от этого устаревающего ПО вообще хочется отказаться в пользу nginx / FastCGI.

Здесь стоит отметить, что в настоящее время (точнее, с тех пор, как стали широко продаваться ПК с предустановленной Windows Vista) нет никаких шансов использовать для аналогичной цели модули типа Apache::AuthenNTLM или mod_ntlm: они используют старый фокус с прослушиванием передаваемого пароля, который не работает при использовании протокола NTLM2: а уже в Vista он включён по умолчанию.

OpenSSO

Если в корпорации-заказчике установлен, качественно поддерживается и стабильно работает OpenSSO-сервер, да к тому же его DNS-имя совпадает с вашим минимум на уровне суффикса 2-го уровня (то есть вы оба *.my_small_corporation.su), можно этим воспользоваться: в Eludia.pm имеется готовый модуль на базе WEB-сервисов. Механизм и настройки показаны в соответствующем разделе.

TinySSO

Ещё один вариант использования доменной аутентификации с привлечением внешнего сервера: TinySSO. Это самодельная лёгкая альтернатива OpenSSO, требующая минимального администрирования.

NTLM своими руками

Eludia.pm поддерживает такую экзотическую возможность, как ведение NTLM-диалога с клиентом (в том числе из-за реверсного proxy) — однако при условии доступа на чтение к зашифрованным паролям. То есть в том случае, если домен построен не на родной Active Directory, а на её эмуляции на базе Samba.

Oracle SSO

Увы, мы не представляем себе, как приспособить Oracle Enterprise Single Sign-On для сколь-нибудь реальных WEB-приложений, написанных на Perl5. По идее, всё должно быть стандартно: mod_osso устанавливает $ENV {REMOTE_USER} — а дальше мы знаем, что делать. Но этим можно было бы воспользоваться из-под mod_perl, загруженного в тот же Apa... извините, Oracle HTTP Server. Увы, этот сервер комплектуется недопустимо устаревшими бинарными сборками Perl5 и mod_perl — в них встречаются такие ошибки (прежде всего, в DBD::Oracle!), что на практике они неприменимы. А если поставить свой mod_perl позади Oracle HTTP Server, работающего как реверсный proxy — REMOTE_USER будет уже не видно.

Может быть, кому-то удалось преодолеть описанные ограничения — мы с удовольствием сослались бы на описание такого опыта.

Делегирование полномочий

Если в вашей системе 1 сотрудник - это ровно 1 логин, это правильная система, но, вероятно, очень маленькая. Либо сотрудники передают логины/пароли друг другу. В серьёзных местах у серьёзных руководителей обязательно имеются помощники с правом подделки подписи. Да и вообще, например, на время отсутствия бывает необходимо залогиниться в систему под чужой учёткой.

Имеет смысл легализовать этот процесс: дать сотруднику возможность явно указать, кому он доверяет. Или администратору — кто кому должен доверять. Заместителям же и секретарям предоставить всю полноту интерфейса того, за кого они работают — но с записью того, кто на самом деле выполняет каждое действие.

Eludia.pm поддерживает такую возможность: при установке $conf -> {core_delegation} = 1 в таблицах sessions и log возникают дополнительные поля id_user_real: туда записывается, соответственно id сотрудника, который реально производит действия в системе (заместителя), а в id_user при этом фигурирует ссылка на номинального пользователя (замещаемого). При этом в контексте приложения истинный (заместительский) id виден как $_USER -> {id__real}. Для обычных заходов в систему $_USER -> {id__real} = $_USER -> {id}.

Как хранить права на замещение — это уже дело приложения. Как именно осуществлять заход заместителя — тоже. Должно быть действие, где в таблицу sessions записывается подменное значение id_user: это может быть как do_execute_logon, так и специальное действие. Например, вот так можно дать некоему админу принимать обличие кого угодно в системе:

sub do_pretend_users { # Зайти под чужим пользователем
 
	$_USER -> {login} eq $preconf -> {admin_login} or die "!!!";
	
	sql_do ('UPDATE sessions SET id_user = ? WHERE id = ?', $_REQUEST {id}, $_REQUEST {sid});
	
	redirect ("/?sid=$_REQUEST{sid}", {kind => 'js', target => '_top'});

}

Соответствующее обратное действие:

sub do_exit_users { # Выход из текущей сессии

	$_USER -> {id} != $_USER -> {id__real} or out_script ('top.close ()');

	sql_do ('UPDATE sessions SET id_user = ? WHERE id = ?', $_USER -> {id__real}, $_REQUEST {sid});
	
	redirect ("/?sid=$_REQUEST{sid}", {kind => 'js', target => '_top'});

}

Некрасивый, но действенный способ инициировать это действие со стандартной кнопки "Выход":

$conf -> {exit_url} = "javaScript:nope (
  '/?sid=$_REQUEST{sid}&type=users&action=exit&_salt=@{[rand ()]}', 'invisible'
)";

где-нибудь в select_subset.


Muzzle.jpg Тема этой заметки имеет некоторое отношение к компьютерной так называемой "безопасности".
Персональные инструменты
Пространства имён

Варианты
Действия
Навигация
Разработчику
Администратору
Инструменты