Авторизация в Ruby on Rails – Vir’s place — LiveJournal

Введение в аутентификацию пользователей

Действия, защищенные паролем, являются распространенной функцией в большинстве веб-приложений, позволяя только зарегистрированным пользователям вводить действительный пароль. Это называется «Аутентификация пользователя», и это требуется многим приложениям 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!

Похожее:  Расширенная настройка Exim и Dovecot с привязкой к OpenLDAP / Хабр

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *