C4 always sleepy

28Sep/094

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, :o 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: Возможно решение не идеальное, но с удовольствием рассмотрю все ваши рекомендации по его оптимизации :)

Comments (4) Trackbacks (0)
  1. Это решение подойдет имхо только для простейших ситуаций, и не является универсальным хотя бы потому, что ограничено соседством баз – а напряму лезть в базу из разных приложений – нехорошо. Да и просто два отдельных сервера(сайта) таким образом не соеденишь. Кроме этого, при такой структуре данных быстро возникнет проблема хранения пользовательских данных, особенно если приложения, которые используеют единую базу пользователей разнятся по смысловой нагрузке – выйдет просто каша. Сервер, являющийся центром авторизации пользователей, должен:
    1. Релизовать авторизацию через защищенное соединение
    2. Реализовать этот процесс в виде хотя бы простого протокола, которым может воспользоваться ЛЮБОЕ ваше периферийное приложение.
    3. неплохо бы еще обратную связь предусмотреть, на тот случай чтобы ВСЕ приложения смогли узнать, что сессия пользователя устарела.

  2. Согласен с доводами, но не со всем.

    >> просто два отдельных сервера(сайта) таким образом не соеденишь
    В чем проблема выделить отдельный сервер для баз данных + настроить коннекшны в нем на сервер БД?

    >> Кроме этого, при такой структуре данных быстро возникнет проблема хранения пользовательских данных, особенно если приложения, которые используеют единую базу пользователей разнятся по смысловой нагрузке – выйдет просто каша

    Ничто не мешает разделить модели User (используемый Authlogic) и SiteUser, используемый сайтом.

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

  3. я о том и говорю, что быстро приходим к внешнему протоколу, типа oauth. Конекшны через инет бросать напрямую к базе ? – я бы не стал.

  4. Я прорабатывал эту архитектуру как раз для сайтов в рамках одного портала. Сорри, что сразу не написал это уточнение. Про внешние сайты – отдельный разговор.


Leave a comment


Trackbacks are disabled.