سلام، همونطور که اطلاع دارین(یعنی امیدوارم که اطلاع داشته باشین!)، میخوایم راجع به Authentication و Authorize در Ruby on Rails صحبت کنیم.
قبل از اینکه شروع کنیم، خدمتتون عرض کنم که این مقاله رو در دو قسمت مینویسم.
قسمت اول راجع به Authentication و Authorize صحبت میکنیم.
قسمت دوم راجع به این که چطوری JWT ایجاد کنیم و ازش استفاده کنیم، صحبت خواهیم کرد.
در ابتدا میریم سراغ ساخت یه پروژه ی کوچیک در rails و سپس میریم سراغ Authentication و Authorize.
راستی بیاین قبل از اینکه شروع کنیم، یه لیوان چای لاهیجان(یکی از شهرستان های استان گیلان معروف به عروس گیلان) آماده کنیم و همینطور که این پروژه رو میبریم جلو، چای هم نوش جان کنیم (:
میتونین از طریق گیت هابم یعنی از اینجا به این پروژه دسترسی داشته باشید.
خب یه پروژه به اسم authentication_authorize_project به صورت زیر میسازیم.
rails new authentication_authorize_project --api
اگه میبینین از api-- استفاده کردیم، به این دلیل که نمیخوایم برامون view ایجاد بشه. (اگه هم نمیدونین چیه هم الان مهم نیست ولی بعدا حتما گوگل کنین).
بعد از اینکه پروژه ساخته شد، با دستور cd authentication_authorize_project بِرید داخل پوشه ی پروژه و دستور bundle install رو بزنین.
حالا برای اینکه مطمئن باشین که همه چی اوکیه، دستور rails s رو بزنین و در مرورگرتون آدرس http://127.0.0.1:3000 رو بزنین. اگه همه چی اوکی بوده باشه، باید صفحه ای زیبا که نوشته hey! You're on Rails رو ببینین (:
در این پروژه ما قصد داریم تا user رو authentication و authorize کنیم. برای این کار یک مدلِ User که فیلد های name, email, password داره رو به صورت زیر میسازیم.
rails g model User name email password_digest
فقط یه دستور کوچیک rails db:migrate هم بزنین که بریم سراغ مرحله ی اصلی.
ما برای اینکه authentication و authorize انجام بدیم، از 'gem 'bcrypt استفاده میکنیم. برای اینکار باید بریم درون فایل Gemfile.rb در پروژه و 'gem 'bcrypt رو که در این فایل کامنت هست رو از حالت کامنت بیرون بیاریم. حالا دومرتبه دستور bundle install رو میزنیم تا gem مورد نظر ما نصب بشه.
درون مدل User، متد has_secure_password رو اضافه میکنیم که تا مطمئن باشیم password به صورت encrypted درون دیتابیس ذخیره بشه. ناگفته نماند که این متد جزئی از bcrypt است.
همچنین متد has_secure_password، علاوه بر کاری که در بالا خدمتتون عرض کردم، password و password_confirmation رو هم مقایسه میکنه که با هم match باشند. منتها یه مشکلی که در اینجا وجود داره، اگه nil ،password_confirmation باشه، باز هم رکورد ما در دیتابیس ذخیره میشه. برای جلوگیری از این کار، یه validates به مدل User به صورت زیر اضافه میکنیم.
validates :password_confirmation, presence: true
حالا باید یه کنترولر برای user بسازید. برای اینکار از دستور زیر اسفاده کنین.
rails g controller users
فایل routes.rb رو باز کنین و resource کنترولر جدید رو به صورت زیر درونش اضافه کنین.
Rails.application.routes.draw do resources :users end
الان بِرید داخل کنترولرِ user و یک action به اسم create ایجاد کنید. حالا میتونین مثل قطعه کد پایین، sign up کنین و user جدید رو در دیتابیس ذخیره کنین (:
class UsersController < ApplicationController before_action :user_params def create @user = User.new(user_params) if @user.save render json: { message: ['Saved'] }, status: 200 else render json: { message: ['Not Saved'] }, status: 400 end end private def user_params params.permit(:name, :email, :password, :password_confirmation) end end
من برای request زدن از postman استفاده میکنم، شما میتونین از هرچی دلتون میخواد استفاده کنین.
راستی، چون ما موقع ایجاد مدل User برای password از password_digest استفاده کردیم، تنها یه فیلد password که encrypted هم هست در دیتابیس ذخیره میشه.
در ابتدا یه کنترولر به اسم Sessions با اکشن های create و destroy به صورت زیر ایجاد میکنیم.
rails g controller Sessions create destroy
به کد زیر با دقت نگاه کنین. در واقع ما یه سرچ زدیم تا user مورد نظرمون رو از طریق اسمی که از پارامتر بهمون میرسه، پیدا کنیم. چون ما از bcrypt استفاده کردیم، یه متدی به نام authenticate بهمون میده و کارش اینه، passwordی که بهش میدیم رو با passwordی که در دیتابیس ذخیره شده بود رو با هم مقایسه میکنه و true یا false بر میگردونه. اگه true بر گردونه، ما ID یوزر مورد نظر رو در session ذخیره میکنیم.
در قسمت destroy هم session یوزری که وجود داره رو nil میکنه.
class SessionsController < ApplicationController def create user = User.find_by( email: params[:email] ) if user.try(:authenticate, params[:password]) session[:user_id] = user.id render json: { message: ['Welcome'] }, status: 200 else render json: { message: ['Invalid user/password combination'] }, status: 400 end end def destroy session[:user_id] = nil render json: { message: ['Logged out'] }, status: 200 end end
بریم routes های کنترولر session هم set کنیم تا بتونیم login و logout انجام بدیم.
controller :sessions do post 'login' => :create delete 'logout' => :destroy end
ما در اینجا بررسی میکنیم که دسترسی یک کاربر یا یوزر به کنترولر های مختلف سایت ما چطور هست.
چون تمام کنترولر های ما از ApplicationController ارث میبرند، لذا ما متد authorize رو در ApplicationController ایجاد میکنیم.
class ApplicationController < ActionController::API before_action :authorize protected def authorize unless User.find_by(id: session[:user_id]) render json: { message: ['Please log in'] }, status: 401 end end end
متد authorize قبل از شروع همه ی کنترولر ها اجرا میشه. حتی زمانی هم که login می کنیم، دومرتبه به ما ارور میده که please login. برای حل این مشکل دستور زیر رو، در کنترولر هایی که نمیخوایم قبل از شروعشون، متد authorize اجرا بشه، مینویسیم.
skip_before_action :authorize
اگه سوالی هست خوش حال میشم بتونم کمکتون کنم (:
امیدوارم از چایتون لذت برده باشید ((: