为什么要使用Cancancan
网站在发展的过程中,用户角色会不断的增加,并且会出现很多细分的权限,此时如果每次都在相应的controller和view里写判断,会变得非常冗杂,后期很难进行维护。针对这种情况,cancancan为我们提供了非常好的一种解法,详见Github官方文档。
举一下我们自己的例子:
我们做的是一个在线编程教育的网站,之前是有三种角色的:admin,VIP和普通用户,最初课程只有草稿/公开两种状态,在上线前增加了一下状态:上锁/未上锁,付费/免费。
于是针对用户能否查看课程,我写了以下的权限判断,看起来比较乱,但是能够快速解决问题。
def check_course_validation(course)
if !current_user.is_admin
if course.is_locked
if course.paid_courses && !current_user.valid_member?
redirect_to vip_to_payment_path
else
redirect_to dashboard_path
flash[:alert] = "暂无权限"
end
else
if course.paid_courses && !current_user.valid_member?
redirect_to vip_to_payment_path
end
end
end
end
上线后,用户基数加大,管理者身份里,单admin一个权限已经不合适了,需要再加editor的角色,editor有很多细分的权限,比如可以管理课程,但不能新增删除课程。
此时再在controller里去判断权限就不合适了,使用cancancan来进行权限管理就合理很多。
cancancan的基础步骤
说明:下文中,注的内容如果看不懂可以忽略掉,在实作的过程中再理解。
第一步:安装
在Gemfile里添加gem 'cancancan'
,然后在终端执行bundle install
,成功后执行
rails g cancan:ability
注:这样会生成一个ability.rb的文件,简单来说,我们会把网站所有的权限定义在这个ability文件里,在controller里和view里不用在权限判断了,只需要在view里检查是否有权限,在controller设置一下,更棒的是,view和controller里的权限是一致的。
第二步:在ability里定义用户角色相应的权限
在app/models/ability.rb里定义用户角色和权限,如果只有两种用户user,admin:
class Ability
include CanCan::Ability
def initialize(user)
if user.blank?
# 未登录
cannot :manage, :all
elsif user.admin?
# admin
can :manage, :all
else
can :manage, Course
cannot [:new, :create, :destroy], Course
end
end
注:
1.以上定义的内容是,未登录状态下,什么都不能做;admin可以做所有的操作, User只能管理Course,单不能对Course执行以下三个(:new, :create, :destroy)的操作
2.all 是指所有 object (resource)
3.cancancan 预设了几个action的组合方便我们使用:
# :manage: 是指这个 controller 內所有的 action
# :read : 指 :index 和 :show
# :update: 指 :edit 和 :update
# :destroy: 指 :destroy
# :create: 指 :new 和 :crate
can :manage, :all 指的是可以管理全部的内容
4.权限的顺序很重要,如果重叠,下面的会覆盖上面的,比如
can :manage, Course
cannot [:new, :create, :destroy], Course
上述说明用户不得删除/新增course
cannot [:new, :create, :destroy], Course
can :manage, Course
但如果顺序反过来,就表明:用户可以对课程进行任意操作。
在对应的controller设置
在app/controllers/admin/courses_controller.rb里设置
class Admin::CoursesController < AdminController
load_and_authorize_resource
def new
@course = Course.new
end
end
此时权限就识别好了,如果你测试会发现,user已经无法删除某一个course了。
注:
- cancancan是根据controller判断的,而非model。
- cancancan 很聪明得会在controller里识别同名的资源,在CoursesController里会load和authorize @course, 如果你希望比如在SyllabusController里去识别 @course,你可以这样写
class Admin::SyllabusController < AdminController
load_and_authorize_resource :course
...
end
- load_and_authorize_resource:指的是两件事load_resource和authorize_resource.关于这个,xdite有一篇文章,Cancan 實作角色權限設計的最佳實踐(2),在此不再赘言。
- 需要补充说明的是,请尽量使用
load_and_authorize_resource
。 - 如果在上述controller里直接使用
authorize_resource
, cancancan是找不到@course这个instance的,所以要么使用load_and_authorize_resource
或者在authorize_resource生效前,使用before_filter塞入一个@course的instance - 请注意:strong_params的命名要规范。比如course_params不可写作courses_params,不然cancancan无法识别。
在View里进行检查
在app/views/admin/courses/index.html.erb里
<% if can? :destroy, Course %>
<%= link_to '删除', admin_course_path(course), method: :delete, data: { confirm: 'Are you sur e?' } , class: "btn btn-default btn-sm" %>
<% end %>
此时,如果我们在ability里定义用户能够删除course,则显示这个按钮,如果用户没有权限,前台就不会显示了。
增加报错提示
我们可以在ApplicationController里定义
class ApplicationController < ActionController::Base
rescue_from CanCan::AccessDenied do |exception|
redirect_to root_path, :alert => exception.message
end
end
这时如果用户进入到权限之外的网址,就会被导至root path,并且看到相应报错了。
下一篇我会写一些具体情况 如nested resource,shallow routes, hash conditions等。