CanCanCan实战指南:让你的权限控制更智能
mc
22 Aug 2024
问题描述
在大多数项目中,通常会涉及多个角色,不同角色对同一资源具有不同的读写权限。例如,管理员可以查看所有商品,而普通用户只能看到已上架的商品。如果要提供一个商品列表API,在引入权限管理模块之前,常见做法有:
- 判断当前角色,并返回相应权限所允许的内容。
- 拆分不同的命名空间,如
app_api
和admin_api
,并返回不同的内容。
然而,如果后续需要添加更多角色,这两种做法都会导致代码难以维护。
解决方案
引入权限管理模块,下面要介绍的是cancancan这个gem,通过各种必要属性构造一个Ability对象,后续有关权限操作都通过该对象去处理,而非当前登录用户的角色
优点
- 易于扩展:项目后续需要增减角色,权限调整,开放Oauth授权等情况,均只需要调整权限配置的代码,基本不需要调整API代码
- 易于维护:权限配置主要集中在
app/models/ability.rb
文件里,遇到权限相关问题时,只要看这个文件并调整就好了
缺点
- 对于十分简单的项目或者单一角色的项目来说,该解决方案会有点繁琐
解决场景描述
下面分别针对以下场景介绍如何使用cancancan
控制可访问内容
需求
- 管理员可以看到所有商品
- 普通用户只能看到已上架商品
代码实现
# app/models/ability.rb
class Ability
include CanCan::Ability
def initialize(user = nil)
case user
when AdminUser
can :read, Product
when User
can :read, Product, published_at: ..Time.current
end
end
end
# app/services/api/v1/products
namespace :products do
desc "列表"
get do
ability = Ability.new(user)
collection = Product.accessible_by(ability, :read)
present collection, with: API::Entities::Product
end
end
控制请求参数
需求
- 管理员可更新分组名称和分组是否热门
- 普通用户只可以更新分组名称
如管理员和用户都又更新分组(Group)的权限,但管理员可修改的内容更广泛,这里也可以通过Ability去控制
代码实现
# app/models/ability.rb
class Ability
include CanCan::Ability
def initialize(user = nil)
case user
when AdminUser
can :update, Group
can :update_hot, Group
when User
can :update, Group, owner: user
end
end
end
# app/services/api/v1/groups
class API::V1::Groups < Grape::API
namespace :groups do
route_param :id do
desc "更新"
params do
optional :title
optional :is_hot, type: Boolean # 是否热门分组
end
put do
ability = Ability.new(user)
resource = Group.find(params[:id])
# 判断是否有权限更新
ability.authorize! :update, resource
# 通过grape的declared把没在params里定义的属性过滤掉
data = declared(params)
# 如果当前登录用户不能编辑is_hot属性,则移除掉
data.delete(:is_hot) unless abilitiy.can?(:update_hot, resource)
if resource.update(data)
present resouce, with: API::Entities::Group
else
# 处理失败情况
end
end
end
end
end
控制响应参数
需求
- 管理员可查看商品基础信息以及成本价
- 普通用户可查看商品基础信息
代码实现
# app/models/ability.rb
class Ability
include CanCan::Ability
def initialize(user = nil)
case user
when AdminUser
can :read, Product
can :read_cost_price, Product
when User
can :read, Product, published_at: ..Time.current
end
end
end
# app/services/api/v1/products
class API::V1::Products < Grape::API
namespace :products do
route_param :id do
desc "详情"
get do
ability = Ability.new(user)
resource = Group.find(params[:id])
only = [:name, :description, :price]
only += [:cost_price] if ability.can?(:read_cost_price, resource)
present resouce, with: API::Entities::Product, only: only
end
end
end
end
这些示例展示了如何使用CanCanCan进行不同场景的权限控制,从而简化和标准化权限管理过程。
Tags
rails
cancancan
权限管理