王梦琪的博客

记录技术学习及感悟

使用CanCanCan进行权限管理(一)

| Comments

为什么要使用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了。

注:

  1. cancancan是根据controller判断的,而非model。
  2. cancancan 很聪明得会在controller里识别同名的资源,在CoursesController里会load和authorize @course, 如果你希望比如在SyllabusController里去识别 @course,你可以这样写
class Admin::SyllabusController < AdminController
 load_and_authorize_resource :course
 ...
end
  1. load_and_authorize_resource:指的是两件事load_resource和authorize_resource.关于这个,xdite有一篇文章,Cancan 實作角色權限設計的最佳實踐(2),在此不再赘言。
  2. 需要补充说明的是,请尽量使用load_and_authorize_resource
  3. 如果在上述controller里直接使用authorize_resource, cancancan是找不到@course这个instance的,所以要么使用load_and_authorize_resource或者在authorize_resource生效前,使用before_filter塞入一个@course的instance
  4. 请注意: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等。

Comments

comments powered by Disqus