Статьи

Магическая Аутентификация с Волшебством

Волшебная Шляпа

Аутентификация является важной частью многих веб-приложений. Мне не нужно убеждать вас, что аутентификация должна быть простой в использовании и в то же время хорошо защищенной. Вы можете написать его с нуля или использовать один из множества драгоценных камней, доступных там.

Эта статья является первой в следующей серии, посвященной аутентификации в Rails. Мы рассмотрим Sorcery , менее известный, но очень удобный и простой в использовании драгоценный камень, созданный Ноамом Бен-Ари и другими людьми. По сравнению с Devise , Sorcery немного более низкого уровня и требует от разработчика выполнения некоторых дополнительных действий. Однако это хорошо, потому что вы можете выбрать только необходимую функциональность.

Дополнительные опции добавляются с помощью субмодулей. Изначально, Sorcery предоставляет только самый минимальный набор функций . Существуют субмодули для активации пользователя, защиты от перебора, поддержки OAuth 2 и многого другого. Вам решать, что нужно вашему приложению. «Меньше значит больше» — один из главных принципов колдовства.

Я надеюсь, что вы взволнованы, чтобы познакомиться с Волшебством лучше. 🙂 Читайте дальше и давайте вместе создадим демонстрационное приложение!

Исходный код доступен на GitHub .

Рабочая демоверсия доступна по адресу sitepoint-sorcery.herokuapp.com .

Подготовка приложения

Я собираюсь назвать мое демо-приложение «Волшебным» — мы интегрируем магическую аутентификацию в конце концов:

$ rails new Magical -T 

Будет использоваться Rails 4.2.0, но такое же решение может быть реализовано с помощью Rails 3. Это демонстрационное приложение не будет предоставлять никаких функций, кроме аутентификации и связанных с ней функций, однако нам нужна хотя бы одна страница для целей тестирования, которая должна быть доступна только для аутентифицированных пользователей. пользователи, поэтому создайте базовый PagesController :

pages_controller.rb

 class PagesController < ApplicationController def index end end 

соответствующий вид:

просмотров / страниц / index.html.erb

 <h1>Welcome!</h1> <p>Restricted area for authorized users only.</p> 

и добавьте маршруты:

конфиг / routes.rb

 [...] get '/secret', to: 'pages#index', as: :secret root to: 'pages#index' [...] 

Я также собираюсь использовать Twitter Bootstrap, чтобы немного стилизовать приложение:

Gemfile

 [...] gem 'bootstrap-sass', '~> 3.3.3' [...] 

application.scss

 @import "bootstrap-sprockets"; @import "bootstrap"; @import "bootstrap/theme"; .nav > li > span { display: block; padding-top: 15px; padding-bottom: 15px; color: #9d9d9d; } 

просмотров / макеты / application.html.erb

 [...] <nav class="navbar navbar-inverse"> <div class="container"> <div class="navbar-header"> <%= link_to 'Magical', root_path, class: 'navbar-brand' %> </div> <div id="navbar"> <ul class="nav navbar-nav"> <li><%= link_to 'Secret Page', secret_path %></li> </ul> </div> </div> </nav> <div class="container"> <% flash.each do |key, value| %> <div class="alert alert-<%= key %>"> <%= value %> </div> <% end %> <%= yield %> </div> [...] 

Хорошо, приготовления сделаны … это было быстро, не так ли? Теперь давайте интегрируем Волшебство в приложение!

Интеграция волшебства

Модель и миграции

Прежде всего, поместите драгоценный камень в свой Gemfile :

Gemfile

 [...] gem 'sorcery' [...] 

и беги

 $ bundle install 

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

Нам нужно создать конфигурацию и миграцию Sorcery. Изначально Sorcery предоставляет только самые основные функции — саму аутентификацию. Никакой защиты от перебора, никакого «запомни меня», никакого «восстановления пароля», даже проверки подлинности электронной почты. Это, однако, означает, что вы можете выбрать только те функции, которые вам действительно нужны.

Продолжайте и выполните команду:

 $ rails g sorcery:install 

Это создаст файл config / initializers / sorcery.rb , модель User и миграцию. Пользователь получает три поля от Волшебства:

  • crypted_password ( string )
  • salt ( string )
  • email ( string )

Если вы хотите указать другое имя для модели, --model флаг --model :

 $ rails g sorcery:install --model Admin 

Откройте только что созданный файл миграции и добавьте следующую строку в метод create_table :

xxx_sorcery_core.rb

 [...] t.string :name [...] 

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

 $ rake db:migrate 

На модели. Если вы откроете файл models / user.rb , вы заметите, что

модели / user.rb

 [...] authenticates_with_sorcery! [...] 

Линия присутствует там. Это добавляет некоторые методы Волшебства к модели. Здесь мы должны добавить несколько проверок, потому что изначально они отсутствуют. Это означает, что может быть предоставлено любое электронное письмо и пароль (включая пустое)

модели / user.rb

 [...] validates :password, length: { minimum: 3 } validates :password, confirmation: true validates :email, uniqueness: true [...] 

Я также требую минимальную длину пароля, требую подтверждения и обеспечения уникальности электронной почты.

Как насчет формата электронной почты? Вы можете написать свое собственное регулярное выражение, но кажется, что это регулярное выражение безумно долго . Вместо этого возьмите существующий драгоценный камень, чтобы решить эту проблему:

Gemfile

 [...] gem 'validates_email_format_of' [...] 

Не забудь бежать

 $ bundle install 

Затем измените модель следующим образом:

модели / user.rb

 [...] validates :email, uniqueness: true, email_format: { message: 'has invalid format' } [...] 

Теперь мы можем быть уверены, что электронная почта имеет правильный формат. Самое время перейти к контроллерам.

Зарегистрироваться

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

Начнем с регистрации. Вызовите этот контроллер UsersController :

users_controller.rb

 class UsersController < ApplicationController def new @user = User.new end def create @user = User.new(user_params) if @user.save flash[:success] = 'Welcome!' redirect_to root_path else render 'new' end end private def user_params params.require(:user).permit(:email, :password, :password_confirmation, :name) end end 

Вид:

просмотров / пользователей / new.html.erb

 <h1>Registration</h1> <%= form_for @user do |f| %> <%= render 'shared/errors', object: @user %> <div class="form-group"> <%= f.label :name %> <%= f.text_field :name, class: 'form-control', required: true %> </div> <div class="form-group"> <%= f.label :email %> <%= f.email_field :email, class: 'form-control', required: true %> </div> <div class="form-group"> <%= f.label :password %> <%= f.password_field :password, class: 'form-control', required: true %> </div> <div class="form-group"> <%= f.label :password_confirmation %> <%= f.password_field :password_confirmation, class: 'form-control', required: true %> </div> <%= f.submit 'Register', class: 'btn btn-primary btn-lg' %> <% end %> 

Колдовство использует гем bcrypt-ruby для защиты паролей. Это означает, что пароль никогда не сохраняется в виде простого текста — в базе данных присутствует только их дайджест (в поле crypted_password ). Следовательно, password и password_confirmation являются виртуальными атрибутами без соответствующих полей таблицы.

Возможно, вы также заметили поле salt — это случайная строка, используемая для дополнительной защиты пароля. Видите ли, дайджесты, сгенерированные хэш-функциями , практически невозможно инвертировать и восстановить в исходное сообщение. Однако можно создать словарь слов и соответствующие дайджесты. Поскольку одно и то же сообщение всегда имеет один и тот же дайджест, злоумышленник, получивший доступ к базе данных, может искать дайджест в словаре и искать соответствующую строку. Соль предназначена для предотвращения таких атак. Он генерируется, когда пользователь впервые создается и используется в процессе хеширования следующим образом:

 hash(salt + password) 

Невозможно построить словарь для каждой уникальной соли, поэтому пароль становится действительно защищенным. Вы можете прочитать больше здесь .

Кстати, вы можете использовать другие алгоритмы шифрования , переопределив user.encryption_algorithm = внутри файла config / initializers / sorcery.rb .

Давайте добавим маршруты:

конфиг / routes.rb

 [...] resources :users, only: [:new, :create] get '/sign_up', to: 'users#new', as: :sign_up [...] 

Хорошо, теперь пользователи могут зарегистрироваться, но они не будут авторизованы по умолчанию. Для этого можно использовать метод login в login который принимает как минимум два аргумента: адрес электронной почты и пароль. Добавьте его в контроллер:

users_controller.rb

 [...] def create @user = User.new(user_params) if @user.save login(params[:user][:email], params[:user][:password]) [...] 

Попробуйте зарегистрироваться … все должно работать нормально.

Войти и выйти

Следующим шагом является реализация функциональности для входа и выхода. Создать новый
SessionsController :

sessions_controller.rb

 class SessionsController < ApplicationController def new end def create if login(params[:email], params[:password]) flash[:success] = 'Welcome back!' redirect_to root_path else flash.now[:warning] = 'E-mail and/or password is incorrect.' render 'new' end end def destroy logout flash[:success] = 'See you!' redirect_to log_in_path end end 

Тот же метод login используется в действии create . Если пользователь указал неверный адрес электронной почты или пароль, этот метод возвращает nil , поэтому будет отображено сообщение об ошибке. Метод logout внутри действия destroy делает почти то же, что и он, — выходит из системы пользователя.

Теперь мнение:

просмотры / сессия / new.html.erb

 <h1>Log In</h1> <%= form_tag sessions_path, method: :post do %> <div class="form-group"> <%= label_tag :email %> <%= email_field_tag :email, nil, class: 'form-control', required: true %> </div> <div class="form-group"> <%= label_tag :password %> <%= password_field_tag :password, nil, class: 'form-control', required: true %> </div> <%= submit_tag 'Log In', class: 'btn btn-primary btn-lg' %> <% end %> 

И маршруты:

конфиг / routes.rb

 [...] resources :sessions, only: [:new, :create, :destroy] get '/log_in', to: 'sessions#new', as: :log_in delete '/log_out', to: 'sessions#destroy', as: :log_out [...] 

После этого пришло время настроить макет для отображения всех необходимых ссылок.

Отображение ссылок

Мы хотим предоставить ссылки «Зарегистрироваться» и «Войти» пользователям, которые в настоящее время не проходят проверку подлинности. Ссылки «Выход» и «Секретная страница» должны отображаться после входа в систему. Чтобы проверить, аутентифицирован ли пользователь или нет, используйте метод current_user , который возвращает запись пользователя или nil :

просмотров / макеты / application.html.erb

 [...] <div id="navbar"> <ul class="nav navbar-nav"> <% if current_user %> <li><%= link_to 'Secret Page', secret_path %></li> <% else %> <li><%= link_to 'Sign Up', sign_up_path %></li> <li><%= link_to 'Log In', log_in_path %></li> <% end %> </ul> <% if current_user %> <ul class="nav navbar-nav pull-right"> <li><span><%= current_user.name %></span></li> <li><%= link_to 'Log Out', log_out_path, method: :delete %></li> </ul> <% end %> </div> [...] 

Это довольно просто, не так ли? Однако к секретной странице все еще можно получить доступ, просто набрав ее URL-адрес. Что нам нужно сделать, это добавить какую-то проверку в контроллер страницы.

Ограничение доступа

К счастью, Sorcery предоставляет метод require_login который ограничивает определенные страницы от неавторизованных пользователей. Он должен использоваться как before_action так:

application_controller.rb

 [...] before_action :require_login [...] 

Однако это приведет к ограничению доступа ко всем страницам. Очевидно, что пользователи, которые не вошли в систему, должны иметь возможность посещать страницы входа и регистрации. Таким образом, используйте skip_before_action :

users_controller.rb

 [...] skip_before_action :require_login, only: [:new, :create] [...] 

sessions_controller.rb

 [...] skip_before_action :require_login, except: [:destroy] [...] 

Как насчет действий, которые происходят, когда неаутентифицированный пользователь пытается открыть запрещенную страницу? Откройте файл инициализатора Sorcery:

конфиг / Инициализаторы / sorcery.rb

 [...] # What controller action to call for non-authenticated users. You can also # override the 'not_authenticated' method of course. # Default: `:not_authenticated` # # config.not_authenticated_action = [...] 

Метод not_authenticated — это то, что нам нужно. Создать это:

application_controller.rb

 [...] private def not_authenticated flash[:warning] = 'You have to authenticate to access this page.' redirect_to log_in_path end [...] 

Есть еще одно небольшое улучшение, которое можно добавить. В настоящее время, когда неаутентифицированный пользователь открывает запрещенную страницу, он будет перенаправлен на страницу входа. После входа они попадают на главную страницу сайта. Это не очень удобно. Было бы лучше перенаправить пользователя на страницу, которую он пытался увидеть. Встречайте метод redirect_back_or_to . Этот метод либо перенаправит пользователя обратно туда, откуда он пришел, либо на указанную страницу:

sessions_controller.rb

 [...] def create if login(params[:email], params[:password]) flash[:success] = 'Welcome back!' redirect_back_or_to root_path [...] 

Ты еще помнишь меня?

Вы, вероятно, привыкли к маленькому флажку «Запомнить меня» на страницах входа. Как насчет добавления его в наше приложение?

Мы можем воспользоваться подмодулями Волшебства для этой задачи. Каждый подмодуль обеспечивает свою собственную функциональность и может быть подключен независимо. Думайте о них как о блоках Lego.

Подмодуль, который нас сейчас интересует, называется RememberMe . Выполните следующую команду, чтобы скопировать необходимую миграцию:

 $ rails g sorcery:install remember_me --only-submodules 

В некоторых документах вы можете найти флаг --migrations вместо --only-submodules но последний предпочтительнее, так как --migrations устарел. Эта миграция добавит два столбца в таблицу users :

  • remember_me_token ( string )
  • remember_me_token_expires_at ( datetime )

Этот токен используется для «запоминания» пользователя и, очевидно, он не должен быть действителен вечно. Применить миграцию:

 $ rake db:migrate 

Теперь зарегистрируйте новый подмодуль:

конфиг / Инициализаторы / sorcery.rb

 [...] Rails.application.config.sorcery.submodules = [:remember_me] [...] 

Если вы хотите настроить длительность токена, user.remember_me_for параметр user.remember_me_for в файле sorcery.rb . Значение по умолчанию составляет одну неделю.

Добавьте флажок в форму входа в систему:

просмотры / сессия / new.html.erb

 [...] <%= form_tag sessions_path, method: :post do %> [...] <div class="form-group"> <%= label_tag :remember_me %> <%= check_box_tag :remember_me %> </div> [...] <% end %> 

Наконец, настройте соответствующий метод контроллера:

sessions_controller.rb

 [...] def create if login(params[:email], params[:password], params[:remember_me]) flash[:success] = 'Welcome back!' redirect_back_or_to root_path else flash.now[:warning] = 'E-mail and/or password is incorrect.' render 'new' end end [...] 

Метод login также может принимать третий необязательный параметр, определяющий, следует ли запомнить пользователя или нет.

Активируй себя!

На многих веб-сайтах пользователь должен активировать свою учетную запись, посетив ссылку, отправленную ему по электронной почте, прежде чем войти в систему. Давайте реализуем эту функцию. Установите новый подмодуль:

 $ rails g sorcery:install user_activation --only-submodules 

и применить миграцию:

 $ rake db:migrate 

Это собирается добавить следующие поля:

  • activation_state ( string )
  • activation_token ( string )
  • activation_token_expires_at ( datetime )

Зарегистрировать субмодуль:

конфиг / Инициализаторы / sorcery.rb

 [...] Rails.application.config.sorcery.submodules = [:user_activation] [...] 

Также настройте параметры:

конфиг / Инициализаторы / sorcery.rb

 [...] user.user_activation_mailer = UserMailer [...] 

Также стоит упомянуть пару настроек:

  • user.activation_mailer_disabled — если установлено значение true , электронное письмо со ссылкой активации не будет отправлено автоматически, что позволит вам решить, когда его отправлять. Значением по умолчанию является false .
  • prevent_non_active_users_to_login — должны ли неактивированные пользователи иметь возможность войти в систему. По умолчанию установлено значение false .

Сгенерируйте почтовую программу для обработки отправки электронной почты:

 $ rails g mailer UserMailer activation_needed_email activation_success_email 

Удалите все файлы .text.erb из каталога раскладок и user_mailer . Теперь измените почтовик так:

отправители / user_mailer.rb

 class UserMailer < ApplicationMailer def activation_needed_email(user) @user = user mail(to: user.email, subject: "Account activation") end def activation_success_email(user) @user = user mail(to: user.email, subject: "Your account is now activated") end end 

Как видите, волшебство требует двух методов:

  • activation_needed_email отправляет электронное письмо со ссылкой для активации
  • activation_success_email отправляет электронное письмо с подтверждением о том, что учетная запись была успешно активирована.

На самом деле, вы можете отключить отправку «успешного» электронного письма, установив Activation_success_email_method_name в sorcery.rb на nil .

Виды:

просмотров / user_mailer / activation_needed_email.html.erb

 <p>Welcome, <%= @user.name %>!</p> <p>To login to the site, just follow <%= link_to 'this link', activate_user_url(@user.activation_token) %>.</p> <p>Thanks for joining and have a great day!</p> 

просмотров / user_mailer / activation_success_email.html.erb

 <p>Congratulations, <%= @user.name %>!</p> <p>You have successfully activated your account.</p> <p>You may now proceed to <%= link_to 'log in page', log_in_url %>.</p> <p>Thanks for joining and have a great day!</p> 

Для работы помощников по ссылкам нам потребуется выполнить некоторую настройку:

конфигурации / среда / development.rb

 [...] config.action_mailer.default_url_options = { host: '127.0.0.1:3000' } [...] 

Обратите внимание, что электронные письма фактически не будут отправляться в процессе разработки — вы сможете видеть только их содержимое и метаинформацию в консоли. Установите config.action_mailer.perform_deliveries в true в development.rb, чтобы изменить это поведение.

Для производства вам нужно будет настроить параметры SMTP. Некоторые примеры можно найти здесь . В моем демонстрационном приложении я собираюсь отключить активацию пользователя.

Чтобы завершить этот шаг, мы должны добавить метод контроллера и маршрут:

конфиг / routes.rb

 [...] resources :users, only: [:new, :create] do member do get :activate end end [...] 

users_controller.rb

 [...] skip_before_action :require_login def activate if @user = User.load_from_activation_token(params[:id]) @user.activate! flash[:success] = 'User was successfully activated.' redirect_to log_in_path else flash[:warning] = 'Cannot activate this user.' redirect_to root_path end end [...] 

load_from_activation_token — это метод, представленный Sorcery, который находит ресурс по токену активации. activate! фактически активирует учетную запись и сохраняет результат в базе данных.

Потрясающие! Теперь вы можете проверить, как все это работает.

Интеграция с DelayedJob

Вы, вероятно, заметили, что отправка электронного письма занимает некоторое время, и страница не будет загружена, пока эта операция не будет завершена. Это не очень удобно для пользователя, так как отправка электронного письма может занять много времени. Чтобы это исправить, процесс отправки электронной почты должен быть асинхронным.

Для достижения этой цели можно использовать DelayedJob от CollectiveIdea.

Вставьте новый драгоценный камень:

Gemfile

 [...] gem 'delayed_job_active_record' # or gem 'delayed_job_mongoid' [...] 

и беги

 $ bundle install 

Создайте и примените проблему миграции DelayedJob:

 $ rails generate delayed_job:active_record $ rake db:migrate 

Кроме того, добавьте следующую строку в конфигурацию приложения, чтобы установить бэкенд очереди:

конфиг / application.rb

 [...] config.active_job.queue_adapter = :delayed_job [...] 

Это должно быть сделано только для Rails 4.2+.

Наконец, поместите следующий код в конец файла инициализатора Sorcery:

конфиг / Инициализаторы / sorcery.rb

 [...] module Sorcery module Model module InstanceMethods def generic_send_email(method, mailer) config = sorcery_config mail = config.send(mailer).delay.send(config.send(method), self) end end end end 

Отправка электронной почты теперь выполняется асинхронно, ура!

Чтобы проверить это на локальной машине, запустите

 $ rake jobs:work 

так что DelayedJob начинает обработку заданий и загружает сервер, выпуская

 $ rails s 

в отдельной вкладке консоли. Затем зарегистрируйте нового пользователя и перейдите на вкладку консоли DelayedJob. Вы увидите что-то вроде

 [Worker(host:xxx pid:7128)] Job UserMailer.send (id=1) RUNNING [Worker(host:xxx pid:7128)] Job UserMailer.send (id=1) COMPLETED after 2.2951 [Worker(host:xxx pid:7128)] 1 jobs processed at 0.4057 j/s, 0 failed 

Это означает, что интеграция была успешно завершена.

Защита от грубой силы

Грубая сила , вероятно, самый широко известный тип атаки. По сути, злоумышленник пытается «угадать» пароль, пробуя одну комбинацию символов за другой. Для обеспечения безопасности учетная запись должна быть заблокирована после некоторых неудачных попыток входа в систему.

Добавить еще один подмодуль Волшебства:

 $ rails g sorcery:install brute_force_protection --only-submodules 

Это генерирует миграцию, которая собирается добавить эти столбцы:

  • failed_logins_count ( integer ) — сколько раз подряд пользователь делал неудачные попытки входа в систему
  • lock_expires_at (по default ) — когда учетная запись будет разблокирована (если она заблокирована)
  • unlock_token ( string ) — случайный токен для разблокировки аккаунта

Применить миграцию:

 $ rake db:migrate 

Зарегистрируйте новый модуль:

конфиг / Инициализаторы / sorcery.rb

 [...] Rails.application.config.sorcery.submodules = [:brute_force_protection] [...] 

Вот и все. Колдовство позаботится обо всем остальном, но вы можете настроить некоторые параметры, такие как:

  • user.consecutive_login_retries_amount_limit — сколько неудачных попыток разрешено. По умолчанию 50.
  • user.login_lock_time_period — как долго пользователь должен быть заблокирован. По умолчанию это 3600 секунд. Укажите 0, чтобы заблокировать пользователя на неопределенный срок.
  • user.unlock_token_mailer и user.unlock_token_email_method_name — класс и метод для отправки электронных писем пользователям с токенами разблокировки (по умолчанию они не настроены).

Если вы хотите позволить пользователю разблокировать свою учетную запись, кроме создания почтовой программы, вам потребуется отдельный контроллер ( ResetPasswordsController , ResetPasswordsController ) с методом, подобным этому:

 [...] def create u = User.load_from_unlock_token(params[:token]) u.unlock! flash[:success] = 'Your account was unlocked!' redirect_to root_url end [...] 

load_from_unlock_token — это метод, предоставляемый Sorcery, который ищет пользователя по предоставленному токену разблокировки. unlock! В свою очередь снимает блокировку. ! в конце имени метода говорится, что пользователь будет немедленно сохранен, поэтому вам не нужно вызывать u.save .

Войти активность

Ведение журнала активности — это подмодуль, который помогает реализовать такие функции, как «кто сейчас в сети». Ты знаешь что делать:

 $ rails g sorcery:install activity_logging --only-submodules 

это добавит следующие поля:

  • last_login_at ( datetime )
  • last_logout_at ( datetime )
  • last_activity_at ( datetime )
  • last_login_from_ip_address ( string )

Применить миграцию:

 $ rake db:migrate 

Зарегистрировать субмодуль:

конфиг / Инициализаторы / sorcery.rb

 [...] Rails.application.config.sorcery.submodules = [:activity_logging] [...] 

Нам нужен метод, который возвращает активных в данный момент пользователей. Волшебство использовалось для предоставления метода current_users , но было решено удалить его . Поэтому давайте напишем нашу собственную версию:

application_controller.rb

 [...] private def current_users User.current_users end helper_method :current_users [...] 

user.rb

 [...] class << self def current_users where("#{sorcery_config.last_activity_at_attribute_name} IS NOT NULL") \ .where("#{sorcery_config.last_logout_at_attribute_name} IS NULL OR #{sorcery_config.last_activity_at_attribute_name} > #{sorcery_config.last_logout_at_attribute_name}") \ .where("#{sorcery_config.last_activity_at_attribute_name} > ? ", sorcery_config.activity_timeout.seconds.ago.utc.to_s(:db)) end end [...] 

Есть и другие примеры на вики-волшебстве.

Теперь просто используйте метод current_users для отображения имен пользователей:

просмотров / макеты / application.html.erb

 [...] <div class="col-sm-9"> <%= yield %> </div> <%= render 'shared/current_users' %> [...] 

просмотров / общий / _current_users.html.erb

 <div class="col-sm-3 well well-sm"> <h3>Currently active users:</h3> <ul> <% current_users.each do |user| %> <li><%= user.name %></li> <% end %> </ul> </div> 

Большой!

Если вам интересно, этот модуль просто устанавливает некоторые обратные вызовы, которые запускаются после входа в систему, перед выходом из системы и после каждого другого запроса. Эти обратные вызовы обновляют соответствующие поля. Например, вот код, который запускается после каждого запроса пользователя.

Обновление 2015/04/01: Забыли пароль

Это обновление снова было вдохновлено одним из читателей, который попросил меня показать, как реализовать функцию сброса пароля с помощью Sorcery. Спасибо всем вам за такие отличные отзывы!

Пользователи, как правило, забывают свои пароли, поэтому возможность их сброса абсолютно необходима для любой системы аутентификации. С Волшебством мы можем сделать это очень быстро.

Установите новый модуль ResetPassword как всегда:

 $ rails g sorcery:install reset_password --only-submodules 

Это создаст миграцию, добавив следующие поля в таблицу users :

  • reset_password_token ( string , индекс)
  • reset_password_token_expires_at , ( datetime )
  • reset_password_email_sent_at , ( datetime )

reset_password_token — это случайная строка, которая будет сгенерирована при запросе инструкций по сбросу пароля. Этот токен действителен только в течение ограниченного периода времени и аннулируется, как только пользователь меняет свой пароль. Позже он будет использоваться для получения пользовательской записи и изменения пароля для нее, поэтому токены должны храниться в безопасности.

Убедитесь, что модуль был зарегистрирован:

конфиг / Инициализаторы / sorcery.rb

 [...] Rails.application.config.sorcery.submodules = [:reset_password] [...] 

Теперь предоставьте ссылку «Забыли пароль?»:

просмотры / сессия / new.html.erb

 <p><%= link_to 'Forgot your password?', new_reset_password_>path %></p> 

Установите несколько маршрутов:

конфиг / routes.rb

 [...] resources :reset_passwords, only: [:new, :create, :update, :edit] [...] 

и создайте новый контроллер:

reset_passwords_controller.rb

 class ResetPasswordsController < ApplicationController skip_before_filter :require_login def new end end 

Создайте первое представление, где пользователь будет вводить электронное письмо для получения инструкций по сбросу пароля:

просмотров / reset_passwords / new.html.erb

 <h1>Reset Password</h1> <%= form_tag reset_passwords_path, method: :post do %> <div class="form-group"> <%= label_tag :email %> <%= text_field_tag :email, nil, class: 'form-control', required: true %> </div> <%= submit_tag "Send reset instructions", class: 'btn btn-primary btn-lg' %> <% end %> 

Теперь метод create :

reset_passwords_controller.rb

 [...] def create @user = User.find_by_email(params[:email]) @user.deliver_reset_password_instructions! if @user flash[:success] = 'Instructions have been sent to your email.' redirect_to log_in_path end [...] 

deliver_reset_password_instructions! это метод, предоставляемый Sorcery, который на самом деле будет полагаться на вашу собственную почтовую программу для отправки инструкций по сбросу пароля. У нас уже есть UserMailer , поэтому давайте просто добавим новый метод:

отправители / user_mailer.rb

 [...] def reset_password_email(user) @user = user @url = edit_reset_password_url(@user.reset_password_token) mail(to: user.email, subject: "Your password has been reset") end [...] 

Добавьте соответствующий вид:

просмотров / user_mailer / reset_password_email.html.erb

 <p>Hello, <%= @user.name %>!</p> <p>Someone (hopefully, you) have requested to reset your password.</p> <p>To choose a new password, follow this link: <%= link_to @url, @url %>.</p> 

Мы также должны указать имя почтовой программы в конфигурационном файле Sorcery:

конфиг / Инициализаторы / sorcery.rb

 [...] user.reset_password_mailer = UserMailer [...] 

Вы можете переопределить user.reset_password_email_method_name если вам не нравится reset_password_email метода reset_password_email по умолчанию.

Теперь добавьте действие edit которое будет вызываться, когда пользователь нажимает на ссылку, указанную в электронном письме:

reset_passwords_controller.rb

 [...] def edit @token = params[:id] @user = User.load_from_reset_password_token(@token) not_authenticated if @user.blank? end [...] 

и вид:

просмотров / reset_passwords / edit.html.erb

 <h1>Choose a new password</h1> <%= form_for @user, url: reset_password_path(@token), method: :patch do |f| %> <%= render 'shared/errors', object: @user %> <div class="form-group"> <%= f.label :password %> <%= f.password_field :password, class: 'form-control', required: true %> </div> <div class="form-group"> <%= f.label :password_confirmation %> <%= f.password_field :password_confirmation, class: 'form-control', required: true %> </div> <%= f.submit 'Set password', class: 'btn btn-primary btn-lg' %> <% end %> 

Наконец, добавьте действие update для обработки смены пароля:

reset_passwords_controller.rb

 [...] def update @token = params[:id] @user = User.load_from_reset_password_token(@token) not_authenticated && return if @user.blank? @user.password_confirmation = params[:user][:password_confirmation] if @user.change_password!(params[:user][:password]) flash[:success] = 'Password was successfully updated.' redirect_to log_in_path else render "edit" end end [...] 

И вы сделали! Не стесняйтесь изменять этот код дальше и отправлять свои вопросы, если у вас возникли проблемы.

Вывод

Мы рассмотрели базовую настройку Sorcery и его подмодули. Есть еще несколько из них, так что посмотрите вики проекта, чтобы узнать больше.

Вы когда-нибудь пробовали использовать Волшебство в своих проектах? Вы нашли это удобным? Поделитесь своим опытом и не стесняйтесь оставлять свои вопросы в комментариях.