SSO на Rails с использованием Authlogic
Вопрос SSO для многих актуален, различных решений великое множество (OpenId, Oauth, CAS итд.), но иногда хочется сделать все по своему. Давайте попробуем написать свой SSO для наших сайтов?
Давайте для начала определим, что нам необходимо сделать:
1. Разработать центральный сервис аутентификации
2. Разработать сайт, который будет использовать SSO
2. Обеспечить доступность идентификатора сессии в рамках всех наших сайтов
3. Обеспечить доступность сессионных данных
4. Обеспечить доступность пользовательских данных
Давайте приступим к процессу разработки.
1. Первым делом нам необходимо реализовать сам сервис аутентификации. Про это уже много написано, например ознакомиться можно в соответствующем railscast или asciicast.
2. Думаю тут я могу обойтись без лишних комментариев, единственное что вам необходимо селать - это включить authlogic, создать модели User и UserSession:
class User < ActiveRecord::Base
establish_connection "auth_#{RAILS_ENV}"
acts_as_authentic
end
(на строку establish_connection пока не обращаем внимания - мы к ней еще подойдем
)
class UserSession < Authlogic::Session::Base
end
(обратите внимание на сложность данного класса
)
а также дополним ApplicationController базовым набором методов
private
def current_user_session
return @current_user_session if defined?(@current_user_session)
@current_user_session = UserSession.find
end
def current_user
return @current_user if defined?(@current_user)
@current_user = current_user_session && current_user_session.record
end
def require_user
unless current_user
store_location
flash[:notice] = I18n.t(:"notices.require_logged_in")
redirect_to login_url
return false
end
end
def require_no_user
if current_user
store_location
flash[:notice] = I18n.t(:"notices.require_logged_out")
redirect_to dashboard_url
return false
end
end
# Store the URI of the current request in the session.
#
# We can return to this location by calling #redirect_back_or_default.
def store_location
session[:return_to] = request.request_uri
end
# Redirect to the URI stored by the most recent store_location call or
# to the passed default. Set an appropriately modified
# after_filter :store_location,
nly => [:index, :new, :show, :edit]
# for any controller you want to be bounce-backable.
def redirect_back_or_default(default)
redirect_to(session[:return_to] || default)
session[:return_to] = nil
end
2. Идентификатор сессии будем хранить в cookies. Для того, чтобы он был доступен для всех сайтов на домене *.example.com (auth.example.com, site1.exampe.com итд) нам по идее достаточно просто написать
ActionController::Base.session = {
:domain => ".example.org"
}
но знающие люди рекомендуют такой вот замечательный gist. Сохраняем его как файл
session_domain.rb в conf/initializers/, а также вносим небольшие изменения в session_store.rb:
ActionController::Base.session = {
:key => '_example_session',
:secret => 'oursupersecretkey'
}
Самое время перейти к следующему пункту
3. Не закрывая session_store.rb продолжаем вносить в него изменения:
ActionController::Base.session_store = :active_record_store
Для сайтов, которые будут использовать интерфейс SSO (а также если вы захотите вынести хранилище сессий в отдельную БД) порписываем в этом же файле
# Use sessions from preconfigured session store
ActiveRecord::SessionStore::Session.establish_connection("sessions_#{RAILS_ENV}")
а в файле database.yml:
# Sessions stores
sessions_development:
adapter: sqlite3
database: ../auth/db/development.sqlite3
pool: 5
timeout: 5000
sessions_test:
adapter: sqlite3
database: ../auth/db/test.sqlite3
pool: 5
timeout: 5000
sessions_production:
adapter: sqlite3
database: ../auth/db/production.sqlite3
pool: 5
timeout: 5000
4. Помните пару абзацев назад я просил не обращать внимания на строчку кода? Самое время к ней вернуться. Класс User будет использовать соединение с БД нашего сервиса аутентификации для получения информации о текущем пользователе. Обновим данные в database.yml:
auth_development:
adapter: sqlite3
database: ../auth/db/development.sqlite3
pool: 5
timeout: 5000
auth_test:
adapter: sqlite3
database: ../auth/db/test.sqlite3
pool: 5
timeout: 5000
auth_production:
adapter: sqlite3
database: ../auth/db/production.sqlite3
pool: 5
timeout: 5000
Вот в принципе и все
Не сложно, правда? Теперь на site1.example.com мы спокойно сможем работать с current_user и пользовательской сессией.
PS: Возможно решение не идеальное, но с удовольствием рассмотрю все ваши рекомендации по его оптимизации



September 30th, 2009 - 22:50
Это решение подойдет имхо только для простейших ситуаций, и не является универсальным хотя бы потому, что ограничено соседством баз – а напряму лезть в базу из разных приложений – нехорошо. Да и просто два отдельных сервера(сайта) таким образом не соеденишь. Кроме этого, при такой структуре данных быстро возникнет проблема хранения пользовательских данных, особенно если приложения, которые используеют единую базу пользователей разнятся по смысловой нагрузке – выйдет просто каша. Сервер, являющийся центром авторизации пользователей, должен:
1. Релизовать авторизацию через защищенное соединение
2. Реализовать этот процесс в виде хотя бы простого протокола, которым может воспользоваться ЛЮБОЕ ваше периферийное приложение.
3. неплохо бы еще обратную связь предусмотреть, на тот случай чтобы ВСЕ приложения смогли узнать, что сессия пользователя устарела.
October 1st, 2009 - 10:37
Согласен с доводами, но не со всем.
>> просто два отдельных сервера(сайта) таким образом не соеденишь
В чем проблема выделить отдельный сервер для баз данных + настроить коннекшны в нем на сервер БД?
>> Кроме этого, при такой структуре данных быстро возникнет проблема хранения пользовательских данных, особенно если приложения, которые используеют единую базу пользователей разнятся по смысловой нагрузке – выйдет просто каша
Ничто не мешает разделить модели User (используемый Authlogic) и SiteUser, используемый сайтом.
А для остальных сайтов (от других производителей и не только
) можно реализовать протокол Oauth и использовать его в полной мере
October 1st, 2009 - 15:15
я о том и говорю, что быстро приходим к внешнему протоколу, типа oauth. Конекшны через инет бросать напрямую к базе ? – я бы не стал.
October 1st, 2009 - 15:30
Я прорабатывал эту архитектуру как раз для сайтов в рамках одного портала. Сорри, что сразу не написал это уточнение. Про внешние сайты – отдельный разговор.