При разработке крупного портала или сети сайтов, объединенных общей базой пользователей, возникает вопрос о том, как реализовать авторизацию на всех сайтах через одно окно. Грубо говоря, если Вася авторизовался на сайте A, то при входе на сайт B он должен видеть сообещение “Привет, Вася!”.
В чем состоит основная сложность? В том, что встроенными средствами браузера мы не можем передавать cookie от одного сайта к другому. Если мы авторизовались на сайте A, то на сайте B мы не можем об этом узнать, не применив бубна и парочки заклинаний. Для обхода этой проблемы были придуманы такие технологии, как CAS. Возможно, это именно то, что вам нужно для решения вашей проблемы.
Однако, сегодня я хочу поговорить не о CAS, а о той технике единой авторизации, которую мы используем в разрабатываемой системе управления порталами. В основе техники лежит расшаривание сессии между сервисами.
Если вы разрабатываете сайты с использованием Ruby on Rails, то знаете, что рельсы могут хранить сессии в различных типах хранилищ - напрямую в cookie, в базе данных, в файловой системе и в memcached. Во всех вариантах, исключая cookie-хранилище, рельсы передают браузеру только ключ сессии, а сами данные хранит на сервере.
Предположим, что мы находимся в ситуации, когда все сервисы работают в некоем едином пространстве и могут быстро и легко обращаться к централизованному хранилищу. Например, наши сайты работают на нескольких серверах в пределах одного датацентра и могут обращаться к центральному серверу, отвечающему за авторизацию. Настроив сервисы таким образом, чтобы они хранили сессии на этом сервере, нам достаточно на каждом установить общий ключ сессии - и каждый сервис будет брать одну и ту же сессию с центрального сервера.
Давайте посмотрим поближе на техническую реализацию. Предположим, у нас есть два сервиса - Фотоальбом и Блоги, на которых мы хотим авторизоваться. Единая авторизация делается через Паспорт, который отвечает за регистрацию пользователей и хранение персональной информации. Условия задачи такие: пользователь заходит на Фотоальбом, авторизуется через Паспорт, а затем переходит на Блоги, где он должен авторизоваться автоматически.
Единая сессия стартуется на точке пересечения всех сервисов - Паспорте. Сессия стартуется, создается идентификатор, а затем по требованию копируется на другие сервисы. Выглядит это следующим образом:
Пользователь в первый раз входит на Фотоальбомы
Проверяется наличие ключа общей сессии, в связи с ее отсутствием пользователь перенаправляется на Паспорт
Паспорт стартует общую сессию, пользователь перенаправляется обратно на Фотоальбомы
Проверяется наличие общей сессии, для Фотоальбомов устанавливается общий ключ сессии
Для проверки наличия ключа и его установки на всех страницах сервиса Фотоальбомов в самом верху устраницы подключен Javascript, который генерируется Паспортом для каждого конкретного запроса. Если на Паспорте не запущена сессия, то Javascript перенаправляет пользователя на страницу, на которой сессия создается. Если сессия запущена, то Javascript говорит браузеру установить общий ключ сессии. Код примерно такой:
fucntion setSharedSession(key, value){
if(getCookie(key) != value) {
setCookie(key, value); // Устанавливаем cookie сессии
window.location = window.location; // Обновляем страницу
}
}
setSharedSession('_my_session_key', '83498234abc4586def90586abc');
Код Javascript-а выполняется уже в пространстве cookie Фотоальбомов, устанавливается сессионная кука и происходит обновление страницы. При обновлении запрос отправляется уже с новой сессией. Так как Javascript мы ставим в самом верху страницы, для пользователя все этим махинации практически незаметны - перенаправление на Паспорт, переход обратно на Фотоальбом и обновление страницы происходит практически незаметно для глаз.
На странице Блогов мы также устанавливаем Javascript, который проверяет сессию. После входа на Фотоальбомы у нас уже запущена сессия на Паспорте, поэтому Javascript просто выставляет общий ключ сессии для Блогов и обновляет страницу.
Если пользователь выходит из системы на Паспорте (кликает по ссылки “Выйти”, например), то мы удаляем текущую общую сессию. При загрузке любой страницы на любом из сервисов, подключенных к общей сессии, ключ сессии сохраняется, однако приложение не сможет получить информацию из несуществующей сессии и пользователь автоматически будет считаться неавторизованным.
Есть несколько тонких моментов, которые надо учитывать при использовании данной методики.
Выбор хранилища сессий. Из тех вариантов, которые есть в рельсах, для такого подхода удобнее всего использовать memcached - он очень легко масштабируется. Менее удобен, но все же приемлем метод хранения сессий в базе данных. Все остальные я исползовать не рекомендую. Само собой, у всех сервисов должны быть абсолютно идентичные настройки хранилища.
Сохранение объектов в сессию. Если вы в одном сервисе сохраните в сессию экземпляр класса, которого нет в другом сервисе, ваша психика может оказаться под угрозой :) Поэтому в сессию рекомендую сохранять только объекты базовых типов - строки, массивы, хэши.
Производительность. Так как волшебный Javascript подключается на всех страницах сервисов, количество его загрузок будет равно количеству загрузок всех остальных страниц. Поэтому такой скрипт я бы рекомендовал реализовать на чем-то более быстром, нежели рельсы или ruby как таковой. Как вариант можно использовать Perl или PHP, благо серверный функционал довольно прост - в зависимости от наличия куки возвращать редирект или инструкцию на установку куки.
Безопасность. Так как на сайте явно присутствует готовая функция для установки cookie, вам нужно быть предельно внимательными к вопросам защиты от XSS-атак. Фактически, любая дырка, в которую можно вставить инструкцию <script>, является для вас смертельно опасной. Чтобы устранить эту опасность можно использовать либо специальные плагины (например, xss_terminate), либо обработчики шаблонов со встроенной защитой (HAML, erubis).
Сфера применимости. Такая методика может быть применена при довольно жестких условиях, описанных в начале статьи. Поэтому стоит заранее задуматься, какова вероятность того, что ваше приложение выйдет за обозначенные рамки, и насколько быстро это произойдет. Кроме того, стоит задуматься, будет ли участвовать в процессе работы с сессиями стороннее ПО - десктопные программы, приложения на других языках, что-то еще. Если да, то я рекомендую смотреть в сторону более универсальных решений.
Ваше мнение?
No comments:
Post a Comment