参考: http://www.xyyz.me/2015/07/24/role-based-authorization-with-pundit.html
本文将介绍: 在 Rails 应用中, 如何使用 Pundit 实现一个基于角色的授权系统.
数据模型
+------+ +------+ +------------+| | N N | | 1 N | || User |<-------->| Role |<-------->| Permission || | | | | |+------+ +------+ +------------+
User 和 Role 是多对多的关系, Role 和 Permission 是一对多的关系. Permission 的定义包含两个字段: action
和 resource
, 分别与 Rails 的 Controller Action 和 Model 对应.
Model 关系定义:
class User < ActiveRecord::Base has_and_belongs_to_many :roles has_many :permissions, through: :rolesend
class Role < ActiveRecord::Base has_many :permissions, dependent: :destroy has_and_belongs_to_many :usersend
class Permission < ActiveRecord::Base belongs_to :role has_many :users, through: :roleend
权限定义
为了方便在角色管理时能够列出可选择的权限点, 权限点的定义需要通过某种方式存储起来:
class ApplicationPolicy class << self def actions @actions ||= [] end def permit(action_or_actions) acts = Array(action_or_actions).collect(&:to_s) acts.each do |act| define_method("#{act}?") { can? act } end actions.concat(acts) end end private def can?(action) permission = { action: action, resource: record.is_a?(Class) ? record.name : record.class.name } user.permissions.exists?(permission) endend
在 ApplicationPolicy
里定义一个 permit
方法 (类方法) 用来定义和保存权限点,can?
方法用来做权限检查.
然后就可以像这样声明权限点:
class ResourcePolicy < ApplicationPolicy permit [:read, :create, :update, :destroy]end
这些 Action 就会被保存到 ResourcePolicy.actions
里.
另外还需要两个方法 policies
和 resource
:
class ApplicationPolicy class << self def policies @policies ||= Dir.chdir(Rails.root.join('app/policies')) do Dir['*/_policy.rb'].collect do |file| file.chomp('.rb').camelize.constantize unless file == File.basename(FILE) end.compact end end def resource name.chomp('Policy') end endend
分别用来获取所有的 Policy 和 每个 Policy 对应的 resource (这两个方法是通过简单的命名规则实现的, 灵活性会差一点).
角色与权限
在角色管理中, 可以像这样列出所有可选择的权限点:
<% ApplicationPolicy.policies.each do |policy| %> <% resource = policy.resource %> <div> <span><%= resource %></span> <% policy.actions.each do |action| %> <% checked = role.permissions.exists?(action: action, resource: resource) %> <% value = "#{action}##{resource}" %> <%= f.check_box :permissions, { multiple: true, checked: checked }, value, nil %> <%= f.label :permissions, value, value: value %> <% end %> </div><% end %>
角色与用户
在用户管理中, 可以这样为用户指定角色:
<div> <%= f.label :roles %><br /> <%= f.collection_check_boxes :role_ids, Role.all, :id, :name %></div>
参考项目
这个系统的完整实现请参考此项目 (mxyzm/oh_my_user) 的后台管理部分.
参考资料
利用 cancan 实现一个优雅可扩展的角色管理系统