Построение аутентификации для приложений — это запуск задачи, с которой мы все сталкивались в тот или иной момент. В прошлом большинство разработчиков заходили бы в свои пояса инструментов и вытягивали спокойную аутентификацию . В последнее время новый ребенок в блоке крадет гром, когда речь идет о проверке подлинности, и на то есть веские причины.
Devise предоставляет полное решение для аутентификации. Представления, почтовые программы и множество общих помощников по аутентификации, таких как регистрация, подтверждение по электронной почте и возможность блокировки учетных записей, входят в стандартную комплектацию.
Использование собственной системы аутентификации может быть хорошим упражнением, и в большинстве случаев это не слишком большая работа. Наличие чего-то стабильного, многофункционального и модульного, такого как Devise, может значительно сэкономить время.
Сегодня мы рассмотрим реализацию Devise в реальном сценарии. Хотя базовые функции Devise хорошо известны, его гибкость несколько упускается из виду. Мы рассмотрим настройку входа пользователей.
Обычная идиома в мультитенантных приложениях в наши дни состоит в том, чтобы субдомены определялись как учетная запись пользователя, например rubysource.basecamp.com
Account
Давайте сделаем это с Devise.
Приложение
Сначала мы начнем с сути нашего приложения. Это будет приложение для заметок на основе Rails 3.1. На этом этапе мы сделаем это по-настоящему простым, и в нашем домене будет только три модели: Note
User
Account
Давайте проверим несколько генераторов для Note
User
rails g model Account subdomain:string
rails g scaffold Note title:string content:text user_id:integer account_id:integer
Account
В нашей модели class Account < ActiveRecord::Base
has_many :users
has_many :notes, :through => :users
end
Note
Аналогично для class Note > ActiveRecord::Base
belongs_to :user
belongs_to :account
end
ActiveRecord
Ничего особенного, просто пара моделей subdomain
Единственный интересный Account
gem devise
bundle install
По сути, это слаг для учетной записи в URL запроса («rubysource» в нашем предыдущем примере).
Представляем Devise
Devise — это драгоценный камень, поэтому его очень просто установить. Добавьте Devise в ваш Gemfile, rails g
Devise:
devise
devise:form_for
devise:install
devise:shared_views
devise:simple_form_for
devise:views
Теперь в командной строке запустите rails g devise:install
notes#index
Мы будем использовать очевидные направления, rails g devise User
После запуска генератора мы должны получить пару инструкций, говорящих нам о том, чтобы установить корневой каталог приложения для отображения, иметь область флэш-памяти в нашей компоновке и установить почтовую программу по умолчанию в наших средах разработки и тестирования. Просто следуйте за этим к письму. Я установил корень приложения, чтобы он указывал на devise_for
Нам нужна пользовательская модель для аутентификации Devise. Как выяснилось, мы можем использовать devise для генерации того, что для нас config/routes.rb
Это генерирует модель, соответствующую миграцию и специализированный маршрут User
Account
Перед запуском миграций нам нужно изменить модель Note
has_many
Мы также ассоциируем :confirmable
before_filter :authenticate_user!
Я должен отметить, что это лучшее время, чтобы решить, какие модули Devise будет использовать, такие как добавление ApplicationController
С установленным Devise, самый простой способ защитить наш контент — использовать одеяло localhost:3000
помещается в lvh.me
При запуске сервера и переходе к www.example.com
Зная это работает
Теперь у нас есть Devise, он пока не делает то, что нам нужно. Нам нужно пройти аутентификацию на поддомене. Есть несколько вариантов с точки зрения тестирования. Правда, мы не очень хотим тестировать сам Devise. Это стороннее, хорошо используемое программное обеспечение, поэтому справедливо сказать, что оно было хорошо протестировано. Тем не менее, мы все еще хотим, чтобы наши настройки работали так, как мы ожидаем. Мы могли бы поработать, запустив локальный сервер и используя блестящий домен app_host
Но разве мы не лучше этого? Мы хотим воспроизводимых, автоматических тестов. Давайте использовать наши интеграционные тесты и потрясающую версию Capybara . По умолчанию Capybara запускает наш пакет под доменом describe "LoginToAccounts" do
before do
other_account = Factory.create(:account)
@invalid_user = Factory.create(:user, account: other_account)
@account = Factory.create(:account, subdomain: "test-account")
@user = Factory.create(:user, account: @account)
Capybara.app_host = "http://test-account.example.com"
visit '/'
end
describe "log in to a valid account" do
before do
fill_in 'Email', with: @user.email
fill_in 'Password', with: @user.password
click_button 'Sign in'
end
it "will notify me that I have logged in successfully" do
page.should have_content "Signed in successfully"
end
end
describe "fail login for valid user wrong account" do
before do
fill_in 'Email', with: @invalid_user.email
fill_in 'Password', with: @invalid_user.password
click_button 'Sign in'
end
it "will not notify me that I have logged in successfully" do
page.should_not have_content "Signed in successfully"
end
end
end Однако, просто используя помощник devise.rb
Вот базовый тест поведения, которое мы ищем, используя запросы RSpec .
config/initializers
Придумать гибкость
Теперь, когда у нас есть тест, пришло время реализовать функцию захвата поддоменов и проверки подлинности пользователя в правильной учетной записи.
Когда мы установили Devise, он сгенерировал инициализатор, метко названный :subdomain
User
Account
Этот файл блестяще прокомментирован и должен быть первым пунктом вызова, когда вы хотите добавить функциональность в стандартную установку Devise.
В строке 33 этого файла (на основе установки версии 1.4.7 Devise) вы увидите, что мы можем добавить ключи запроса для аутентификации. Просто раскомментируйте строку и добавьте find_for_authentication
В основном, этот параметр запроса будет передан в метод аутентификации Devises для проверки.
Хотя это было достаточно просто, мы еще не совсем там. Помните, что Devise аутентифицируется на User
def self.find_for_authentication(conditions={})
Теперь мы можем просто добавить атрибут subdomain к пользователю и все готово, но мне это кажется просто неправильным.
conditions[:account_id] = Account.find_by_subdomain(conditions.delete(:subdomain)).id
super(conditions)
end
Вместо этого мы переопределим метод subdomain
Этот метод является нашей точкой отсчета до того, как Devise вступит во владение. Чтобы найти учетную запись, которую мы ищем, мы возьмем субдомен из запроса и передадим его по цепочке для аутентификации. Использование Rails 3+ делает это действительно легко. В модели Account
find_for_authentication
Здесь мы end
do … end
{}
bundle install
Со всем этим мы можем запустить тесты и убедиться, что у нас есть проходной пакет. Демо-код можно найти на Github .
Куда дальше?
Наше маленькое приложение имеет аутентификацию, и никакие неавторизованные пользователи не могут получить доступ к нашим важным заметкам. У нас еще есть над чем поработать, какие записи отображаются для какого логина. Это решается с помощью общей мультитенантной идиомы: все наши данные в контроллере извлекаются из «текущего арендатора», как это делает DHH .
Это не совсем то, на что мы смотрели сегодня. Обычно я бы избегал тестирования стороннего кода, но поведение нашего приложения и внесенные нами изменения требуют, чтобы мы хотя бы склонили голову перед Devise. Оборачивать это как интеграционный тест — лучшее место для этого, и использование такого инструмента, как Capybara, облегчает его, поэтому было бы преступно не быть тщательным.
Аутентификация — это шаблон, который мы будем использовать снова и снова, когда речь заходит о приложениях на Rails, и такие сложные функции, как Devise, могут быть пугающими. Следовательно, почему некоторые обращаются к более простым драгоценным камням, таким как Restful Authentication , Sorcery или просто катят наши собственные. Я рекомендую вам потратить некоторое время на разработку. При хорошем понимании вы можете почти списать элемент аутентификации из вашего приложения и использовать Devise в качестве замены. Его дизайн настолько хорош, что лично мне еще предстоит оказаться в ситуации, когда я сражаюсь против Devise. Обычно это тот случай, когда я пытаюсь создать именно то, что мне нужно.