Введение в аутентификацию пользователей
Действия, защищенные паролем, являются распространенной функцией в большинстве веб-приложений, позволяя только зарегистрированным пользователям вводить действительный пароль. Это называется «Аутентификация пользователя», и это требуется многим приложениям Rails. Давайте начнем с быстрого сценария того, как работает процесс аутентификации пользователя.
Bcrypt
Есть другой простой способ зашифровать пароли, а не создавать соль с нуля: используйте bcrypt.bcrypt-ruby – это гем ruby для шифрования, и мы будем использовать его в нашем приложении.
Чтобы установить его, просто добавьте в файл gem следующее:
и внутри вашей директории приложения запустите:
тогда вы можете просто написать:
Has_secure_token
Создать модель пользователя
Let’s get started!
The first thing we are going to need is a Rails project to work in. I already have one built, but if you are looking to just play around or create your own new project for whatever reason, you can do so by using the rails new <app-name> command.
Views
The last step is to create some views that we can actually interact with. This is the bit that is most customizable, as you can create whatever variety of UI you would like for this. As mine are also rather basic just to get things working, I will just give an overview of what is needed here.
Авторизация в ruby on rails
В системном программировании есть два понятия аутентификация (authentication) и авторизация (authorization). Аутентификация (authentication) — это процесс определения кем именно является пользователь системы. Самый простой и часто используемый способ — это использование логина и пароля. Существуют также распределённые системы: OpenId, Google API, Facebook API и т. п.
Всё это выглядит примерно следующим образом:
Для реализации аутентификации в Ruby on Rails существует несколько плагинов: restful_authentication, authlogic. Я бы посоветовал authlogic. В последнем проекте я использую самописный механизм аутентификации, так как у меня legacy база данных и механизм аутентификации очень простой.
Я хочу написать об авторизации. Авторизация — это процесс проверки прав пользователя при выполнении определённых действий. При реализации авторизации возникают следующие вопросы:
Я перерыл множество плагинов рельс и понял, что не хочу использовать ничего из имеющегося, так как плагины слишком сложны и непрактичны для меня.
Мои ответы на эти вопросы следующие:
Начнём по порядку.
Для хранения прав пользователей заводится атрибут role:
class User < ActiveRecord::Base ... module Role ADMIN = ... MODERATOR = ... USER = ... end ... validates_presence_of :role validates_inclusion_of :role, :in => [Role::ADMIN, Role::MODERATOR, Role::USER] ... def admin? role == Role::ADMIN end def moderator? role == Role::MODERATOR end def user? role == Role::USER end end
Права пользователей проверяются в контроллерах, при этом права пользователя зависят не только от самого пользователя, но и от конкретной ситуации. Например у пользователя есть группы и телефоны. В routes.rb это выглядит вот так:
map.resources :users do |user| user.resources :groups user.resources :phones end
Но пользователь может просматривать только свои группы и телефоны. Это достигается следующим образом. GroupsController и PhonesController оба наследуются от UserResourceController.
class GroupsController < UserResourceController ... end
class PhonesController < UserResourceController ... end
А UserResourceController выглядит следующим образом
class UserResourceController < ApplicationController before_filter :init_user deny_all allow_any :admin?, :owner? private def init_user @user = User.find(params[:user_id]) end def owner? current_user == @user end end
Для авторизации здесь написаны две строчки, первая
deny_all
запрещает доступ к этому контроллеру для всех кто бы то ни был. Вторая
allow_any :admin?, :owner?
разрешает доступ либо админу (admin), либо хозяину (owner). Правила применяются в порядке следования с верху вниз. Возникает вопрос, как система авторизации поймёт admin это или owner. Передоваемые методу allow_any значения — это просто имена методов контроллера. Чтобы понять не является ли текущий пользователь хозяином ресурса вызывается метод owner? класса UserResourceController. В этом методе и происходит проверка. Как видно умение пользователя быть хозяином нигде не хранится, не соответствует никакой роли, это просто метод контроллера. Таким же образом метод admin? проверяет является ли пользователь администратором. Этот метод объявлен в родительском классе ApplicationController.
Можно обойтись без дополнительного метода, если передовать методу allow_any не символ, а Proc.
allow_any :owner?, lambda {|controller| controller.current_user.admin?}
Здесь уже используется роль, которая хранится в базе данных.
В случае если в системе есть несколько администраторов, но у них должен быть доступ только к своим ресурсам, а не к ресурсам друг друга, то для проверки прав можно использовать следующую инструкцию.
allow_all :admin?, :owner?
Для того чтобы пользователь получил доступ к такому ресурсу необходимо чтобы он был и администратором и хозяимном ресурса.
Так же в инструкциях allow и deny можно использовать опции :only и :except, которые работают абсолютно так же как в инструкции before_filter:
deny :owner?, :only => [:published, :accountable, :account_all] allow :moderator?, :only => [:show, :index, :accept, :decline, :published]
Как же обрабатывать недостаток прав пользователя. Для этого просто пишется обработчик исключения в ApplicationController’е
class ApplicationController < ActionController::Base ... rescue_from AuthorizationSystem::AccessDenied, :with => :render_forbidden ... private ... def render_forbidden(exception) log_error(exception) respond_to do |type| type.html { render :template => 'errors/forbidden', :layout => 'application', :status => :forbidden } type.all { render :nothing => true, :status => :forbidden } end end end
В результате при обращении пользователя к ресурсу на который у него нет прав ему показывается страница с соответствующим текстом, а Веб-сервер возвращает код 403.
Для того, чтобы пользоваться данной системой нужно просто включить в ApplicationController соответствующий модуль. ApplicationController приобретёт следующий вид:
class ApplicationController < ActionController::Base ... include AuthorizationSystem rescue_from AuthorizationSystem::AccessDenied, :with => :render_forbidden deny_all # Доступ по умолчанию ... private ... def render_forbidden(exception) log_error(exception) respond_to do |type| type.html { render :template => 'errors/forbidden', :layout => 'mmsportal_legacy', :status => :forbidden } type.all { render :nothing => true, :status => :forbidden } end end ... end
Для того чтобы всё это работало я положил в каталог lib проекта на Ruby on Rails файл authorization_system.rb содержание которого приводится ниже. Указанная система авторизации полностью независима от аутентификации, вы можете использовать любой плагин, какой только захотите. Кроме того структура прав пользователя тоже может быть произвольной. Всё что необходимо сделать — просто написать необходимые функции-тесты в контроллерах.
А вот и сам модуль
module AuthorizationSystem class AccessDenied < StandardError end def self.included(base) base.send :include, InstanceMethods base.send :extend, ClassMethods end module InstanceMethods def access_denied raise AccessDenied end private def check_access @access || access_denied end end module ClassMethods # Чтобы получить доступ пользователь должен обладать всеми указанными правами def allow_all(*roles) process_roles(roles, true, :&.to_proc, method(:allow_combiner)) end # Пользователь не получит доступ только если обладает всеми указанными правами def deny_all(*roles) process_roles(roles, true, :&.to_proc, method(:deny_combiner)) end # Чтобы получить доступ пользователь должен обладать любым из указанных прав def allow_any(*roles) process_roles(roles, false, :|.to_proc, method(:allow_combiner)) end # Пользователь не получит доступ если обладает хотябы одним из указанных прав def deny_any(*roles) process_roles(roles, false, :|.to_proc, method(:deny_combiner)) end # Пользователь с данным правом получит доступ def allow(role, opts = {}) allow_all(role, opts) end # Пользователь с данным правом не получит доступ def deny(role, opts = {}) deny_all(role, opts) end # Только пользователь с данным правом будет получать доступ def allow_only(role, opts = {}) deny_all(opts) allow(role, opts) end # Только пользователь с данным правом не будет получать доступ def deny_only(role, opts = {}) allow_all(opts) deny(role, opts) end private def allow_combiner(old_access, match) old_access || match end def deny_combiner(old_access, match) old_access && !match end def process_roles(roles, match_initial_value, match_combiner, access_combiner) opts = roles.extract_options! modify_access opts do |controller| controller.instance_eval do match = match_initial_value roles.each do |role| case role when Symbol then match = match_combiner.call(match, send(role)) when Proc then match = match_combiner.call(match, role.call(controller)) else match = match_combiner.call(match, role) end end @access = access_combiner.call(@access, match) end end end def modify_access(opts = {}, &block) skip_before_filter :check_access before_filter(opts, &block) before_filter :check_access end end end
Аутентификация с помощью утилиты
Добавить драгоценный камень в Gemfile:
gem ‘devise’
Затем запустите команду bundle install .
Используйте команду $ rails generate devise:install чтобы сгенерировать требуемый файл конфигурации.
Настройте параметры URL по умолчанию для почтовой программы Devise в каждой среде. В среде разработки добавьте эту строку:
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
к вашему config/environments/development.rb
аналогично в этом файле редактирования config/environments/production.rb и добавьте
config.action_mailer.default_url_options = { host: 'your-site-url'}
Добавление некоторых проверок в модель пользователя
В дополнение к attr_accessors нам нужно добавить несколько правил проверки, чтобы убедиться, что входные данные соответствуют нашим требованиям.
classUser < ActiveRecord::Base | |
attr_accessor:password | |
EMAIL_REGEX=/^[A-Z0-9._% -] @[A-Z0-9.-] .[A-Z]{2,4}$/i | |
validates:username,:presence=>true,:uniqueness=>true,:length=>{:in=>3..20} | |
validates:email,:presence=>true,:uniqueness=>true,:format=>EMAIL_REGEX | |
validates:password,:confirmation=>true#password_confirmation attr | |
validates_length_of:password,:in=>6..20,:on=>:create | |
end |
Мы создали страницу регистрации, которая создает нового пользователя и проверяет входные данные, но мы не шифровали пароль. Прежде чем мы это сделаем, давайте поговорим о шифровании пароля.
Еще больше делать
Еще предстоит еще много работы, прежде чем мы сможем сказать, что процесс аутентификации пользователя завершен. В моем следующем посте я расскажу о работе с сессиями и файлами cookie, ограничении доступа и настройке маршрута. Спасибо за прочтение!
Защита массовых назначений
Одна из наиболее распространенных проблем безопасности в Rails называется «уязвимость массового назначения», и она проистекает из соглашения ActiveRecord о создании методов получения и установки для всех значений атрибутов объекта ActiveRecord.
В действии создания вместо непосредственного назначения каждого атрибута по одному мы используем хэш всех значений, которые мы хотим присвоить атрибутам субъекта. Это вышеупомянутое массовое задание и суть проблемы. Есть атрибуты, которые мы не хотим, чтобы пользователь мог изменять через форму, но мы не проверяем параметры.
Чтобы избежать этого, в Rails есть два метода для защиты атрибутов от массового назначения.
- attr_protected: все атрибуты, отмеченные как attr_protected , игнорируются при массовом назначении, и все остальные атрибуты будут доступны.
- attr_accessible: все атрибуты marekd с attr_accessible доступны во время массового назначения, а все остальные атрибуты будут защищены.
Наконец, давайте добавим доступные атрибуты в модель пользователя
Это завершает процесс регистрации! Вы можете запустить свой сервер, зарегистрироваться как новый пользователь и протестировать его, чтобы убедиться, что пароль успешно зашифрован в базе данных!
Примечание. Не забудьте добавить маршрут по умолчанию в файл маршрутов! Вы можете просто откомментировать в конце файла.
Конфигурирование фильтров и помощников
Чтобы настроить контроллер с аутентификацией пользователя с помощью devise, добавьте это before_action: (если ваша модель разработки – «Пользователь»):
Методы шифрования паролей
Как упоминалось ранее, никогда не храните пароли в базе данных в виде простого текста. Шифрование паролей является наилучшей практикой и является фундаментальным для всех подходов аутентификации пользователей в Rails.
Пользовательские виды
Если вам нужно настроить свои представления, вы можете использовать генератор rails generate devise:views , который скопирует все представления в ваше приложение. Затем вы можете отредактировать их по своему желанию.
Солевой пароль
Из-за некоторых недостатков, которые существуют в технике хеширования паролей, таких как таблицы Rainbow , использование пароля является более безопасным способом шифрования паролей. «Соль» – это дополнительная строка данных, добавляемая к паролю перед его шифрованием. Он должен быть уникальным и случайным, чтобы сделать недостаток таблиц Rainbow бесполезным.
Шифрование с использованием соли:
Хеширование пароля
Хеширование – это процесс применения математических функций и алгоритмов к строке данных для получения уникальной выходной строки. При создании нового пользователя простой текстовый пароль хешируется и сохраняется в базе данных. Когда пользователь входит в систему, входной пароль хешируется и сравнивается с хешированным паролем, хранящимся в базе данных.
Мы можем реализовать метод хеширования, используя SHA1 в Rails, используя всего две строки кода
Conclusion
And that’s how I get started with pretty much every Rails project I create to have a bare-bones functional login system. I hope this was helpful in some way if you’re still here. Happy Coding!