23. 用戶權限控管
23-1 增加後台管理員權限
目前只要有登入就可以進入後台,接下來實作基本的權限檢查(Authorization)吧,只有管理員才可以進入後台。
基本的原理很簡單,我們在 User model 上新增一個欄位標記是不是管理員即可。
執行 rails g migration add_role_to_users role:string
增加一個 role 字串符欄位到 users table 上。
在之前的課程中,是用
is_admin:boolean
欄位來表示是不是管理員,在這裡改用role:string
,這樣的好處是可以有角色擴充的彈性。
執行 rake db:migrate
編輯 app/controllers/admin_controller.rb
,增加權限檢查 current_user
的 role (角色)必須是 admin
。
class AdminController < ApplicationController
protect_from_forgery with: :exception
before_action :authenticate_user!
+ before_action :require_admin!
layout "admin"
+ protected
+ def require_admin!
+ if current_user.role != "admin"
+ flash[:alert] = "您的權限不足"
+ redirect_to root_path
+ end
+ end
end
這時候點選主選單的後臺面板,就會出現:
這樣就進不去後台了。
要設定第一個管理員,只能透過 rails console 了。
執行 rails console
,依序輸入:
u = User.find_by_email("[email protected]")
u.role = "admin"
u.save!
這樣 [email protected]
就有權限可以進入後台管理。
23-2 增加其他角色
用 role
的設計好處是可以擴充不同角色,讓我們新設計一個 editor
角色:
admin
有全部後台權限editor
可以進入後台管理活動,但不能管理用戶
編輯 app/controllers/admin_controller.rb
,拿掉本來後台都套用的 before_action :require_admin!
權限檢查。然後新增一個 require_editor!
方法,等會我們得逐個 controllers 一個一個加上權限檢查。
class AdminController < ApplicationController
protect_from_forgery with: :exception
before_action :authenticate_user!
- before_action :require_admin!
layout "admin"
protected
+ def require_editor!
+ if current_user.role != "editor" && current_user.role != "admin"
+ flash[:alert] = "您的權限不足"
+ redirect_to root_path
+ end
+ end
def require_admin!
if current_user.role != "admin"
flash[:alert] = "您的權限不足"
redirect_to root_path
end
end
end
編輯以下檔案,需要 editor 權限:
app/controllers/admin/event_registrations_controller.rb
app/controllers/admin/event_tickets_controller.rb
app/controllers/admin/events_controller.rb
+ before_action :require_editor!
編輯以下檔案,需要 admin 權限:
app/controllers/admin/user_profiles_controller.rb
app/controllers/admin/users_controller.rb
app/controllers/admin/versions_controller.rb
+ before_action :require_admin!
這樣就完成了,每個 app/controllers/admin/
目錄下的 controller 都要記得加上權限檢查。
23-3 編輯用戶角色
除了第一個管理員需要進入 rails console 編輯角色之外,在編輯用戶那一頁,我們可以實作一個下拉選單來編輯角色。
編輯 app/models/user.rb
class User < ApplicationRecord
+ ROLES = ["admin", "editor"]
編輯 app/views/admin/users/edit.html.erb
<div class="form-group">
<%= f.label :email %>
<%= f.text_field :email, :class => "form-control" %>
</div>
+ <div class="form-group">
+ <%= f.label :role %>
+ <%= f.select :role, User::ROLES.map{ |x| [t(x, :scope => "user.role"), x] }, { :include_blank => true }, :class => "form-control" %>
+ </div>
註意這裡多加了
:include_blank => true
參數,這會讓下拉選單多一個空的 nil 空選項。
編輯 config/locales/zh-CN.yml
"zh-CN":
+ user:
+ role:
+ admin: 超級管理員
+ editor: 活動管理員
編輯 app/controllers/admin/users_controller.rb
def user_params
- params.require(:user).permit(:email, :group_ids => [])
+ params.require(:user).permit(:email, :role, :group_ids => [])
end
這樣就可以編輯用戶的角色了。請編輯任一個用戶變成 editor,然後登出,改用那個帳號登入(假用戶的密碼是 12345678),就可以測試上一節的權限檢查了:可以活動管理,但是點用戶管理會跳出權限不足。
23-4 權限重構
既然 editor 沒有權限可以管理用戶,那麽在主選單上就不應該出現用戶管理的連結,讓我們小小改進一下:
編輯 app/views/layouts/admin.html.erb
,根據不同權限決定要不要顯示連結:
<% if current_user %>
+ <% if current_user.role == "admin" || current_user.role == "editor" %>
<li class="active"><%= link_to('活動管理', admin_events_path) %></li>
+ <% end %>
+ <% if current_user.role == "admin" %>
<li><%= link_to('用戶管理', admin_users_path) %></li>
+ <% end %>
<% end %>
不過這個 if 條件跟 admin_controller.rb
裡面的權限檢查條件,其實邏輯是一樣的,也就是 admin 的權限包括 editor 的權限,我們進一步重構一下。
編輯 app/models/user.rb
,加上兩個方法判斷是不是 admin 和 editor
+ def is_admin?
+ self.role == "admin"
+ end
+
+ def is_editor?
+ ["admin", "editor"].include?(self.role) # 如果是 admin 的話,當然也有 editor 的權限
+ end
編輯 app/controllers/admin_controller.rb
,改成用 is_admin?
和 is_editor?
方法
protected
def require_editor!
- if current_user.role != "editor" && current_user.role != "admin"
+ unless current_user.is_editor?
flash[:alert] = "您的權限不足"
redirect_to root_path
end
end
def require_admin!
- if current_user.role != "admin"
+ unless current_user.is_admin?
flash[:alert] = "您的權限不足"
redirect_to root_path
end
end
編輯 app/views/layouts/admin.html.erb
,改成用 is_admin?
和 is_editor?
方法
<% if current_user %>
- <% if current_user.role == "admin" || current_user.role == "editor" %>
+ <% if current_user.is_editor? %>
<li class="active"><%= link_to('活動管理', admin_events_path) %></li>
<% end %>
- <% if current_user.role == "admin" %>
+ <% if current_user.is_admin? %>
<li><%= link_to('用戶管理', admin_users_path) %></li>
<% end %>
<% end %>
這樣就完成了,透過 User model 的 is_admin?
和 is_editor?
方法集中權限檢查的邏輯,之後如果新增不同子權限,只要改 model 就可以了。
23-5 補充: pundit 和 cancancan
如果權限檢查的邏輯比較複雜,有很多角色而且不同 action 又有不同權限,甚至是不同資料有不同權限,這時候我們可以考慮使用專用的權限檢查 gem,幫助我們組織代碼,也避免忘記加上權限(前幾節的實作,如果忘記加上 before_action
你就忘記檢查到囉)。最知名的有以下兩套:
詳細用法請自行參考文檔。