Перад тым як рэалізаваць магчымасць карыстальніка маніпуляваць дадзенымі праз web-інтэрфэйс, варта падумаць пра забеспячэнне пэўнай бяспекі і абмежаванне роляў карыстальніка (напрыклад, магчымасць выдаляць водгукі і агляд пэўных старонак толькі для адміна). Звычайна гэта робіца праз аўтэнтыфікацыю і аўтарызацыю.
У двух сказах:
- аўтэнтыфікацыя - гэта працэс ідэнтыфікацыі канкрэтнага карыстальніка.
- аўтарызацыя - гэта механізм надання і абмежавання роляў для карыстальніка.
Большасць web-прыкладанняў павінна сачыць за станам карыстальніка. Але, праблема у тым, што HTTP - гэта пратакол, які незалежны ад папярэдніх запытаў. З дапамогай сесій можна стварыць такую залежнасць. Дзякуючы сесіям карыстальніку не трэба ідэнтыфікаваць сябе з кожным запытам. Rails аўтаматычна стварае новую сесію, альбо выкарыстоўвае ужо створаную, калі карыстальнік атрымлівае доступ да прыкладання. Падрабязней пра рэалізацыю сесій Rails чытайце тут.
Існуюць папулярныя гемы, якія вырашаюць задачы аўтарызацыі і аўтэнтыфікацыі. Але для нашага course-app гэтыя рашэнні будуць празмерна грувасткімі. Мы рэалізуем простую сістэму аўтэнтыфікацыі для ментара (выкладчыка курса) праз логін і пароль.
Перш за усё ствараем мадэль ментара:
$ rails g model Mentor name:string password:digest --no-test-framework
Звярніце увагу на незнаёмы тып digest. Rails дазваляе захоўваць пароль ў выглядзе лічбавага хэша. Такім чынам, у прыкладанні і БД нідзе не будзе сустракацца пароль у яўным выглядзе, гэта дае дадатковую бяспеку.
Не забываем пра міграцыі:
$ rake db:migrate
Зараз вы можаце паглядзець схему БД і убачыце, што калонка password мае тып string. Усё правільна, пароль будзе захоўвацца у выглядзе захэшыраванага радка.
Дададзім валідацыі наяўнасці і унікальнасці імя, таксама добрай ідэяй будзе накласці абмежаванне на мінімальную даўжыню пароля:
# app/models/mentor.rb
class Mentor < ActiveRecord::Base
has_secure_password
validates :name, presence: true, uniqueness: true
validates :password, length: { minimum: 8 }
end
Вы, напэўна, звярнулі увагу, што мы не рабілі праверку наяўнасці пароля. Справа у тым, што метад has_secure_password() робіць праверку падчас стварэння аб'екта. Гэты метад быў аўтаматычна дададзены падчас генерацыі мадэлі. Акрамя гэтага, метад has_secure_password() абмяжоўвае максімальную даўжыню пароля у 72 сімвала і дазваляе выкарыстоўваць пацверджанне пароля у формах.
Для таго, каб скарыстацца магчымасцямі гэтага метада, трэба дадаць гем bcrypt:
#Gemfile
gem 'bcrypt', '~> 3.1.7'
Пасля усталёўкі трэба будзе перазапусціць сервер.
Мы не будзем ствараць форму рэгістрацыі для прыкладання усяго з адным карыстальнікам, які, між іншым, дасведчаны у Ruby on Rails. Ствараем ментара проста у Rails-кансолі:
> Mentor.create(name: 'mentor_name', password: 'password')
Давайце дададзім адмысловую адміністрацыйную старонку (у будучым мы зробім гэтую старонку бачнай толькі для аўтарызаванага ментара).
Ствараем кантролер для адміністрацыйнай старонкі ментара з адным дзеяннем index:
# app/controllers/mentor_controller.rb
class MentorController < ApplicationController
def index
end
end
Дадаем адміністрацыйную старонку:
# app/views/mentor/index.html.erb
<div class="page-header">
<h1>Welcome Admin!</h1>
</div>
Застаецца дадаць маршрут:
# config/routes.rb
get 'mentor' => 'mentor#index'
Мы стварылі ментара, зараз трэба даць магчымасць карыстальніку ідэнтыфікаваць сябе, а таксама зафіксаваць уваход для сессіі. Звычана гэта рэалізуецца праз форму увахода. Давайце накідаем старонку з формай:
# app/views/sessions/new.html.erb
<div class="page-header">
<h1>Log in</h1>
</div>
<%= form_tag({:action => 'create'}, {class: 'form-horizontal center'}) do %>
<div class="form-group">
<%= label_tag :name, 'Name:', class: 'col-md-4 control-label' %>
<div class="col-md-4">
<%= text_field_tag :name, params[:name], class: 'form-control' %>
</div>
</div>
<div class="form-group">
<%= label_tag :password, 'Password:', class: 'col-md-4 control-label' %>
<div class="col-md-4">
<%= password_field_tag :password, params[:password], class: 'form-control' %>
</div>
</div>
<div>
<%= submit_tag 'Login', class: 'btn btn-large btn-primary' %>
</div>
<% end %>
Гэта форма істотна адрозніваецца ад формы для водгука, хаця HTML-код, які будзе
на выхадзе, вельмі падобны. Справа у тым, што гэтая форма не звязана з аб'ектам
мадэлі. Дзеянне new, якое у кантроллеры адпавядае гэтай форме можна будзе
пакінуць пустым. Сама форма генеруецца з дапамогай метада form_tag. Таксама
мы бачым метады text_field_tag, password_field_tag i submit_tag.
Гэтыя метады ствараюць HTML-інпуты розных тыпаў. Напрыклад інпут тыпа password
хавае уведзеныя сімвалы. Такая форма дазваляе абысціся без мадэлі - значэнні
параметраў (напрыклад params[:name]
) звязваюцца наўпрост з інпутамі.
Зараз напішам адмысловы кантролер сесій, каб можна было ствараць і выдаляць сесіі (інакш кажучы, рабіць у браўзеры звыклыя нам дзеянні log in / log out. Асаблівасць у тым, што сесіі мы будзем захоўваць у cookies карыстальніка, а не ў базе дадзеных. Таму, мадэль нам тут не патрэбна.
Ствараем кантролер сесій:
# app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
def new
end
def create
mentor = Mentor.find_by(name: params[:name])
if mentor and mentor.authenticate(params[:password])
session[:mentor_id] = mentor.id
redirect_to mentor_url
end
end
def destroy
end
end
Разглядзім дзеянне create. Зменная mentor будзе роўная альбо адпаведнаму
аб'екту класа Mentor (калі у форме мы пазначым правільнае імя), альбо nil
у
любым іншым выпадку. Такім чынам код if mentor
вяртае альбо true
альбо false
.
У выпадку true
далей на аб'екце mentor выклікаецца метад authenticate(),
які звярае пароль, які уведзены у форме (params[:password]
) з паролем аб'екта
mentor. І толькі калі выклік гэтага метада таксама вяртае true
у сесію
запісваецца id ментара. Пасля гэтага адбываецца перанакіраванне на адміністрацыйную старонку.
Прыйшоў час дадаць маршруты, каб праверыць, як працуе форма. Дадамо маршруты адразу на усе дзеянні кантролера, але зробім гэта незвычайным чынам:
# config/routes.rb
controller :sessions do
get 'login' => :new
post 'login' => :create
delete 'logout' => :destroy
end
Мы змясцілі абвяшчэнне маршрутаў у блок і перададзім гэты блок метаду
controller. Зараз не трэба рабіць запісы кшталту get 'login' => 'sessions#new'
.
Тут мы яўна пазначылі HTTP-метады POST і DELETE па аналогіі з рэсурснымі
маршрутамі кантролера Testimonials. Параўнайце:
$ rake routes
POST-запыт выклікаецца, калі мы націскаем кнопку Login, якую стварыў метад submit_tag. DELETE мы выкарыстаем пазней для выхада з адміністратыўнай часткі і выдалення сессіі.
Зараз можна праверыць нашу форму: http://0.0.0.0:3000/login
Калі мы уводзім слушны пароль і імя нашага ментара, то ўсё цудоўна працуе - ствараецца сесія і запісваецца у cookies, адбываецца перанакіраванне на старонку /mentor:
Але, калі зрабіць памылку падчас аўтэнтыфікацыі, то на выхадзе мы і ўбачым памылку. Праўда не ў тым выглядзе, як хацелася б :) Зараз мы ускладнім нашу кіруючую паслядоўнасць дзеяння create і апрацуем няправільныя параметры:
# app/controllers/sessions_controller.rb
def create
mentor = Mentor.find_by(name: params[:name])
if mentor and mentor.authenticate(params[:password])
session[:mentor_id] = mentor.id
redirect_to mentor_url
else
redirect_to login_url, alert: 'Invalid username or password'
end
end
Мы перанакіроўваем карыстальніка зноў на форму увахода. Але зараз мы не можам выкарыстаць метад errors (у адрозненні ад аналагічнага дзеяння Testimonials кантролера). Справа ў тым, што для сессіі мы не стваралі мадэль. Але выхад ёсць, мы выкарыстаем флэш-паведамленні, для таго, каб паведаміць карыстальніку пра памылкі. Flash - гэта частка сесіі, якая ачышчаецца з кожным запытам. Гэта значыць, што значэнні флэш (звычайна гэта проста радок, масіў ці хэш) захоўваюцца там паміж запытамі. Тут мы і будзем захоўваць паведамленне пра памылку , якое мы перадалі у атрыбут alert. Застаецца толькi адлюстраваць паведамленне пра памылку на старонцы:
# app/views/sessions/new.html.erb
<% if flash[:alert] %>
<div class="alert alert-danger"><%= flash[:alert] %></div>
<% end %>
Зараз усе працуе як трэба і можна абнавіць наша production-асяроддзе. Не забываем пра міграцыі Хероку:
$ heroku run rake db:migrate
Для таго каб скарыстацца Rails console у разгорнутаым прыкладанні увядзіце:
$ heroku run rails c
Мы зрабілі даволі простую сістэму аўтэнтыфікацыі і навучыліся ствараць сесіі. Але пакуль карысці ад гэтага няма і старонка /mentor бачная ўсім карыстальнікам, нават не аўтарызаваным. На наступным занятку мы пазнаёмімся з аўтарызацыяй і навучымся размяжоўваць ролі карыстальнікаў.