Rails插件:CanCan权限验证插件学习总结
CanCan是rails下的一个用于限制用户对网站资源访问控制权限的插件,所有的权限都定义在一个文件中(ability.rb)。
1.安装
在gemfile中加上gem ‘cancan’
2.注意要点
注意:CanCan需要调用controller中的current_user方法来获取当前登录的用户对象,当然也允许用户修改这个方法名称,如下:
(1) 在ApplicationController中定义如下方法
private
def current_ability
@current_ability ||= AccountAbility.new(current_account)
# 上一句话将会将CanCan调用Ability类修改为调用AccountAbility类,并且通过current_account获取当前登录用户
end
(2) 修改Ability参数,在某些情况下会要根据用户及其他相关信息进行权限控制,如限制某个IP的用户访问,因为Ability类中没有request对象,因此需要从controller中传递给Ability,方法如下:(同理可用于session和cookie)
(A) 在ApplicationController中定义如下方法
private
def current_ability
@current_ability ||= Ability.new(current_user, request.remote_ip)
end
(B) Ability类如下:
class Ability
include CanCan:Ability
def initialize(user, ip_address)
can :create, Comment unless BLACKLIST_IPS.include? ip_address
end
end
3.使用方法
(1) 定义Ability类,可以手动建立(model目录下),也可以通过命令行建立。
命令行方法为:rails g cancan:ability
(2) 检测和认证权限的函数
(A) 在controller或view中使用can?, cannot?
(B) authorize!方法只能用于controller中
(C) 可以在controler中使用load_and_authorize_resource或authorize_resource,本方法如要用于RESTful样式的controller中,它将会为所有action添加个before_filter来检测权限,不同的是load_and_authorize_resource会先加载本类model的值。
(3) 处理未授权的访问的方法
如果权限认证失败,cancan会抛出一个CanCan::AccessDenied的异常,你可以在ApplicationController中捕获它来显示自己内容。
rescue_from CanCan::AccessDenied do |exception|
redirect_to root_url, :alert => exception.message
# exception.action, exception.subject
end
(4) 设置对应用程序所有action都检查权限,可以在ApplicationController中添加check_authorization,如果在个别的controller中需要跳过验证,可以在该controller中添加skip_authorization_check。
注意:使用check_authorization可以跟 if 和 unless 条件,如:
check_authorization if||unless) => :函数
4.如何定义Ability类
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # 防止用户未登录
if …..# 判断当前用户是否时管理员
can :manage, :all # 可以管理所有资源
else
can :read, :all # 可以读取所有资源
end
end
end
(1) can函数
can 方法, 对象[, 对象ID]
这里的方法通常为:read, :create, :update和:destroy, 但是它可以为任何方法。对象可以是model的对象或类名
(A) 可以通过alias_action来将几个方法合并成一个方法。如下:
class Ability
include CanCan::Ability
def initialize(user)
alias_action :update, :destroy, :to => :modify
can :modify, Comment
end
end
(B) 传递数组
can [:update, :destroy], [Article, Comment]
(C) 根据条件判断权限
# 允许查看projects中active=true,并且user_id为当前登录用户的project
can :read, Project, :active => true, :user_id => user.id
# 允许查看projects中belongs_to的category(visible = true)的project
can :read, Project, :category => {:visible => true}
# 允许查看projects中priority为1-3之间的project
can :read, Project, :priority => 1..3
# 允许查看projects中belongs_to的group(group的id在用户所属group的id之中)的project
can :read, Project, :group => {:id => user.group_ids}
(D) 根据block条件来判断权限
# 允许更新所有project.priority < 3的project
can :update, Project do |project|
project.priority < 3
end
注意:使用block时,内部只能用当前类的对象,如上个例子只能使用project对象,其他对象都不行,如user等。
5.controller中验证的函数
(1) authorize! 方法, 对象
调用本方法检测无权限时,会抛出一个CanCan::AccessDenied异常
(2) authorize_resource
本方法会自动将当前controller中所有action都加上before_filter来调用authorize!来检测权限
(3) load_and_authorize_resource包含(load_resource和authorize_resource)
与authorize_resurce类似,只是会在验证之前根据controller的名字去自动生成对应的model的对象,如:
class ProductsController < ActiveRecord::Base load_and_authorize_resource def index # 注意生成对象的名字和controller是同名的 # @products = Product.accessible_by(current_ability) end def show # @products = Products.find(params[:id]) end end 6.controller中自定义检测对象的类名load_and_authorize_resource :class => ‘Store::Product’
6.controller中加载验证过程覆盖的方法
class BooksController < ApplicationController before_filter :find_my_book, nly => :show
load_and_authorize_resource
private
def find_my_book
@book = Book.released.find(params[:id])
# 这里会将load_resource自动生成的@book换成自己的
end
end
注:如果只是使用了authorize_resource来验证权限,那么必须使用prepend_before_filter来调用自己的代码
7.当前用户更新自己的信息时,最好清空下ability和当前用户,防止有缓存存在
if @user.update_attributes(params[:user])
@current_ability = nil
@current_user = nil
end
8.无权限的异常捕捉
(1) 错误信息的自定义
(A) authorize! :read, Article, :message => “Unable to read the article.”
(B) raise CanCan::AccessDenied.new(“Not authorized!”, :read, Article)
(C) 修改语言包config/locales/en.yml
en:
unauthorized:
manage:
all: “Not authorized to %{action} %{subject}”
user: “Not allowed to manage other user accounts”
update:
project: “Not allowed to update the project”
9.Ability权限的调试方法
user = User.first # fetch any user you want to test abilities on
project = Project.first # any model you want to test against
ability = Ability.new(user)
ability.can?(:create, project) # see if it returns the expected behavior for that action
ability.can?(:index, Project) # see if user can access the class
Project.accessible_by(ability) # see if returns the records the user can access
Project.accessible_by(ability).to_sql # see what the generated SQL looks like to help determine why it’s not fetching the records you want
另外可以将无权限的异常写入日志文件在rescue_from CanCan:AccessDenied中添加:
Rails.logger.debug “Access denied on #{exception.action} #{exception.subject.inspect}”
10.Ability权限设置的技巧
(1) 可以管理项目的相关信息,但是不能删除项目
can :manage, Project
cannot :destroy, Project
(2) 可以管理自己的项目,但是不能编辑已经锁定的项目
can :manage, Project, :user_id => user.id
can :update, Project do |project|
!project.locked?
end
(3) RESTful格式的action名称,有以下注意点:
:read 等于 :index, :search, :show
:update 等于 :update, :edit
:create 等于 :new, :create
:delete 等于 :destroy, :delete
11.根据用户权限获取数据
通常使用accessible_by(current_ability)来获取当前用户可以:read的信息,当然当使用了load_resource后,可以不需要自己获取。
也可以修改默认的:read,如accessible_by(current_ability, :update)来获取当前用户可以编辑的数据
12.带角色的权限设置
(1) 一个用户一个角色,通过用户表中role的字段来存储,直接在Ability中用角色名称判断
(2) 多个用户对应多个角色,如:
(A) 用到的表如下
用户表:users
字段:name:string, password:string
角色表:roles
字段:name:string
用户和角色关系表:users_roles_relations
字段:role_id:integer, user_id:integer
权限表:permissions
字段:action:string, subject_class:string, role_id:integer
(B) models
class User
has_many :users_roles_relations
has_many :roles, :through => users_roles_relations
end
class Role
has_many :users_roles_relations
has_many :users, :through => users_roles_relations
has_many :permissions
end
class UsersRolesRelation
belongs_to :role
belongs_to :user
end
class Permission
belongs_to :role
end
(C) ability
class Ability
include CanCan::Ability
def initialize(user) # 这里的user是由cancan自动调用current_user来获取到的,最好定义在ApplicationController中
user ||= User.new
if user.roles.find_name(‘admin’) # 当前用户有角色名称为admin的角色时,有所有权限
can :manage, :all
else
user.roles.each do |role|
role.permissions.each do |permission|
can permission.action.to_sym, permission.subject_class.constantize
end
end
end
end
end
(D) 在controller中调用相应的验证函数就行了。
(E) 无权限异常的捕捉,上述也已讲过,只用在ApplicationController中添加rescue_from CanCan::AccessDenied就行了